forked from github/plane
chore: removed unused hooks and components (#2611)
* chore: remove unused hooks * chore: removed useProjectMembers hook * chore: removed issue hooks * fix: build errors
This commit is contained in:
parent
c987c6f308
commit
d5fd69354e
@ -1,12 +1,13 @@
|
|||||||
import React, { FC, Dispatch, SetStateAction, useEffect, useState } from "react";
|
import React, { FC, Dispatch, SetStateAction, useEffect, useState } from "react";
|
||||||
import { Command } from "cmdk";
|
import { Command } from "cmdk";
|
||||||
import { THEME_OPTIONS } from "constants/themes";
|
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { Settings } from "lucide-react";
|
import { Settings } from "lucide-react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
// constants
|
||||||
|
import { THEME_OPTIONS } from "constants/themes";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setIsPaletteOpen: Dispatch<SetStateAction<boolean>>;
|
setIsPaletteOpen: Dispatch<SetStateAction<boolean>>;
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import { Dispatch, SetStateAction, useCallback, FC } from "react";
|
import { Dispatch, SetStateAction, useCallback, FC } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
import { Command } from "cmdk";
|
import { Command } from "cmdk";
|
||||||
|
import { Check } from "lucide-react";
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// services
|
// services
|
||||||
import { IssueService } from "services/issue";
|
import { IssueService } from "services/issue";
|
||||||
// hooks
|
|
||||||
import useProjectMembers from "hooks/use-project-members";
|
|
||||||
// constants
|
|
||||||
import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
|
|
||||||
// ui
|
// ui
|
||||||
import { Avatar } from "@plane/ui";
|
import { Avatar } from "@plane/ui";
|
||||||
// icons
|
|
||||||
import { Check } from "lucide-react";
|
|
||||||
// types
|
// types
|
||||||
import { IUser, IIssue } from "types";
|
import { IUser, IIssue } from "types";
|
||||||
|
// constants
|
||||||
|
import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setIsPaletteOpen: Dispatch<SetStateAction<boolean>>;
|
setIsPaletteOpen: Dispatch<SetStateAction<boolean>>;
|
||||||
@ -24,11 +24,14 @@ type Props = {
|
|||||||
// services
|
// services
|
||||||
const issueService = new IssueService();
|
const issueService = new IssueService();
|
||||||
|
|
||||||
export const ChangeIssueAssignee: FC<Props> = ({ setIsPaletteOpen, issue, user }) => {
|
export const ChangeIssueAssignee: FC<Props> = observer((props) => {
|
||||||
|
const { setIsPaletteOpen, issue, user } = props;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, issueId } = router.query;
|
const { workspaceSlug, projectId, issueId } = router.query;
|
||||||
|
|
||||||
const { members } = useProjectMembers(workspaceSlug as string, projectId as string);
|
const { project: projectStore } = useMobxStore();
|
||||||
|
const members = projectId ? projectStore.members?.[projectId.toString()] : undefined;
|
||||||
|
|
||||||
const options =
|
const options =
|
||||||
members?.map(({ member }) => ({
|
members?.map(({ member }) => ({
|
||||||
@ -104,4 +107,4 @@ export const ChangeIssueAssignee: FC<Props> = ({ setIsPaletteOpen, issue, user }
|
|||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -89,6 +89,7 @@ const LabelPill = ({ labelId }: { labelId: string }) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const EstimatePoint = ({ point }: { point: string }) => {
|
const EstimatePoint = ({ point }: { point: string }) => {
|
||||||
const { estimateValue, isEstimateActive } = useEstimateOption(Number(point));
|
const { estimateValue, isEstimateActive } = useEstimateOption(Number(point));
|
||||||
const currentPoint = Number(point) + 1;
|
const currentPoint = Number(point) + 1;
|
||||||
|
@ -1,349 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { X } from "lucide-react";
|
|
||||||
// ui
|
|
||||||
import { Avatar, PriorityIcon, StateGroupIcon } from "@plane/ui";
|
|
||||||
// helpers
|
|
||||||
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
|
||||||
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
|
||||||
// types
|
|
||||||
import { IIssueFilterOptions, IIssueLabels, IState, IUserLite, TStateGroups } from "types";
|
|
||||||
// constants
|
|
||||||
import { STATE_GROUP_COLORS } from "constants/state";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
filters: Partial<IIssueFilterOptions>;
|
|
||||||
setFilters: (updatedFilter: Partial<IIssueFilterOptions>) => void;
|
|
||||||
clearAllFilters: (...args: any) => void;
|
|
||||||
labels: IIssueLabels[] | undefined;
|
|
||||||
members: IUserLite[] | undefined;
|
|
||||||
states: IState[] | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FiltersList: React.FC<Props> = ({ filters, setFilters, clearAllFilters, labels, members, states }) => {
|
|
||||||
if (!filters) return <></>;
|
|
||||||
|
|
||||||
const nullFilters = Object.keys(filters).filter((key) => filters[key as keyof IIssueFilterOptions] === null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-1 flex-wrap items-center gap-2 text-xs">
|
|
||||||
{Object.keys(filters).map((filterKey) => {
|
|
||||||
const key = filterKey as keyof typeof filters;
|
|
||||||
|
|
||||||
if (filters[key] === null) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={key}
|
|
||||||
className="flex items-center gap-x-2 rounded-full border border-custom-border-200 bg-custom-background-80 px-2 py-1"
|
|
||||||
>
|
|
||||||
<span className="capitalize text-custom-text-200">
|
|
||||||
{key === "target_date" ? "Due Date" : replaceUnderscoreIfSnakeCase(key)}:
|
|
||||||
</span>
|
|
||||||
{filters[key] === null || (filters[key]?.length ?? 0) <= 0 ? (
|
|
||||||
<span className="inline-flex items-center px-2 py-0.5 font-medium">None</span>
|
|
||||||
) : Array.isArray(filters[key]) ? (
|
|
||||||
<div className="space-x-2">
|
|
||||||
<div className="flex flex-wrap items-center gap-1">
|
|
||||||
{key === "state"
|
|
||||||
? filters.state?.map((stateId: string) => {
|
|
||||||
const state = states?.find((s) => s.id === stateId);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<p
|
|
||||||
key={state?.id}
|
|
||||||
className="inline-flex items-center gap-x-1 rounded-full px-2 py-0.5 font-medium"
|
|
||||||
style={{
|
|
||||||
color: state?.color,
|
|
||||||
backgroundColor: `${state?.color}20`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<StateGroupIcon stateGroup={state?.group ?? "backlog"} color={state?.color} />
|
|
||||||
</span>
|
|
||||||
<span>{state?.name ?? ""}</span>
|
|
||||||
<span
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() =>
|
|
||||||
setFilters({
|
|
||||||
state: filters.state?.filter((s: any) => s !== stateId),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<X className="h-3 w-3" />
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: key === "state_group"
|
|
||||||
? filters.state_group?.map((stateGroup) => {
|
|
||||||
const group = stateGroup as TStateGroups;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<p
|
|
||||||
key={group}
|
|
||||||
className="inline-flex items-center gap-x-1 rounded-full px-2 py-0.5 capitalize"
|
|
||||||
style={{
|
|
||||||
color: STATE_GROUP_COLORS[group],
|
|
||||||
backgroundColor: `${STATE_GROUP_COLORS[group]}20`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<StateGroupIcon stateGroup={group} color={undefined} />
|
|
||||||
</span>
|
|
||||||
<span>{group}</span>
|
|
||||||
<span
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() =>
|
|
||||||
setFilters({
|
|
||||||
state_group: filters.state_group?.filter((g) => g !== group),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<X className="h-3 w-3" />
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: key === "priority"
|
|
||||||
? filters.priority?.map((priority: any) => (
|
|
||||||
<p
|
|
||||||
key={priority}
|
|
||||||
className={`inline-flex items-center gap-x-1 rounded-full px-2 py-0.5 capitalize ${
|
|
||||||
priority === "urgent"
|
|
||||||
? "bg-red-500/20 text-red-500"
|
|
||||||
: priority === "high"
|
|
||||||
? "bg-orange-500/20 text-orange-500"
|
|
||||||
: priority === "medium"
|
|
||||||
? "bg-yellow-500/20 text-yellow-500"
|
|
||||||
: priority === "low"
|
|
||||||
? "bg-green-500/20 text-green-500"
|
|
||||||
: "bg-custom-background-90 text-custom-text-200"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<PriorityIcon priority={priority} className="h-3 w-3" />
|
|
||||||
</span>
|
|
||||||
<span>{priority === "null" ? "None" : priority}</span>
|
|
||||||
<span
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() =>
|
|
||||||
setFilters({
|
|
||||||
priority: filters.priority?.filter((p: any) => p !== priority),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<X className="h-3 w-3" />
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
))
|
|
||||||
: key === "mentions"
|
|
||||||
? filters.mentions?.map((mentionId: string) => {
|
|
||||||
const member = members?.find((m) => m.id === mentionId);
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={mentionId}
|
|
||||||
className="inline-flex items-center gap-x-1 rounded-full bg-custom-background-90 px-1"
|
|
||||||
>
|
|
||||||
<Avatar name={member?.display_name} src={member?.avatar} showTooltip={false} />
|
|
||||||
<span>{member?.display_name}</span>
|
|
||||||
<span
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() =>
|
|
||||||
setFilters({
|
|
||||||
mentions: filters.mentions?.filter((p: any) => p !== mentionId),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<X className="h-3 w-3" />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: key === "assignees"
|
|
||||||
? filters.assignees?.map((memberId: string) => {
|
|
||||||
const member = members?.find((m) => m.id === memberId);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={memberId}
|
|
||||||
className="inline-flex items-center gap-x-1 rounded-full bg-custom-background-90 px-1"
|
|
||||||
>
|
|
||||||
<Avatar name={member?.display_name} src={member?.avatar} showTooltip={false} />
|
|
||||||
<span>{member?.display_name}</span>
|
|
||||||
<span
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() =>
|
|
||||||
setFilters({
|
|
||||||
subscriber: filters.subscriber?.filter((p: any) => p !== memberId),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<X className="h-3 w-3" />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: key === "created_by"
|
|
||||||
? filters.created_by?.map((memberId: string) => {
|
|
||||||
const member = members?.find((m) => m.id === memberId);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={`${memberId}-${key}`}
|
|
||||||
className="inline-flex items-center gap-x-1 rounded-full bg-custom-background-90 px-1 capitalize"
|
|
||||||
>
|
|
||||||
<Avatar name={member?.display_name} src={member?.avatar} showTooltip={false} />
|
|
||||||
<span>{member?.display_name}</span>
|
|
||||||
<span
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() =>
|
|
||||||
setFilters({
|
|
||||||
created_by: filters.created_by?.filter((p: any) => p !== memberId),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<X className="h-3 w-3" />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: key === "labels"
|
|
||||||
? filters.labels?.map((labelId: string) => {
|
|
||||||
const label = labels?.find((l) => l.id === labelId);
|
|
||||||
|
|
||||||
if (!label) return null;
|
|
||||||
const color = label.color !== "" ? label.color : "#0f172a";
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="inline-flex items-center gap-x-1 rounded-full px-2 py-0.5"
|
|
||||||
style={{
|
|
||||||
color: color,
|
|
||||||
backgroundColor: `${color}20`, // add 20% opacity
|
|
||||||
}}
|
|
||||||
key={labelId}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="h-1.5 w-1.5 rounded-full"
|
|
||||||
style={{
|
|
||||||
backgroundColor: color,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span>{label.name}</span>
|
|
||||||
<span
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() =>
|
|
||||||
setFilters({
|
|
||||||
labels: filters.labels?.filter((l: any) => l !== labelId),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<X
|
|
||||||
className="h-3 w-3"
|
|
||||||
style={{
|
|
||||||
color: color,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: key === "start_date"
|
|
||||||
? filters.start_date?.map((date: string) => {
|
|
||||||
if (filters.start_date && filters.start_date.length <= 0) return null;
|
|
||||||
|
|
||||||
const splitDate = date.split(";");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={date}
|
|
||||||
className="inline-flex items-center gap-x-1 rounded-full border border-custom-border-200 bg-custom-background-100 px-1 py-0.5"
|
|
||||||
>
|
|
||||||
<div className="h-1.5 w-1.5 rounded-full" />
|
|
||||||
<span className="capitalize">
|
|
||||||
{splitDate[1]} {renderShortDateWithYearFormat(splitDate[0])}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() =>
|
|
||||||
setFilters({
|
|
||||||
start_date: filters.start_date?.filter((d: any) => d !== date),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<X className="h-3 w-3" />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: key === "target_date"
|
|
||||||
? filters.target_date?.map((date: string) => {
|
|
||||||
if (filters.target_date && filters.target_date.length <= 0) return null;
|
|
||||||
|
|
||||||
const splitDate = date.split(";");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={date}
|
|
||||||
className="inline-flex items-center gap-x-1 rounded-full border border-custom-border-200 bg-custom-background-100 px-1 py-0.5"
|
|
||||||
>
|
|
||||||
<div className="h-1.5 w-1.5 rounded-full" />
|
|
||||||
<span className="capitalize">
|
|
||||||
{splitDate[1]} {renderShortDateWithYearFormat(splitDate[0])}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() =>
|
|
||||||
setFilters({
|
|
||||||
target_date: filters.target_date?.filter((d: any) => d !== date),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<X className="h-3 w-3" />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: (filters[key] as any)?.join(", ")}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() =>
|
|
||||||
setFilters({
|
|
||||||
[key]: null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<X className="h-3 w-3" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex items-center gap-x-1 capitalize">
|
|
||||||
{filters[key as keyof typeof filters]}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() =>
|
|
||||||
setFilters({
|
|
||||||
[key]: null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<X className="h-3 w-3" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{Object.keys(filters).length > 0 && nullFilters.length !== Object.keys(filters).length && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={clearAllFilters}
|
|
||||||
className="flex items-center gap-x-1 rounded-full border border-custom-border-200 bg-custom-background-80 px-3 py-1.5 text-xs"
|
|
||||||
>
|
|
||||||
<span>Clear all filters</span>
|
|
||||||
<X className="h-3 w-3" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,3 +1,2 @@
|
|||||||
export * from "./date-filter-modal";
|
export * from "./date-filter-modal";
|
||||||
export * from "./date-filter-select";
|
export * from "./date-filter-select";
|
||||||
export * from "./filters-list";
|
|
||||||
|
@ -2,7 +2,6 @@ export * from "./filters";
|
|||||||
export * from "./modals";
|
export * from "./modals";
|
||||||
export * from "./sidebar";
|
export * from "./sidebar";
|
||||||
export * from "./theme";
|
export * from "./theme";
|
||||||
export * from "./views";
|
|
||||||
export * from "./activity";
|
export * from "./activity";
|
||||||
export * from "./reaction-selector";
|
export * from "./reaction-selector";
|
||||||
export * from "./image-picker-popover";
|
export * from "./image-picker-popover";
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import useSWR, { mutate } from "swr";
|
import useSWR from "swr";
|
||||||
// react hook form
|
// react hook form
|
||||||
import { SubmitHandler, useForm } from "react-hook-form";
|
import { SubmitHandler, useForm } from "react-hook-form";
|
||||||
// headless ui
|
// headless ui
|
||||||
@ -9,7 +9,6 @@ import { Combobox, Dialog, Transition } from "@headlessui/react";
|
|||||||
import { IssueService } from "services/issue";
|
import { IssueService } from "services/issue";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
|
||||||
// ui
|
// ui
|
||||||
import { Button, LayersIcon } from "@plane/ui";
|
import { Button, LayersIcon } from "@plane/ui";
|
||||||
// icons
|
// icons
|
||||||
@ -17,15 +16,7 @@ import { Search } from "lucide-react";
|
|||||||
// types
|
// types
|
||||||
import { IUser, IIssue } from "types";
|
import { IUser, IIssue } from "types";
|
||||||
// fetch keys
|
// fetch keys
|
||||||
import {
|
import { PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
||||||
CYCLE_DETAILS,
|
|
||||||
CYCLE_ISSUES_WITH_PARAMS,
|
|
||||||
MODULE_DETAILS,
|
|
||||||
MODULE_ISSUES_WITH_PARAMS,
|
|
||||||
PROJECT_ISSUES_LIST,
|
|
||||||
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
|
||||||
VIEW_ISSUES,
|
|
||||||
} from "constants/fetch-keys";
|
|
||||||
|
|
||||||
type FormInput = {
|
type FormInput = {
|
||||||
delete_issue_ids: string[];
|
delete_issue_ids: string[];
|
||||||
@ -43,7 +34,7 @@ export const BulkDeleteIssuesModal: React.FC<Props> = (props) => {
|
|||||||
const { isOpen, onClose, user } = props;
|
const { isOpen, onClose, user } = props;
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
// states
|
// states
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
// fetching project issues.
|
// fetching project issues.
|
||||||
@ -53,9 +44,6 @@ export const BulkDeleteIssuesModal: React.FC<Props> = (props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
const { displayFilters, params } = useIssuesView();
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { order_by, group_by, ...viewGanttParams } = params;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@ -89,14 +77,6 @@ export const BulkDeleteIssuesModal: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
if (!Array.isArray(data.delete_issue_ids)) data.delete_issue_ids = [data.delete_issue_ids];
|
if (!Array.isArray(data.delete_issue_ids)) data.delete_issue_ids = [data.delete_issue_ids];
|
||||||
|
|
||||||
const ganttFetchKey = cycleId
|
|
||||||
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString())
|
|
||||||
: moduleId
|
|
||||||
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString())
|
|
||||||
: viewId
|
|
||||||
? VIEW_ISSUES(viewId.toString(), viewGanttParams)
|
|
||||||
: PROJECT_ISSUES_LIST_WITH_PARAMS(projectId?.toString() ?? "");
|
|
||||||
|
|
||||||
await issueService
|
await issueService
|
||||||
.bulkDeleteIssues(
|
.bulkDeleteIssues(
|
||||||
workspaceSlug as string,
|
workspaceSlug as string,
|
||||||
@ -113,17 +93,6 @@ export const BulkDeleteIssuesModal: React.FC<Props> = (props) => {
|
|||||||
message: "Issues deleted successfully!",
|
message: "Issues deleted successfully!",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (displayFilters.layout === "gantt_chart") mutate(ganttFetchKey);
|
|
||||||
else {
|
|
||||||
if (cycleId) {
|
|
||||||
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params));
|
|
||||||
mutate(CYCLE_DETAILS(cycleId.toString()));
|
|
||||||
} else if (moduleId) {
|
|
||||||
mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
|
|
||||||
mutate(MODULE_DETAILS(moduleId as string));
|
|
||||||
} else mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params));
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClose();
|
handleClose();
|
||||||
})
|
})
|
||||||
.catch(() =>
|
.catch(() =>
|
||||||
|
@ -1,30 +1,16 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import { mutate } from "swr";
|
|
||||||
|
|
||||||
// headless ui
|
|
||||||
import { Combobox, Dialog, Transition } from "@headlessui/react";
|
import { Combobox, Dialog, Transition } from "@headlessui/react";
|
||||||
|
import { Rocket, Search, X } from "lucide-react";
|
||||||
// services
|
// services
|
||||||
import { ProjectService } from "services/project";
|
import { ProjectService } from "services/project";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
|
||||||
import useDebounce from "hooks/use-debounce";
|
import useDebounce from "hooks/use-debounce";
|
||||||
// ui
|
// ui
|
||||||
import { Button, LayersIcon, Loader, ToggleSwitch, Tooltip } from "@plane/ui";
|
import { Button, LayersIcon, Loader, ToggleSwitch, Tooltip } from "@plane/ui";
|
||||||
// icons
|
|
||||||
import { Rocket, Search, X } from "lucide-react";
|
|
||||||
// types
|
// types
|
||||||
import { ISearchIssueResponse, TProjectIssuesSearchParams } from "types";
|
import { ISearchIssueResponse, TProjectIssuesSearchParams } from "types";
|
||||||
// fetch-keys
|
|
||||||
import {
|
|
||||||
CYCLE_DETAILS,
|
|
||||||
CYCLE_ISSUES_WITH_PARAMS,
|
|
||||||
MODULE_DETAILS,
|
|
||||||
MODULE_ISSUES_WITH_PARAMS,
|
|
||||||
} from "constants/fetch-keys";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -53,12 +39,10 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
|
|||||||
const debouncedSearchTerm: string = useDebounce(searchTerm, 500);
|
const debouncedSearchTerm: string = useDebounce(searchTerm, 500);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { params } = useIssuesView();
|
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
onClose();
|
onClose();
|
||||||
setSearchTerm("");
|
setSearchTerm("");
|
||||||
@ -81,16 +65,6 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
|
|||||||
|
|
||||||
await handleOnSubmit(selectedIssues).finally(() => setIsSubmitting(false));
|
await handleOnSubmit(selectedIssues).finally(() => setIsSubmitting(false));
|
||||||
|
|
||||||
if (cycleId) {
|
|
||||||
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params));
|
|
||||||
mutate(CYCLE_DETAILS(cycleId as string));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (moduleId) {
|
|
||||||
mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
|
|
||||||
mutate(MODULE_DETAILS(moduleId as string));
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClose();
|
handleClose();
|
||||||
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
|
@ -15,6 +15,7 @@ type Props = {
|
|||||||
|
|
||||||
export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, handleEditLink, userAuth }) => {
|
export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, handleEditLink, userAuth }) => {
|
||||||
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
|
const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{links.map((link) => (
|
{links.map((link) => (
|
||||||
|
@ -5,7 +5,6 @@ import Image from "next/image";
|
|||||||
import { Tab } from "@headlessui/react";
|
import { Tab } from "@headlessui/react";
|
||||||
// hooks
|
// hooks
|
||||||
import useLocalStorage from "hooks/use-local-storage";
|
import useLocalStorage from "hooks/use-local-storage";
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
|
||||||
// images
|
// images
|
||||||
import emptyLabel from "public/empty-state/empty_label.svg";
|
import emptyLabel from "public/empty-state/empty_label.svg";
|
||||||
import emptyMembers from "public/empty-state/empty_members.svg";
|
import emptyMembers from "public/empty-state/empty_members.svg";
|
||||||
@ -47,8 +46,6 @@ export const SidebarProgressStats: React.FC<Props> = ({
|
|||||||
noBackground,
|
noBackground,
|
||||||
isPeekView = false,
|
isPeekView = false,
|
||||||
}) => {
|
}) => {
|
||||||
const { filters, setFilters } = useIssuesView();
|
|
||||||
|
|
||||||
const { storedValue: tab, setValue: setTab } = useLocalStorage("tab", "Assignees");
|
const { storedValue: tab, setValue: setTab } = useLocalStorage("tab", "Assignees");
|
||||||
|
|
||||||
const currentValue = (tab: string | null) => {
|
const currentValue = (tab: string | null) => {
|
||||||
@ -145,16 +142,17 @@ export const SidebarProgressStats: React.FC<Props> = ({
|
|||||||
total={assignee.total_issues}
|
total={assignee.total_issues}
|
||||||
{...(!isPeekView && {
|
{...(!isPeekView && {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (filters?.assignees?.includes(assignee.assignee_id ?? ""))
|
// TODO: set filters here
|
||||||
setFilters({
|
// if (filters?.assignees?.includes(assignee.assignee_id ?? ""))
|
||||||
assignees: filters?.assignees?.filter((a) => a !== assignee.assignee_id),
|
// setFilters({
|
||||||
});
|
// assignees: filters?.assignees?.filter((a) => a !== assignee.assignee_id),
|
||||||
else
|
// });
|
||||||
setFilters({
|
// else
|
||||||
assignees: [...(filters?.assignees ?? []), assignee.assignee_id ?? ""],
|
// setFilters({
|
||||||
});
|
// assignees: [...(filters?.assignees ?? []), assignee.assignee_id ?? ""],
|
||||||
|
// });
|
||||||
},
|
},
|
||||||
selected: filters?.assignees?.includes(assignee.assignee_id ?? ""),
|
// selected: filters?.assignees?.includes(assignee.assignee_id ?? ""),
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -203,14 +201,15 @@ export const SidebarProgressStats: React.FC<Props> = ({
|
|||||||
completed={label.completed_issues}
|
completed={label.completed_issues}
|
||||||
total={label.total_issues}
|
total={label.total_issues}
|
||||||
{...(!isPeekView && {
|
{...(!isPeekView && {
|
||||||
|
// TODO: set filters here
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (filters.labels?.includes(label.label_id ?? ""))
|
// if (filters.labels?.includes(label.label_id ?? ""))
|
||||||
setFilters({
|
// setFilters({
|
||||||
labels: filters?.labels?.filter((l) => l !== label.label_id),
|
// labels: filters?.labels?.filter((l) => l !== label.label_id),
|
||||||
});
|
// });
|
||||||
else setFilters({ labels: [...(filters?.labels ?? []), label.label_id ?? ""] });
|
// else setFilters({ labels: [...(filters?.labels ?? []), label.label_id ?? ""] });
|
||||||
},
|
},
|
||||||
selected: filters?.labels?.includes(label.label_id ?? ""),
|
// selected: filters?.labels?.includes(label.label_id ?? ""),
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
// react hook form
|
|
||||||
import { useFormContext } from "react-hook-form";
|
|
||||||
|
|
||||||
// hooks
|
|
||||||
import useProjectDetails from "hooks/use-project-details";
|
|
||||||
|
|
||||||
// components
|
|
||||||
import { InlineCreateIssueFormWrapper } from "components/core";
|
|
||||||
|
|
||||||
// types
|
|
||||||
import { IIssue } from "types";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isOpen: boolean;
|
|
||||||
handleClose: () => void;
|
|
||||||
onSuccess?: (data: IIssue) => Promise<void> | void;
|
|
||||||
prePopulatedData?: Partial<IIssue>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const InlineInput = () => {
|
|
||||||
const { projectDetails } = useProjectDetails();
|
|
||||||
|
|
||||||
const { register, setFocus } = useFormContext();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setFocus("name");
|
|
||||||
}, [setFocus]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="w-[14px] h-[14px] rounded-full border border-custom-border-1000 flex-shrink-0" />
|
|
||||||
<h4 className="text-sm text-custom-text-400">{projectDetails?.identifier ?? "..."}</h4>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
autoComplete="off"
|
|
||||||
placeholder="Issue Title"
|
|
||||||
{...register("name", {
|
|
||||||
required: "Issue title is required.",
|
|
||||||
})}
|
|
||||||
className="w-full px-2 rounded-md bg-transparent text-sm font-medium leading-5 text-custom-text-200 outline-none"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GanttInlineCreateIssueForm: React.FC<Props> = (props) => (
|
|
||||||
<>
|
|
||||||
<InlineCreateIssueFormWrapper
|
|
||||||
className="flex py-3 px-4 border-[0.5px] border-custom-border-100 mr-2.5 items-center rounded gap-x-2 bg-custom-background-100 shadow-custom-shadow-sm"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<InlineInput />
|
|
||||||
</InlineCreateIssueFormWrapper>
|
|
||||||
{props.isOpen && (
|
|
||||||
<p className="text-xs ml-3 mt-3 italic text-custom-text-200">
|
|
||||||
Press {"'"}Enter{"'"} to add another issue
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
@ -1 +0,0 @@
|
|||||||
export * from "./inline-issue-create-wrapper";
|
|
@ -1,238 +0,0 @@
|
|||||||
import { useEffect, useRef } from "react";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { mutate } from "swr";
|
|
||||||
import { useForm, FormProvider } from "react-hook-form";
|
|
||||||
import { Transition } from "@headlessui/react";
|
|
||||||
// services
|
|
||||||
import { ModuleService } from "services/module.service";
|
|
||||||
import { IssueService, IssueDraftService } from "services/issue";
|
|
||||||
// hooks
|
|
||||||
import useToast from "hooks/use-toast";
|
|
||||||
import useUser from "hooks/use-user";
|
|
||||||
import useKeypress from "hooks/use-keypress";
|
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
|
||||||
import useMyIssues from "hooks/my-issues/use-my-issues";
|
|
||||||
import useGanttChartIssues from "hooks/gantt-chart/issue-view";
|
|
||||||
// import useCalendarIssuesView from "hooks/use-calendar-issues-view";
|
|
||||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
|
||||||
// import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
|
|
||||||
// helpers
|
|
||||||
import { getFetchKeysForIssueMutation } from "helpers/string.helper";
|
|
||||||
|
|
||||||
// fetch-keys
|
|
||||||
import {
|
|
||||||
USER_ISSUE,
|
|
||||||
SUB_ISSUES,
|
|
||||||
CYCLE_ISSUES_WITH_PARAMS,
|
|
||||||
MODULE_ISSUES_WITH_PARAMS,
|
|
||||||
CYCLE_DETAILS,
|
|
||||||
MODULE_DETAILS,
|
|
||||||
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
|
||||||
PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS,
|
|
||||||
} from "constants/fetch-keys";
|
|
||||||
|
|
||||||
// types
|
|
||||||
import { IIssue } from "types";
|
|
||||||
|
|
||||||
const defaultValues: Partial<IIssue> = {
|
|
||||||
name: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
isOpen: boolean;
|
|
||||||
handleClose: () => void;
|
|
||||||
onSuccess?: (data: IIssue) => Promise<void> | void;
|
|
||||||
prePopulatedData?: Partial<IIssue>;
|
|
||||||
className?: string;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
const issueService = new IssueService();
|
|
||||||
const issueDraftService = new IssueDraftService();
|
|
||||||
const moduleService = new ModuleService();
|
|
||||||
|
|
||||||
export const addIssueToCycle = async (
|
|
||||||
workspaceSlug: string,
|
|
||||||
projectId: string,
|
|
||||||
issueId: string,
|
|
||||||
cycleId: string,
|
|
||||||
user: any,
|
|
||||||
params: any
|
|
||||||
) => {
|
|
||||||
if (!workspaceSlug || !projectId) return;
|
|
||||||
|
|
||||||
await issueService
|
|
||||||
.addIssueToCycle(
|
|
||||||
workspaceSlug as string,
|
|
||||||
projectId.toString(),
|
|
||||||
cycleId,
|
|
||||||
{
|
|
||||||
issues: [issueId],
|
|
||||||
},
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
if (cycleId) {
|
|
||||||
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId, params));
|
|
||||||
mutate(CYCLE_DETAILS(cycleId as string));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addIssueToModule = async (
|
|
||||||
workspaceSlug: string,
|
|
||||||
projectId: string,
|
|
||||||
issueId: string,
|
|
||||||
moduleId: string,
|
|
||||||
user: any,
|
|
||||||
params: any
|
|
||||||
) => {
|
|
||||||
await moduleService
|
|
||||||
.addIssuesToModule(
|
|
||||||
workspaceSlug as string,
|
|
||||||
projectId.toString(),
|
|
||||||
moduleId as string,
|
|
||||||
{
|
|
||||||
issues: [issueId],
|
|
||||||
},
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
if (moduleId) {
|
|
||||||
mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
|
|
||||||
mutate(MODULE_DETAILS(moduleId as string));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const InlineCreateIssueFormWrapper: React.FC<Props> = (props) => {
|
|
||||||
const { isOpen, handleClose, onSuccess, prePopulatedData, children, className } = props;
|
|
||||||
|
|
||||||
const ref = useRef<HTMLFormElement>(null);
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
|
||||||
|
|
||||||
const isDraftIssues = router.pathname?.split("/")?.[4] === "draft-issues";
|
|
||||||
|
|
||||||
const { user } = useUser();
|
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
|
||||||
|
|
||||||
const { displayFilters, params } = useIssuesView();
|
|
||||||
// const { params: calendarParams } = useCalendarIssuesView();
|
|
||||||
const { ...viewGanttParams } = params;
|
|
||||||
// const { params: spreadsheetParams } = useSpreadsheetIssuesView();
|
|
||||||
const { groupedIssues, mutateMyIssues } = useMyIssues(workspaceSlug?.toString());
|
|
||||||
const { params: ganttParams } = useGanttChartIssues(workspaceSlug?.toString(), projectId?.toString());
|
|
||||||
|
|
||||||
const method = useForm<IIssue>({ defaultValues });
|
|
||||||
const {
|
|
||||||
reset,
|
|
||||||
handleSubmit,
|
|
||||||
getValues,
|
|
||||||
formState: { errors, isSubmitting },
|
|
||||||
} = method;
|
|
||||||
|
|
||||||
useOutsideClickDetector(ref, handleClose);
|
|
||||||
useKeypress("Escape", handleClose);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const values = getValues();
|
|
||||||
|
|
||||||
if (prePopulatedData) reset({ ...defaultValues, ...values, ...prePopulatedData });
|
|
||||||
}, [reset, prePopulatedData, getValues]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isOpen) reset({ ...defaultValues });
|
|
||||||
}, [isOpen, reset]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!errors) return;
|
|
||||||
|
|
||||||
Object.keys(errors).forEach((key) => {
|
|
||||||
const error = errors[key as keyof IIssue];
|
|
||||||
|
|
||||||
setToastAlert({
|
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message: error?.message?.toString() || "Some error occurred. Please try again.",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, [errors, setToastAlert]);
|
|
||||||
|
|
||||||
const { ganttFetchKey } = getFetchKeysForIssueMutation({
|
|
||||||
cycleId: cycleId,
|
|
||||||
moduleId: moduleId,
|
|
||||||
viewId: viewId,
|
|
||||||
projectId: projectId?.toString() ?? "",
|
|
||||||
viewGanttParams,
|
|
||||||
ganttParams,
|
|
||||||
});
|
|
||||||
|
|
||||||
const onSubmitHandler = async (formData: IIssue) => {
|
|
||||||
if (!workspaceSlug || !projectId || !user || isSubmitting) return;
|
|
||||||
|
|
||||||
reset({ ...defaultValues });
|
|
||||||
|
|
||||||
await (!isDraftIssues
|
|
||||||
? issueService.createIssue(workspaceSlug.toString(), projectId.toString(), formData, user)
|
|
||||||
: issueDraftService.createDraftIssue(workspaceSlug.toString(), projectId.toString(), formData)
|
|
||||||
)
|
|
||||||
.then(async (res) => {
|
|
||||||
await mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params));
|
|
||||||
if (formData.cycle && formData.cycle !== "")
|
|
||||||
await addIssueToCycle(workspaceSlug.toString(), projectId.toString(), res.id, formData.cycle, user, params);
|
|
||||||
if (formData.module && formData.module !== "")
|
|
||||||
await addIssueToModule(workspaceSlug.toString(), projectId.toString(), res.id, formData.module, user, params);
|
|
||||||
|
|
||||||
if (isDraftIssues) await mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(projectId.toString() ?? "", params));
|
|
||||||
if (displayFilters.layout === "gantt_chart") await mutate(ganttFetchKey);
|
|
||||||
if (groupedIssues) await mutateMyIssues();
|
|
||||||
|
|
||||||
setToastAlert({
|
|
||||||
type: "success",
|
|
||||||
title: "Success!",
|
|
||||||
message: "Issue created successfully.",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (onSuccess) await onSuccess(res);
|
|
||||||
|
|
||||||
if (formData.assignees?.some((assignee) => assignee === user?.id)) mutate(USER_ISSUE(workspaceSlug as string));
|
|
||||||
|
|
||||||
if (formData.parent && formData.parent !== "") mutate(SUB_ISSUES(formData.parent));
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
Object.keys(err || {}).forEach((key) => {
|
|
||||||
const error = err?.[key];
|
|
||||||
const errorTitle = error ? (Array.isArray(error) ? error.join(", ") : error) : null;
|
|
||||||
|
|
||||||
setToastAlert({
|
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message: errorTitle || "Some error occurred. Please try again.",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Transition
|
|
||||||
show={isOpen}
|
|
||||||
enter="transition ease-in-out duration-200 transform"
|
|
||||||
enterFrom="opacity-0 scale-95"
|
|
||||||
enterTo="opacity-100 scale-100"
|
|
||||||
leave="transition ease-in-out duration-200 transform"
|
|
||||||
leaveFrom="opacity-100 scale-100"
|
|
||||||
leaveTo="opacity-0 scale-95"
|
|
||||||
>
|
|
||||||
<FormProvider {...method}>
|
|
||||||
<form ref={ref} className={className} onSubmit={handleSubmit(onSubmitHandler)}>
|
|
||||||
{children}
|
|
||||||
</form>
|
|
||||||
</FormProvider>
|
|
||||||
</Transition>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,17 +1,16 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import useSWR, { mutate } from "swr";
|
import useSWR from "swr";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import { CycleService } from "services/cycle.service";
|
import { CycleService } from "services/cycle.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
|
||||||
//icons
|
//icons
|
||||||
import { ContrastIcon, TransferIcon } from "@plane/ui";
|
import { ContrastIcon, TransferIcon } from "@plane/ui";
|
||||||
import { AlertCircle, Search, X } from "lucide-react";
|
import { AlertCircle, Search, X } from "lucide-react";
|
||||||
// fetch-key
|
// fetch-key
|
||||||
import { CYCLE_ISSUES_WITH_PARAMS, INCOMPLETE_CYCLES_LIST } from "constants/fetch-keys";
|
import { INCOMPLETE_CYCLES_LIST } from "constants/fetch-keys";
|
||||||
// types
|
// types
|
||||||
import { ICycle } from "types";
|
import { ICycle } from "types";
|
||||||
//helper
|
//helper
|
||||||
@ -30,15 +29,12 @@ export const TransferIssuesModal: React.FC<Props> = ({ isOpen, handleClose }) =>
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId } = router.query;
|
const { workspaceSlug, projectId, cycleId } = router.query;
|
||||||
|
|
||||||
const { params } = useIssuesView();
|
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const transferIssue = async (payload: any) => {
|
const transferIssue = async (payload: any) => {
|
||||||
await cycleService
|
await cycleService
|
||||||
.transferIssues(workspaceSlug as string, projectId as string, cycleId as string, payload)
|
.transferIssues(workspaceSlug as string, projectId as string, cycleId as string, payload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params));
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Issues transfered successfully",
|
title: "Issues transfered successfully",
|
||||||
|
@ -6,8 +6,6 @@ import { Popover } from "@headlessui/react";
|
|||||||
|
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// contexts
|
|
||||||
import { useProjectMyMembership } from "contexts/project-member.context";
|
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
// components
|
||||||
@ -38,9 +36,9 @@ export const InboxActionsHeader = observer(() => {
|
|||||||
const { inboxIssues: inboxIssuesStore, inboxIssueDetails: inboxIssueDetailsStore, user: userStore } = useMobxStore();
|
const { inboxIssues: inboxIssuesStore, inboxIssueDetails: inboxIssueDetailsStore, user: userStore } = useMobxStore();
|
||||||
|
|
||||||
const user = userStore?.currentUser;
|
const user = userStore?.currentUser;
|
||||||
|
const userRole = userStore.currentProjectRole;
|
||||||
const issuesList = inboxId ? inboxIssuesStore.inboxIssues[inboxId.toString()] : null;
|
const issuesList = inboxId ? inboxIssuesStore.inboxIssues[inboxId.toString()] : null;
|
||||||
|
|
||||||
const { memberRole } = useProjectMyMembership();
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const markInboxStatus = async (data: TInboxStatus) => {
|
const markInboxStatus = async (data: TInboxStatus) => {
|
||||||
@ -73,7 +71,7 @@ export const InboxActionsHeader = observer(() => {
|
|||||||
}, [issue]);
|
}, [issue]);
|
||||||
|
|
||||||
const issueStatus = issue?.issue_inbox[0].status;
|
const issueStatus = issue?.issue_inbox[0].status;
|
||||||
const isAllowed = memberRole.isMember || memberRole.isOwner;
|
const isAllowed = userRole === 15 || userRole === 20;
|
||||||
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const tomorrow = new Date(today);
|
const tomorrow = new Date(today);
|
||||||
|
@ -7,8 +7,6 @@ import { AlertTriangle, CheckCircle2, Clock, Copy, ExternalLink, Inbox, XCircle
|
|||||||
|
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// contexts
|
|
||||||
import { useProjectMyMembership } from "contexts/project-member.context";
|
|
||||||
// components
|
// components
|
||||||
import { IssueDescriptionForm, IssueDetailsSidebar, IssueReaction } from "components/issues";
|
import { IssueDescriptionForm, IssueDetailsSidebar, IssueReaction } from "components/issues";
|
||||||
import { InboxIssueActivity } from "components/inbox";
|
import { InboxIssueActivity } from "components/inbox";
|
||||||
@ -35,8 +33,7 @@ export const InboxMainContent: React.FC = observer(() => {
|
|||||||
const { inboxIssues: inboxIssuesStore, inboxIssueDetails: inboxIssueDetailsStore, user: userStore } = useMobxStore();
|
const { inboxIssues: inboxIssuesStore, inboxIssueDetails: inboxIssueDetailsStore, user: userStore } = useMobxStore();
|
||||||
|
|
||||||
const user = userStore.currentUser;
|
const user = userStore.currentUser;
|
||||||
|
const userRole = userStore.currentProjectRole;
|
||||||
const { memberRole } = useProjectMyMembership();
|
|
||||||
|
|
||||||
const { reset, control, watch } = useForm<IIssue>({
|
const { reset, control, watch } = useForm<IIssue>({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
@ -225,7 +222,7 @@ export const InboxMainContent: React.FC = observer(() => {
|
|||||||
description_html: issueDetails.description_html,
|
description_html: issueDetails.description_html,
|
||||||
}}
|
}}
|
||||||
handleFormSubmit={submitChanges}
|
handleFormSubmit={submitChanges}
|
||||||
isAllowed={memberRole.isMember || memberRole.isOwner || user?.id === issueDetails.created_by}
|
isAllowed={userRole === 15 || userRole === 20 || user?.id === issueDetails.created_by}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { IMentionHighlight, IMentionSuggestion, RichTextEditorWithRef } from "@plane/rich-text-editor";
|
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
|
||||||
|
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
@ -15,8 +15,6 @@ import { IssuePrioritySelect } from "components/issues/select";
|
|||||||
import { Button, Input, ToggleSwitch } from "@plane/ui";
|
import { Button, Input, ToggleSwitch } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
import useProjectMembers from "hooks/use-project-members";
|
|
||||||
import useUser from "hooks/use-user";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { mutate } from "swr";
|
|
||||||
|
|
||||||
import useUser from "hooks/use-user";
|
|
||||||
|
|
||||||
// headless ui
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// services
|
// services
|
||||||
import { IssueDraftService } from "services/issue";
|
import { IssueDraftService } from "services/issue";
|
||||||
// hooks
|
// hooks
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// icons
|
// icons
|
||||||
import { AlertTriangle } from "lucide-react";
|
import { AlertTriangle } from "lucide-react";
|
||||||
@ -19,8 +14,6 @@ import { AlertTriangle } from "lucide-react";
|
|||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import type { IIssue } from "types";
|
import type { IIssue } from "types";
|
||||||
// fetch-keys
|
|
||||||
import { PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS } from "constants/fetch-keys";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -31,20 +24,19 @@ type Props = {
|
|||||||
|
|
||||||
const issueDraftService = new IssueDraftService();
|
const issueDraftService = new IssueDraftService();
|
||||||
|
|
||||||
export const DeleteDraftIssueModal: React.FC<Props> = (props) => {
|
export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
|
||||||
const { isOpen, handleClose, data, onSubmit } = props;
|
const { isOpen, handleClose, data, onSubmit } = props;
|
||||||
|
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const { user: userStore } = useMobxStore();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const user = userStore.currentUser;
|
||||||
|
|
||||||
const { params } = useIssuesView();
|
const router = useRouter();
|
||||||
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { user } = useUser();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsDeleteLoading(false);
|
setIsDeleteLoading(false);
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
@ -64,7 +56,7 @@ export const DeleteDraftIssueModal: React.FC<Props> = (props) => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
setIsDeleteLoading(false);
|
setIsDeleteLoading(false);
|
||||||
handleClose();
|
handleClose();
|
||||||
mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(projectId as string, params));
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
message: "Draft Issue deleted successfully",
|
message: "Draft Issue deleted successfully",
|
||||||
@ -146,4 +138,4 @@ export const DeleteDraftIssueModal: React.FC<Props> = (props) => {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</Transition.Root>
|
</Transition.Root>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -9,28 +9,14 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
|||||||
import { IssueService, IssueDraftService } from "services/issue";
|
import { IssueService, IssueDraftService } from "services/issue";
|
||||||
import { ModuleService } from "services/module.service";
|
import { ModuleService } from "services/module.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useLocalStorage from "hooks/use-local-storage";
|
import useLocalStorage from "hooks/use-local-storage";
|
||||||
import useMyIssues from "hooks/my-issues/use-my-issues";
|
|
||||||
// components
|
// components
|
||||||
import { DraftIssueForm } from "components/issues";
|
import { DraftIssueForm } from "components/issues";
|
||||||
// types
|
// types
|
||||||
import type { IIssue } from "types";
|
import type { IIssue } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import { PROJECT_ISSUES_DETAILS, USER_ISSUE, SUB_ISSUES } from "constants/fetch-keys";
|
||||||
PROJECT_ISSUES_DETAILS,
|
|
||||||
USER_ISSUE,
|
|
||||||
SUB_ISSUES,
|
|
||||||
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
|
||||||
CYCLE_ISSUES_WITH_PARAMS,
|
|
||||||
MODULE_ISSUES_WITH_PARAMS,
|
|
||||||
VIEW_ISSUES,
|
|
||||||
PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS,
|
|
||||||
CYCLE_DETAILS,
|
|
||||||
MODULE_DETAILS,
|
|
||||||
} from "constants/fetch-keys";
|
|
||||||
|
|
||||||
interface IssuesModalProps {
|
interface IssuesModalProps {
|
||||||
data?: IIssue | null;
|
data?: IIssue | null;
|
||||||
@ -77,21 +63,15 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
const [prePopulateData, setPreloadedData] = useState<Partial<IIssue> | undefined>(undefined);
|
const [prePopulateData, setPreloadedData] = useState<Partial<IIssue> | undefined>(undefined);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||||
|
|
||||||
const { project: projectStore } = useMobxStore();
|
const { project: projectStore, user: userStore } = useMobxStore();
|
||||||
|
|
||||||
|
const user = userStore.currentUser;
|
||||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
||||||
|
|
||||||
const { displayFilters, params } = useIssuesView();
|
|
||||||
const { ...viewGanttParams } = params;
|
|
||||||
|
|
||||||
const { user } = useUser();
|
|
||||||
|
|
||||||
const { clearValue: clearDraftIssueLocalStorage } = useLocalStorage("draftedIssue", {});
|
const { clearValue: clearDraftIssueLocalStorage } = useLocalStorage("draftedIssue", {});
|
||||||
|
|
||||||
const { groupedIssues, mutateMyIssues } = useMyIssues(workspaceSlug?.toString());
|
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
@ -184,31 +164,19 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null);
|
setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null);
|
||||||
}, [activeProject, data, projectId, projects, isOpen, prePopulateData]);
|
}, [activeProject, data, projectId, projects, isOpen, prePopulateData]);
|
||||||
|
|
||||||
const ganttFetchKey = cycleId
|
|
||||||
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString())
|
|
||||||
: moduleId
|
|
||||||
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString())
|
|
||||||
: viewId
|
|
||||||
? VIEW_ISSUES(viewId.toString(), viewGanttParams)
|
|
||||||
: PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject?.toString() ?? "");
|
|
||||||
|
|
||||||
const createDraftIssue = async (payload: Partial<IIssue>) => {
|
const createDraftIssue = async (payload: Partial<IIssue>) => {
|
||||||
if (!workspaceSlug || !activeProject || !user) return;
|
if (!workspaceSlug || !activeProject || !user) return;
|
||||||
|
|
||||||
await issueDraftService
|
await issueDraftService
|
||||||
.createDraftIssue(workspaceSlug as string, activeProject ?? "", payload)
|
.createDraftIssue(workspaceSlug as string, activeProject ?? "", payload)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
|
||||||
|
|
||||||
if (groupedIssues) mutateMyIssues();
|
|
||||||
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "Issue created successfully.",
|
message: "Issue created successfully.",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (payload.assignees?.some((assignee) => assignee === user?.id)) mutate(USER_ISSUE(workspaceSlug as string));
|
if (payload.assignees?.some((assignee) => assignee === user?.id)) mutate(USER_ISSUE(workspaceSlug.toString()));
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
@ -231,8 +199,6 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
mutate<IIssue>(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false);
|
mutate<IIssue>(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false);
|
||||||
} else {
|
} else {
|
||||||
if (payload.parent) mutate(SUB_ISSUES(payload.parent.toString()));
|
if (payload.parent) mutate(SUB_ISSUES(payload.parent.toString()));
|
||||||
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
|
||||||
mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!payload.is_draft) {
|
if (!payload.is_draft) {
|
||||||
@ -258,64 +224,42 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addIssueToCycle = async (issueId: string, cycleId: string) => {
|
const addIssueToCycle = async (issueId: string, cycleId: string) => {
|
||||||
if (!workspaceSlug || !activeProject) return;
|
if (!workspaceSlug || !activeProject || !user) return;
|
||||||
|
|
||||||
await issueService
|
await issueService.addIssueToCycle(
|
||||||
.addIssueToCycle(
|
workspaceSlug as string,
|
||||||
workspaceSlug as string,
|
activeProject ?? "",
|
||||||
activeProject ?? "",
|
cycleId,
|
||||||
cycleId,
|
{
|
||||||
{
|
issues: [issueId],
|
||||||
issues: [issueId],
|
},
|
||||||
},
|
user
|
||||||
user
|
);
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
if (cycleId) {
|
|
||||||
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId, params));
|
|
||||||
mutate(CYCLE_DETAILS(cycleId as string));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const addIssueToModule = async (issueId: string, moduleId: string) => {
|
const addIssueToModule = async (issueId: string, moduleId: string) => {
|
||||||
if (!workspaceSlug || !activeProject) return;
|
if (!workspaceSlug || !activeProject || !user) return;
|
||||||
|
|
||||||
await moduleService
|
await moduleService.addIssuesToModule(
|
||||||
.addIssuesToModule(
|
workspaceSlug as string,
|
||||||
workspaceSlug as string,
|
activeProject ?? "",
|
||||||
activeProject ?? "",
|
moduleId as string,
|
||||||
moduleId as string,
|
{
|
||||||
{
|
issues: [issueId],
|
||||||
issues: [issueId],
|
},
|
||||||
},
|
user
|
||||||
user
|
);
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
if (moduleId) {
|
|
||||||
mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
|
|
||||||
mutate(MODULE_DETAILS(moduleId as string));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createIssue = async (payload: Partial<IIssue>) => {
|
const createIssue = async (payload: Partial<IIssue>) => {
|
||||||
if (!workspaceSlug || !activeProject) return;
|
if (!workspaceSlug || !activeProject || !user) return;
|
||||||
|
|
||||||
await issueService
|
await issueService
|
||||||
.createIssue(workspaceSlug as string, activeProject ?? "", payload, user)
|
.createIssue(workspaceSlug.toString(), activeProject, payload, user)
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
|
||||||
if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res.id, payload.cycle);
|
if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res.id, payload.cycle);
|
||||||
if (payload.module && payload.module !== "") await addIssueToModule(res.id, payload.module);
|
if (payload.module && payload.module !== "") await addIssueToModule(res.id, payload.module);
|
||||||
|
|
||||||
if (displayFilters.layout === "gantt_chart")
|
|
||||||
mutate(ganttFetchKey, {
|
|
||||||
start_target_date: true,
|
|
||||||
order_by: "sort_order",
|
|
||||||
});
|
|
||||||
if (groupedIssues) mutateMyIssues();
|
|
||||||
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
@ -400,7 +344,7 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
projectId={activeProject ?? ""}
|
projectId={activeProject ?? ""}
|
||||||
setActiveProject={setActiveProject}
|
setActiveProject={setActiveProject}
|
||||||
status={data ? true : false}
|
status={data ? true : false}
|
||||||
user={user}
|
user={user ?? undefined}
|
||||||
fieldsToShow={fieldsToShow}
|
fieldsToShow={fieldsToShow}
|
||||||
/>
|
/>
|
||||||
</Dialog.Panel>
|
</Dialog.Panel>
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// ui icons
|
// ui icons
|
||||||
import { DiceIcon, DoubleCircleIcon, UserGroupIcon } from "@plane/ui";
|
import { DiceIcon, DoubleCircleIcon, UserGroupIcon } from "@plane/ui";
|
||||||
import { CalendarDays, ContrastIcon, Link2, Plus, Signal, Tag, Triangle, User2 } from "lucide-react";
|
import { CalendarDays, ContrastIcon, Link2, Plus, Signal, Tag, Triangle, User2 } from "lucide-react";
|
||||||
@ -23,10 +25,7 @@ import { CustomDatePicker } from "components/ui";
|
|||||||
import { LinkModal, LinksList } from "components/core";
|
import { LinkModal, LinksList } from "components/core";
|
||||||
// types
|
// types
|
||||||
import { ICycle, IIssue, IIssueLink, IModule, TIssuePriorities, linkDetails } from "types";
|
import { ICycle, IIssue, IIssueLink, IModule, TIssuePriorities, linkDetails } from "types";
|
||||||
// contexts
|
|
||||||
import { useProjectMyMembership } from "contexts/project-member.context";
|
|
||||||
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import { IssueService } from "services/issue";
|
import { IssueService } from "services/issue";
|
||||||
|
|
||||||
@ -38,18 +37,19 @@ interface IPeekOverviewProperties {
|
|||||||
|
|
||||||
const issueService = new IssueService();
|
const issueService = new IssueService();
|
||||||
|
|
||||||
export const PeekOverviewProperties: FC<IPeekOverviewProperties> = (props) => {
|
export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((props) => {
|
||||||
const { issue, issueUpdate, user } = props;
|
const { issue, issueUpdate, user } = props;
|
||||||
const [linkModal, setLinkModal] = useState(false);
|
const [linkModal, setLinkModal] = useState(false);
|
||||||
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<linkDetails | null>(null);
|
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<linkDetails | null>(null);
|
||||||
|
|
||||||
|
const { user: userStore } = useMobxStore();
|
||||||
|
const userRole = userStore.currentProjectRole;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { memberRole } = useProjectMyMembership();
|
|
||||||
|
|
||||||
const handleState = (_state: string) => {
|
const handleState = (_state: string) => {
|
||||||
issueUpdate({ ...issue, state: _state });
|
issueUpdate({ ...issue, state: _state });
|
||||||
};
|
};
|
||||||
@ -346,7 +346,12 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = (props) => {
|
|||||||
links={issue.issue_link}
|
links={issue.issue_link}
|
||||||
handleDeleteLink={handleDeleteLink}
|
handleDeleteLink={handleDeleteLink}
|
||||||
handleEditLink={handleEditLink}
|
handleEditLink={handleEditLink}
|
||||||
userAuth={memberRole}
|
userAuth={{
|
||||||
|
isGuest: userRole === 5,
|
||||||
|
isViewer: userRole === 10,
|
||||||
|
isMember: userRole === 15,
|
||||||
|
isOwner: userRole === 20,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
@ -355,4 +360,4 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// services
|
// services
|
||||||
import { IssueService, IssueCommentService } from "services/issue";
|
import { IssueService, IssueCommentService } from "services/issue";
|
||||||
// hooks
|
// hooks
|
||||||
import useUserAuth from "hooks/use-user-auth";
|
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useProjectDetails from "hooks/use-project-details";
|
|
||||||
// contexts
|
|
||||||
import { useProjectMyMembership } from "contexts/project-member.context";
|
|
||||||
// components
|
// components
|
||||||
import {
|
import {
|
||||||
AddComment,
|
AddComment,
|
||||||
@ -40,16 +37,18 @@ type Props = {
|
|||||||
const issueService = new IssueService();
|
const issueService = new IssueService();
|
||||||
const issueCommentService = new IssueCommentService();
|
const issueCommentService = new IssueCommentService();
|
||||||
|
|
||||||
export const IssueMainContent: React.FC<Props> = ({ issueDetails, submitChanges, uneditable = false }) => {
|
export const IssueMainContent: React.FC<Props> = observer((props) => {
|
||||||
|
const { issueDetails, submitChanges, uneditable = false } = props;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, issueId } = router.query;
|
const { workspaceSlug, projectId, issueId } = router.query;
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { user } = useUserAuth();
|
const { user: userStore, project: projectStore } = useMobxStore();
|
||||||
const { memberRole } = useProjectMyMembership();
|
const user = userStore.currentUser ?? undefined;
|
||||||
|
const userRole = userStore.currentProjectRole;
|
||||||
const { projectDetails } = useProjectDetails();
|
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : undefined;
|
||||||
|
|
||||||
const { data: siblingIssues } = useSWR(
|
const { data: siblingIssues } = useSWR(
|
||||||
workspaceSlug && projectId && issueDetails?.parent ? SUB_ISSUES(issueDetails.parent) : null,
|
workspaceSlug && projectId && issueDetails?.parent ? SUB_ISSUES(issueDetails.parent) : null,
|
||||||
@ -75,7 +74,7 @@ export const IssueMainContent: React.FC<Props> = ({ issueDetails, submitChanges,
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCommentDelete = async (commentId: string) => {
|
const handleCommentDelete = async (commentId: string) => {
|
||||||
if (!workspaceSlug || !projectId || !issueId) return;
|
if (!workspaceSlug || !projectId || !issueId || !user) return;
|
||||||
|
|
||||||
mutateIssueActivity((prevData: any) => prevData?.filter((p: any) => p.id !== commentId), false);
|
mutateIssueActivity((prevData: any) => prevData?.filter((p: any) => p.id !== commentId), false);
|
||||||
|
|
||||||
@ -85,7 +84,7 @@ export const IssueMainContent: React.FC<Props> = ({ issueDetails, submitChanges,
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAddComment = async (formData: IIssueComment) => {
|
const handleAddComment = async (formData: IIssueComment) => {
|
||||||
if (!workspaceSlug || !issueDetails) return;
|
if (!workspaceSlug || !issueDetails || !user) return;
|
||||||
|
|
||||||
await issueCommentService
|
await issueCommentService
|
||||||
.createIssueComment(workspaceSlug.toString(), issueDetails.project, issueDetails.id, formData, user)
|
.createIssueComment(workspaceSlug.toString(), issueDetails.project, issueDetails.id, formData, user)
|
||||||
@ -167,7 +166,7 @@ export const IssueMainContent: React.FC<Props> = ({ issueDetails, submitChanges,
|
|||||||
workspaceSlug={workspaceSlug as string}
|
workspaceSlug={workspaceSlug as string}
|
||||||
issue={issueDetails}
|
issue={issueDetails}
|
||||||
handleFormSubmit={submitChanges}
|
handleFormSubmit={submitChanges}
|
||||||
isAllowed={memberRole.isMember || memberRole.isOwner || !uneditable}
|
isAllowed={userRole === 20 || userRole === 15 || !uneditable}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IssueReaction workspaceSlug={workspaceSlug} issueId={issueId} projectId={projectId} />
|
<IssueReaction workspaceSlug={workspaceSlug} issueId={issueId} projectId={projectId} />
|
||||||
@ -199,4 +198,4 @@ export const IssueMainContent: React.FC<Props> = ({ issueDetails, submitChanges,
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import React, { useCallback, useState } from "react";
|
import React, { useCallback, useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
import { Controller, UseFormWatch } from "react-hook-form";
|
import { Controller, UseFormWatch } from "react-hook-form";
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useUserAuth from "hooks/use-user-auth";
|
|
||||||
import useUserIssueNotificationSubscription from "hooks/use-issue-notification-subscription";
|
import useUserIssueNotificationSubscription from "hooks/use-issue-notification-subscription";
|
||||||
import useEstimateOption from "hooks/use-estimate-option";
|
import useEstimateOption from "hooks/use-estimate-option";
|
||||||
// services
|
// services
|
||||||
import { IssueService } from "services/issue";
|
import { IssueService } from "services/issue";
|
||||||
import { ModuleService } from "services/module.service";
|
import { ModuleService } from "services/module.service";
|
||||||
// contexts
|
|
||||||
import { useProjectMyMembership } from "contexts/project-member.context";
|
|
||||||
// components
|
// components
|
||||||
import { LinkModal, LinksList } from "components/core";
|
import { LinkModal, LinksList } from "components/core";
|
||||||
import {
|
import {
|
||||||
@ -72,23 +72,20 @@ type Props = {
|
|||||||
const issueService = new IssueService();
|
const issueService = new IssueService();
|
||||||
const moduleService = new ModuleService();
|
const moduleService = new ModuleService();
|
||||||
|
|
||||||
export const IssueDetailsSidebar: React.FC<Props> = ({
|
export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||||
control,
|
const { control, submitChanges, issueDetail, watch: watchIssue, fieldsToShow = ["all"], uneditable = false } = props;
|
||||||
submitChanges,
|
|
||||||
issueDetail,
|
|
||||||
watch: watchIssue,
|
|
||||||
fieldsToShow = ["all"],
|
|
||||||
uneditable = false,
|
|
||||||
}) => {
|
|
||||||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||||
const [linkModal, setLinkModal] = useState(false);
|
const [linkModal, setLinkModal] = useState(false);
|
||||||
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<linkDetails | null>(null);
|
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<linkDetails | null>(null);
|
||||||
|
|
||||||
|
const { user: userStore } = useMobxStore();
|
||||||
|
const user = userStore.currentUser;
|
||||||
|
const userRole = userStore.currentProjectRole;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, issueId } = router.query;
|
const { workspaceSlug, projectId, issueId } = router.query;
|
||||||
|
|
||||||
const { user } = useUserAuth();
|
|
||||||
|
|
||||||
const { isEstimateActive } = useEstimateOption();
|
const { isEstimateActive } = useEstimateOption();
|
||||||
|
|
||||||
const { loading, handleSubscribe, handleUnsubscribe, subscribed } = useUserIssueNotificationSubscription(
|
const { loading, handleSubscribe, handleUnsubscribe, subscribed } = useUserIssueNotificationSubscription(
|
||||||
@ -97,13 +94,11 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
issueId
|
issueId
|
||||||
);
|
);
|
||||||
|
|
||||||
const { memberRole } = useProjectMyMembership();
|
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const handleCycleChange = useCallback(
|
const handleCycleChange = useCallback(
|
||||||
(cycleDetails: ICycle) => {
|
(cycleDetails: ICycle) => {
|
||||||
if (!workspaceSlug || !projectId || !issueDetail) return;
|
if (!workspaceSlug || !projectId || !issueDetail || !user) return;
|
||||||
|
|
||||||
issueService
|
issueService
|
||||||
.addIssueToCycle(
|
.addIssueToCycle(
|
||||||
@ -124,7 +119,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
|
|
||||||
const handleModuleChange = useCallback(
|
const handleModuleChange = useCallback(
|
||||||
(moduleDetail: IModule) => {
|
(moduleDetail: IModule) => {
|
||||||
if (!workspaceSlug || !projectId || !issueDetail) return;
|
if (!workspaceSlug || !projectId || !issueDetail || !user) return;
|
||||||
|
|
||||||
moduleService
|
moduleService
|
||||||
.addIssuesToModule(
|
.addIssuesToModule(
|
||||||
@ -262,7 +257,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
setLinkModal(true);
|
setLinkModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isNotAllowed = memberRole.isGuest || memberRole.isViewer;
|
const isNotAllowed = userRole === 5 || userRole === 10;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -341,7 +336,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
<SidebarStateSelect
|
<SidebarStateSelect
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(val: string) => submitChanges({ state: val })}
|
onChange={(val: string) => submitChanges({ state: val })}
|
||||||
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
disabled={isNotAllowed || uneditable}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -362,7 +357,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
<SidebarAssigneeSelect
|
<SidebarAssigneeSelect
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(val: string[]) => submitChanges({ assignees: val })}
|
onChange={(val: string[]) => submitChanges({ assignees: val })}
|
||||||
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
disabled={isNotAllowed || uneditable}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -383,7 +378,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
<SidebarPrioritySelect
|
<SidebarPrioritySelect
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(val) => submitChanges({ priority: val })}
|
onChange={(val) => submitChanges({ priority: val })}
|
||||||
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
disabled={isNotAllowed || uneditable}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -404,7 +399,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
<SidebarEstimateSelect
|
<SidebarEstimateSelect
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(val: number | null) => submitChanges({ estimate_point: val })}
|
onChange={(val: number | null) => submitChanges({ estimate_point: val })}
|
||||||
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
disabled={isNotAllowed || uneditable}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -432,7 +427,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
onChange(val);
|
onChange(val);
|
||||||
}}
|
}}
|
||||||
issueDetails={issueDetail}
|
issueDetails={issueDetail}
|
||||||
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
disabled={isNotAllowed || uneditable}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -457,7 +452,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
|
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
|
||||||
}}
|
}}
|
||||||
watch={watchIssue}
|
watch={watchIssue}
|
||||||
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
disabled={isNotAllowed || uneditable}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("blocked")) && (
|
{(fieldsToShow.includes("all") || fieldsToShow.includes("blocked")) && (
|
||||||
@ -478,7 +473,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
|
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
|
||||||
}}
|
}}
|
||||||
watch={watchIssue}
|
watch={watchIssue}
|
||||||
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
disabled={isNotAllowed || uneditable}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("duplicate")) && (
|
{(fieldsToShow.includes("all") || fieldsToShow.includes("duplicate")) && (
|
||||||
@ -496,7 +491,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
|
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
|
||||||
}}
|
}}
|
||||||
watch={watchIssue}
|
watch={watchIssue}
|
||||||
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
disabled={isNotAllowed || uneditable}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("relates_to")) && (
|
{(fieldsToShow.includes("all") || fieldsToShow.includes("relates_to")) && (
|
||||||
@ -514,7 +509,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
|
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
|
||||||
}}
|
}}
|
||||||
watch={watchIssue}
|
watch={watchIssue}
|
||||||
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
disabled={isNotAllowed || uneditable}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("startDate")) && (
|
{(fieldsToShow.includes("all") || fieldsToShow.includes("startDate")) && (
|
||||||
@ -587,7 +582,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
<SidebarCycleSelect
|
<SidebarCycleSelect
|
||||||
issueDetail={issueDetail}
|
issueDetail={issueDetail}
|
||||||
handleCycleChange={handleCycleChange}
|
handleCycleChange={handleCycleChange}
|
||||||
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
disabled={isNotAllowed || uneditable}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -602,7 +597,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
<SidebarModuleSelect
|
<SidebarModuleSelect
|
||||||
issueDetail={issueDetail}
|
issueDetail={issueDetail}
|
||||||
handleModuleChange={handleModuleChange}
|
handleModuleChange={handleModuleChange}
|
||||||
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
disabled={isNotAllowed || uneditable}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -650,7 +645,12 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
links={issueDetail.issue_link}
|
links={issueDetail.issue_link}
|
||||||
handleDeleteLink={handleDeleteLink}
|
handleDeleteLink={handleDeleteLink}
|
||||||
handleEditLink={handleEditLink}
|
handleEditLink={handleEditLink}
|
||||||
userAuth={memberRole}
|
userAuth={{
|
||||||
|
isGuest: userRole === 5,
|
||||||
|
isViewer: userRole === 10,
|
||||||
|
isMember: userRole === 15,
|
||||||
|
isOwner: userRole === 20,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
@ -660,4 +660,4 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
// next imports
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// swr
|
import { observer } from "mobx-react-lite";
|
||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
// lucide icons
|
|
||||||
import { Plus, ChevronRight, ChevronDown } from "lucide-react";
|
import { Plus, ChevronRight, ChevronDown } from "lucide-react";
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// components
|
// components
|
||||||
import { ExistingIssuesListModal } from "components/core";
|
import { ExistingIssuesListModal } from "components/core";
|
||||||
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
|
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
|
||||||
@ -13,8 +13,6 @@ import { ProgressBar } from "./progressbar";
|
|||||||
// ui
|
// ui
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { CustomMenu } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
|
||||||
import { useProjectMyMembership } from "contexts/project-member.context";
|
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// helpers
|
// helpers
|
||||||
import { copyTextToClipboard } from "helpers/string.helper";
|
import { copyTextToClipboard } from "helpers/string.helper";
|
||||||
@ -42,23 +40,21 @@ export interface ISubIssuesRootLoadersHandler {
|
|||||||
|
|
||||||
const issueService = new IssueService();
|
const issueService = new IssueService();
|
||||||
|
|
||||||
export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) => {
|
export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => {
|
||||||
|
const { parentIssue, user } = props;
|
||||||
|
|
||||||
|
const { user: userStore, issue: issueStore, issueDetail: issueDetailStore } = useMobxStore();
|
||||||
|
const userRole = userStore.currentProjectRole;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query as {
|
const { workspaceSlug, projectId } = router.query;
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
peekIssue: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const { issue: issueStore, issueDetail: issueDetailStore } = useMobxStore();
|
|
||||||
|
|
||||||
const { memberRole } = useProjectMyMembership();
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { data: issues, isLoading } = useSWR(
|
const { data: issues, isLoading } = useSWR(
|
||||||
workspaceSlug && projectId && parentIssue && parentIssue?.id ? SUB_ISSUES(parentIssue?.id) : null,
|
workspaceSlug && projectId && parentIssue && parentIssue?.id ? SUB_ISSUES(parentIssue?.id) : null,
|
||||||
workspaceSlug && projectId && parentIssue && parentIssue?.id
|
workspaceSlug && projectId && parentIssue && parentIssue?.id
|
||||||
? () => issueService.subIssues(workspaceSlug, projectId, parentIssue.id)
|
? () => issueService.subIssues(workspaceSlug.toString(), projectId.toString(), parentIssue.id)
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -117,20 +113,20 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) =
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addAsSubIssueFromExistingIssues = async (data: ISearchIssueResponse[]) => {
|
const addAsSubIssueFromExistingIssues = async (data: ISearchIssueResponse[]) => {
|
||||||
if (!workspaceSlug || !parentIssue || issueCrudOperation?.existing?.issueId === null) return;
|
if (!workspaceSlug || !projectId || !parentIssue || issueCrudOperation?.existing?.issueId === null) return;
|
||||||
const issueId = issueCrudOperation?.existing?.issueId;
|
const issueId = issueCrudOperation?.existing?.issueId;
|
||||||
const payload = {
|
const payload = {
|
||||||
sub_issue_ids: data.map((i) => i.id),
|
sub_issue_ids: data.map((i) => i.id),
|
||||||
};
|
};
|
||||||
await issueService.addSubIssues(workspaceSlug, projectId, issueId, payload).finally(() => {
|
await issueService.addSubIssues(workspaceSlug.toString(), projectId.toString(), issueId, payload).finally(() => {
|
||||||
if (issueId) mutate(SUB_ISSUES(issueId));
|
if (issueId) mutate(SUB_ISSUES(issueId));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeIssueFromSubIssues = async (parentIssueId: string, issue: IIssue) => {
|
const removeIssueFromSubIssues = async (parentIssueId: string, issue: IIssue) => {
|
||||||
if (!workspaceSlug || !parentIssue || !issue?.id) return;
|
if (!workspaceSlug || !projectId || !parentIssue || !issue?.id) return;
|
||||||
issueService
|
issueService
|
||||||
.patchIssue(workspaceSlug, projectId, issue.id, { parent: null }, user)
|
.patchIssue(workspaceSlug.toString(), projectId.toString(), issue.id, { parent: null }, user)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
if (parentIssueId) await mutate(SUB_ISSUES(parentIssueId));
|
if (parentIssueId) await mutate(SUB_ISSUES(parentIssueId));
|
||||||
handleIssuesLoader({ key: "delete", issueId: issue?.id });
|
handleIssuesLoader({ key: "delete", issueId: issue?.id });
|
||||||
@ -176,7 +172,7 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) =
|
|||||||
[issueStore, issueDetailStore, projectId, user, workspaceSlug]
|
[issueStore, issueDetailStore, projectId, user, workspaceSlug]
|
||||||
);
|
);
|
||||||
|
|
||||||
const isEditable = memberRole?.isGuest || memberRole?.isViewer ? false : true;
|
const isEditable = userRole === 5 || userRole === 10 ? false : true;
|
||||||
|
|
||||||
const mutateSubIssues = (parentIssueId: string | null) => {
|
const mutateSubIssues = (parentIssueId: string | null) => {
|
||||||
if (parentIssueId) mutate(SUB_ISSUES(parentIssueId));
|
if (parentIssueId) mutate(SUB_ISSUES(parentIssueId));
|
||||||
@ -233,11 +229,11 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) =
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* issues */}
|
{/* issues */}
|
||||||
{issuesLoader.visibility.includes(parentIssue?.id) && (
|
{issuesLoader.visibility.includes(parentIssue?.id) && workspaceSlug && projectId && (
|
||||||
<div className="border border-b-0 border-custom-border-100">
|
<div className="border border-b-0 border-custom-border-100">
|
||||||
<SubIssuesRootList
|
<SubIssuesRootList
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
projectId={projectId}
|
projectId={projectId.toString()}
|
||||||
parentIssue={parentIssue}
|
parentIssue={parentIssue}
|
||||||
user={undefined}
|
user={undefined}
|
||||||
editable={isEditable}
|
editable={isEditable}
|
||||||
@ -371,4 +367,4 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user }) =
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -8,8 +8,6 @@ import { Disclosure, Transition } from "@headlessui/react";
|
|||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// services
|
// services
|
||||||
import { ModuleService } from "services/module.service";
|
import { ModuleService } from "services/module.service";
|
||||||
// contexts
|
|
||||||
import { useProjectMyMembership } from "contexts/project-member.context";
|
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
// components
|
||||||
@ -60,10 +58,9 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
const { module: moduleStore, user: userStore } = useMobxStore();
|
const { module: moduleStore, user: userStore } = useMobxStore();
|
||||||
|
|
||||||
const user = userStore.currentUser ?? undefined;
|
const user = userStore.currentUser ?? undefined;
|
||||||
|
const userRole = userStore.currentProjectRole;
|
||||||
const moduleDetails = moduleStore.moduleDetails[moduleId] ?? undefined;
|
const moduleDetails = moduleStore.moduleDetails[moduleId] ?? undefined;
|
||||||
|
|
||||||
const { memberRole } = useProjectMyMembership();
|
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { reset, control } = useForm({
|
const { reset, control } = useForm({
|
||||||
@ -429,7 +426,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
<Transition show={open}>
|
<Transition show={open}>
|
||||||
<Disclosure.Panel>
|
<Disclosure.Panel>
|
||||||
<div className="flex flex-col w-full mt-2 space-y-3 h-72 overflow-y-auto">
|
<div className="flex flex-col w-full mt-2 space-y-3 h-72 overflow-y-auto">
|
||||||
{memberRole && moduleDetails.link_module && moduleDetails.link_module.length > 0 ? (
|
{userRole && moduleDetails.link_module && moduleDetails.link_module.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-end w-full">
|
<div className="flex items-center justify-end w-full">
|
||||||
<button
|
<button
|
||||||
@ -445,7 +442,12 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
links={moduleDetails.link_module}
|
links={moduleDetails.link_module}
|
||||||
handleEditLink={handleEditLink}
|
handleEditLink={handleEditLink}
|
||||||
handleDeleteLink={handleDeleteLink}
|
handleDeleteLink={handleDeleteLink}
|
||||||
userAuth={memberRole}
|
userAuth={{
|
||||||
|
isGuest: userRole === 5,
|
||||||
|
isViewer: userRole === 10,
|
||||||
|
isMember: userRole === 15,
|
||||||
|
isOwner: userRole === 20,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
@ -15,7 +15,7 @@ import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
|
|||||||
// icons
|
// icons
|
||||||
import { Check, ChevronDown, Plus, X } from "lucide-react";
|
import { Check, ChevronDown, Plus, X } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import { IUser, IWorkspace, TOnboardingSteps } from "types";
|
import { IUser, IWorkspace, TOnboardingSteps, TUserWorkspaceRole } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { ROLE } from "constants/workspace";
|
import { ROLE } from "constants/workspace";
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ type Props = {
|
|||||||
|
|
||||||
type EmailRole = {
|
type EmailRole = {
|
||||||
email: string;
|
email: string;
|
||||||
role: 5 | 10 | 15 | 20;
|
role: TUserWorkspaceRole;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FormValues = {
|
type FormValues = {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
export * from "./overview";
|
export * from "./overview";
|
||||||
export * from "./navbar";
|
export * from "./navbar";
|
||||||
export * from "./profile-issues-view";
|
|
||||||
export * from "./sidebar";
|
export * from "./sidebar";
|
||||||
|
|
||||||
export * from "./profile-issues-filter";
|
export * from "./profile-issues-filter";
|
||||||
|
@ -1,297 +0,0 @@
|
|||||||
import { useCallback, useState } from "react";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import useSWR from "swr";
|
|
||||||
import { DropResult } from "react-beautiful-dnd";
|
|
||||||
// services
|
|
||||||
import { IssueService, IssueLabelService } from "services/issue";
|
|
||||||
import { UserService } from "services/user.service";
|
|
||||||
// hooks
|
|
||||||
import useProfileIssues from "hooks/use-profile-issues";
|
|
||||||
import useUser from "hooks/use-user";
|
|
||||||
// components
|
|
||||||
import { FiltersList } from "components/core";
|
|
||||||
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
|
|
||||||
// helpers
|
|
||||||
import { orderArrayBy } from "helpers/array.helper";
|
|
||||||
// types
|
|
||||||
import { IIssue, IIssueFilterOptions, TIssuePriorities } from "types";
|
|
||||||
// fetch-keys
|
|
||||||
import { USER_PROFILE_PROJECT_SEGREGATION, WORKSPACE_LABELS } from "constants/fetch-keys";
|
|
||||||
|
|
||||||
// services
|
|
||||||
const issueService = new IssueService();
|
|
||||||
const issueLabelService = new IssueLabelService();
|
|
||||||
const userService = new UserService();
|
|
||||||
|
|
||||||
export const ProfileIssuesView = () => {
|
|
||||||
// create issue modal
|
|
||||||
const [createIssueModal, setCreateIssueModal] = useState(false);
|
|
||||||
const [preloadedData, setPreloadedData] = useState<
|
|
||||||
(Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | undefined
|
|
||||||
>(undefined);
|
|
||||||
|
|
||||||
// update issue modal
|
|
||||||
const [editIssueModal, setEditIssueModal] = useState(false);
|
|
||||||
const [issueToEdit, setIssueToEdit] = useState<(IIssue & { actionType: "edit" | "delete" }) | undefined>(undefined);
|
|
||||||
|
|
||||||
// delete issue modal
|
|
||||||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
|
||||||
const [issueToDelete, setIssueToDelete] = useState<IIssue | null>(null);
|
|
||||||
|
|
||||||
// trash box
|
|
||||||
const [trashBox, setTrashBox] = useState(false);
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const { workspaceSlug, userId } = router.query;
|
|
||||||
|
|
||||||
const { user } = useUser();
|
|
||||||
|
|
||||||
const {
|
|
||||||
groupedIssues,
|
|
||||||
mutateProfileIssues,
|
|
||||||
displayFilters,
|
|
||||||
isEmpty,
|
|
||||||
filters,
|
|
||||||
setFilters,
|
|
||||||
displayProperties,
|
|
||||||
params,
|
|
||||||
} = useProfileIssues(workspaceSlug?.toString(), userId?.toString());
|
|
||||||
|
|
||||||
const { data: profileData } = useSWR(
|
|
||||||
workspaceSlug && userId ? USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString()) : null,
|
|
||||||
workspaceSlug && userId
|
|
||||||
? () => userService.getUserProfileProjectsSegregation(workspaceSlug.toString(), userId.toString())
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: labels } = useSWR(
|
|
||||||
workspaceSlug && (filters?.labels ?? []).length > 0 ? WORKSPACE_LABELS(workspaceSlug.toString()) : null,
|
|
||||||
workspaceSlug && (filters?.labels ?? []).length > 0
|
|
||||||
? () => issueLabelService.getWorkspaceIssueLabels(workspaceSlug.toString())
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleDeleteIssue = useCallback(
|
|
||||||
(issue: IIssue) => {
|
|
||||||
setDeleteIssueModal(true);
|
|
||||||
setIssueToDelete(issue);
|
|
||||||
},
|
|
||||||
[setDeleteIssueModal, setIssueToDelete]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleOnDragEnd = useCallback(
|
|
||||||
async (result: DropResult) => {
|
|
||||||
setTrashBox(false);
|
|
||||||
|
|
||||||
if (!result.destination || !workspaceSlug || !groupedIssues || displayFilters?.group_by !== "priority") return;
|
|
||||||
|
|
||||||
const { source, destination } = result;
|
|
||||||
|
|
||||||
if (source.droppableId === destination.droppableId) return;
|
|
||||||
|
|
||||||
const draggedItem = groupedIssues[source.droppableId][source.index];
|
|
||||||
|
|
||||||
if (!draggedItem) return;
|
|
||||||
|
|
||||||
if (destination.droppableId === "trashBox") handleDeleteIssue(draggedItem);
|
|
||||||
else {
|
|
||||||
const sourceGroup = source.droppableId;
|
|
||||||
const destinationGroup = destination.droppableId;
|
|
||||||
|
|
||||||
draggedItem[displayFilters.group_by] = destinationGroup as TIssuePriorities;
|
|
||||||
|
|
||||||
mutateProfileIssues((prevData: any) => {
|
|
||||||
if (!prevData) return prevData;
|
|
||||||
|
|
||||||
const sourceGroupArray = [...groupedIssues[sourceGroup]];
|
|
||||||
const destinationGroupArray = [...groupedIssues[destinationGroup]];
|
|
||||||
|
|
||||||
sourceGroupArray.splice(source.index, 1);
|
|
||||||
destinationGroupArray.splice(destination.index, 0, draggedItem);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...prevData,
|
|
||||||
[sourceGroup]: orderArrayBy(sourceGroupArray, displayFilters.order_by ?? "-created_at"),
|
|
||||||
[destinationGroup]: orderArrayBy(destinationGroupArray, displayFilters.order_by ?? "-created_at"),
|
|
||||||
};
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
// patch request
|
|
||||||
issueService
|
|
||||||
.patchIssue(
|
|
||||||
workspaceSlug as string,
|
|
||||||
draggedItem.project,
|
|
||||||
draggedItem.id,
|
|
||||||
{
|
|
||||||
priority: draggedItem.priority,
|
|
||||||
},
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.catch(() => mutateProfileIssues());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[displayFilters, groupedIssues, handleDeleteIssue, mutateProfileIssues, user, workspaceSlug]
|
|
||||||
);
|
|
||||||
|
|
||||||
const addIssueToGroup = useCallback(
|
|
||||||
(groupTitle: string) => {
|
|
||||||
setCreateIssueModal(true);
|
|
||||||
|
|
||||||
let preloadedValue: string | string[] = groupTitle;
|
|
||||||
|
|
||||||
if (displayFilters?.group_by === "labels") {
|
|
||||||
if (groupTitle === "None") preloadedValue = [];
|
|
||||||
else preloadedValue = [groupTitle];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (displayFilters?.group_by)
|
|
||||||
setPreloadedData({
|
|
||||||
[displayFilters?.group_by]: preloadedValue,
|
|
||||||
actionType: "createIssue",
|
|
||||||
});
|
|
||||||
else setPreloadedData({ actionType: "createIssue" });
|
|
||||||
},
|
|
||||||
[setCreateIssueModal, setPreloadedData, displayFilters?.group_by]
|
|
||||||
);
|
|
||||||
|
|
||||||
const addIssueToDate = useCallback(
|
|
||||||
(date: string) => {
|
|
||||||
setCreateIssueModal(true);
|
|
||||||
setPreloadedData({
|
|
||||||
target_date: date,
|
|
||||||
actionType: "createIssue",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[setCreateIssueModal, setPreloadedData]
|
|
||||||
);
|
|
||||||
|
|
||||||
const makeIssueCopy = useCallback(
|
|
||||||
(issue: IIssue) => {
|
|
||||||
setCreateIssueModal(true);
|
|
||||||
|
|
||||||
setPreloadedData({ ...issue, name: `${issue.name} (Copy)`, actionType: "createIssue" });
|
|
||||||
},
|
|
||||||
[setCreateIssueModal, setPreloadedData]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleEditIssue = useCallback(
|
|
||||||
(issue: IIssue) => {
|
|
||||||
setEditIssueModal(true);
|
|
||||||
setIssueToEdit({
|
|
||||||
...issue,
|
|
||||||
actionType: "edit",
|
|
||||||
cycle: issue.issue_cycle ? issue.issue_cycle.cycle : null,
|
|
||||||
module: issue.issue_module ? issue.issue_module.module : null,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[setEditIssueModal, setIssueToEdit]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleIssueAction = useCallback(
|
|
||||||
(issue: IIssue, action: "copy" | "edit" | "delete" | "updateDraft") => {
|
|
||||||
if (action === "copy") makeIssueCopy(issue);
|
|
||||||
else if (action === "edit") handleEditIssue(issue);
|
|
||||||
else if (action === "delete") handleDeleteIssue(issue);
|
|
||||||
},
|
|
||||||
[makeIssueCopy, handleEditIssue, handleDeleteIssue]
|
|
||||||
);
|
|
||||||
|
|
||||||
const filtersToDisplay = { ...filters, assignees: null, created_by: null, subscriber: null };
|
|
||||||
|
|
||||||
const nullFilters = Object.keys(filtersToDisplay).filter(
|
|
||||||
(key) => filtersToDisplay[key as keyof IIssueFilterOptions] === null
|
|
||||||
);
|
|
||||||
const areFiltersApplied =
|
|
||||||
Object.keys(filtersToDisplay).length > 0 && nullFilters.length !== Object.keys(filtersToDisplay).length;
|
|
||||||
|
|
||||||
const isSubscribedIssuesRoute = router.pathname.includes("subscribed");
|
|
||||||
const isMySubscribedIssues =
|
|
||||||
(filters.subscriber && filters.subscriber.length > 0 && router.pathname.includes("my-issues")) ?? false;
|
|
||||||
|
|
||||||
const disableAddIssueOption = isSubscribedIssuesRoute || isMySubscribedIssues || user?.id !== userId;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<CreateUpdateIssueModal
|
|
||||||
isOpen={createIssueModal && preloadedData?.actionType === "createIssue"}
|
|
||||||
handleClose={() => setCreateIssueModal(false)}
|
|
||||||
prePopulateData={{
|
|
||||||
...preloadedData,
|
|
||||||
}}
|
|
||||||
onSubmit={async () => {
|
|
||||||
mutateProfileIssues();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<CreateUpdateIssueModal
|
|
||||||
isOpen={editIssueModal && issueToEdit?.actionType !== "delete"}
|
|
||||||
handleClose={() => setEditIssueModal(false)}
|
|
||||||
data={issueToEdit}
|
|
||||||
onSubmit={async () => {
|
|
||||||
mutateProfileIssues();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{issueToDelete && (
|
|
||||||
<DeleteIssueModal
|
|
||||||
handleClose={() => setDeleteIssueModal(false)}
|
|
||||||
isOpen={deleteIssueModal}
|
|
||||||
data={issueToDelete}
|
|
||||||
onSubmit={async () => {
|
|
||||||
mutateProfileIssues();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{areFiltersApplied && (
|
|
||||||
<>
|
|
||||||
<div className="flex items-center justify-between gap-2 px-5 pt-3 pb-0">
|
|
||||||
<FiltersList
|
|
||||||
filters={filtersToDisplay}
|
|
||||||
setFilters={setFilters}
|
|
||||||
labels={labels}
|
|
||||||
members={undefined}
|
|
||||||
states={undefined}
|
|
||||||
clearAllFilters={() =>
|
|
||||||
setFilters({
|
|
||||||
mentions: null,
|
|
||||||
labels: null,
|
|
||||||
priority: null,
|
|
||||||
state_group: null,
|
|
||||||
start_date: null,
|
|
||||||
target_date: null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{<div className="mt-3 border-t border-custom-border-200" />}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{/* <AllViews
|
|
||||||
addIssueToDate={addIssueToDate}
|
|
||||||
addIssueToGroup={addIssueToGroup}
|
|
||||||
disableUserActions={false}
|
|
||||||
dragDisabled={displayFilters?.group_by !== "priority"}
|
|
||||||
emptyState={{
|
|
||||||
title: router.pathname.includes("assigned")
|
|
||||||
? `Issues assigned to ${profileData?.user_data.display_name} will appear here`
|
|
||||||
: router.pathname.includes("created")
|
|
||||||
? `Issues created by ${profileData?.user_data.display_name} will appear here`
|
|
||||||
: `Issues subscribed by ${profileData?.user_data.display_name} will appear here`,
|
|
||||||
}}
|
|
||||||
handleOnDragEnd={handleOnDragEnd}
|
|
||||||
handleIssueAction={handleIssueAction}
|
|
||||||
openIssuesListModal={null}
|
|
||||||
removeIssue={null}
|
|
||||||
disableAddIssueOption={disableAddIssueOption}
|
|
||||||
trashBox={trashBox}
|
|
||||||
setTrashBox={setTrashBox}
|
|
||||||
viewProps={{
|
|
||||||
groupedIssues,
|
|
||||||
displayFilters,
|
|
||||||
isEmpty,
|
|
||||||
mutateIssues: mutateProfileIssues,
|
|
||||||
params,
|
|
||||||
properties: displayProperties,
|
|
||||||
}}
|
|
||||||
/> */}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -16,6 +16,7 @@ import { CustomMenu, CustomSelect } from "@plane/ui";
|
|||||||
import { ChevronDown, X } from "lucide-react";
|
import { ChevronDown, X } from "lucide-react";
|
||||||
// constants
|
// constants
|
||||||
import { ROLE } from "constants/workspace";
|
import { ROLE } from "constants/workspace";
|
||||||
|
import { TUserProjectRole } from "types";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
const projectInvitationService = new ProjectInvitationService();
|
const projectInvitationService = new ProjectInvitationService();
|
||||||
@ -144,7 +145,7 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
value={member.role}
|
value={member.role}
|
||||||
onChange={(value: 5 | 10 | 15 | 20 | undefined) => {
|
onChange={(value: TUserProjectRole | undefined) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
projectStore
|
projectStore
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { useForm, Controller, useFieldArray } from "react-hook-form";
|
import { useForm, Controller, useFieldArray } from "react-hook-form";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { ChevronDown, Plus, X } from "lucide-react";
|
import { ChevronDown, Plus, X } from "lucide-react";
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// ui
|
// ui
|
||||||
import { Avatar, Button, CustomSelect, CustomSearchSelect } from "@plane/ui";
|
import { Avatar, Button, CustomSelect, CustomSearchSelect } from "@plane/ui";
|
||||||
// services
|
// services
|
||||||
import { ProjectService } from "services/project";
|
import { ProjectService } from "services/project";
|
||||||
import { WorkspaceService } from "services/workspace.service";
|
import { WorkspaceService } from "services/workspace.service";
|
||||||
// contexts
|
|
||||||
import { useProjectMyMembership } from "contexts/project-member.context";
|
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// types
|
// types
|
||||||
import { IUser } from "types";
|
import { IUser, TUserProjectRole } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
import { WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
||||||
// constants
|
// constants
|
||||||
@ -29,7 +30,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type member = {
|
type member = {
|
||||||
role: 5 | 10 | 15 | 20;
|
role: TUserProjectRole;
|
||||||
member_id: string;
|
member_id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -50,14 +51,16 @@ const defaultValues: FormValues = {
|
|||||||
const projectService = new ProjectService();
|
const projectService = new ProjectService();
|
||||||
const workspaceService = new WorkspaceService();
|
const workspaceService = new WorkspaceService();
|
||||||
|
|
||||||
export const SendProjectInvitationModal: React.FC<Props> = (props) => {
|
export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
|
||||||
const { isOpen, setIsOpen, members, user, onSuccess } = props;
|
const { isOpen, setIsOpen, members, user, onSuccess } = props;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
const { memberDetails } = useProjectMyMembership();
|
|
||||||
|
const { user: userStore } = useMobxStore();
|
||||||
|
const userRole = userStore.currentProjectRole;
|
||||||
|
|
||||||
const { data: people } = useSWR(
|
const { data: people } = useSWR(
|
||||||
workspaceSlug ? WORKSPACE_MEMBERS(workspaceSlug as string) : null,
|
workspaceSlug ? WORKSPACE_MEMBERS(workspaceSlug as string) : null,
|
||||||
@ -243,7 +246,7 @@ export const SendProjectInvitationModal: React.FC<Props> = (props) => {
|
|||||||
width="w-full"
|
width="w-full"
|
||||||
>
|
>
|
||||||
{Object.entries(ROLE).map(([key, label]) => {
|
{Object.entries(ROLE).map(([key, label]) => {
|
||||||
if (parseInt(key) > (memberDetails?.role ?? 5)) return null;
|
if (parseInt(key) > (userRole ?? 5)) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomSelect.Option key={key} value={key}>
|
<CustomSelect.Option key={key} value={key}>
|
||||||
@ -305,4 +308,4 @@ export const SendProjectInvitationModal: React.FC<Props> = (props) => {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</Transition.Root>
|
</Transition.Root>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -1,182 +0,0 @@
|
|||||||
import useSWR from "swr";
|
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
import Image from "next/image";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
// hooks
|
|
||||||
import useToast from "hooks/use-toast";
|
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
|
||||||
// services
|
|
||||||
import { AuthService } from "services/auth.service";
|
|
||||||
import { AppConfigService } from "services/app_config.service";
|
|
||||||
// components
|
|
||||||
import {
|
|
||||||
GoogleLoginButton,
|
|
||||||
GithubLoginButton,
|
|
||||||
EmailCodeForm,
|
|
||||||
EmailPasswordForm,
|
|
||||||
EmailPasswordFormValues,
|
|
||||||
} from "components/account";
|
|
||||||
// ui
|
|
||||||
import { Spinner } from "@plane/ui";
|
|
||||||
// images
|
|
||||||
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
|
|
||||||
import { IUserSettings } from "types";
|
|
||||||
|
|
||||||
const appConfigService = new AppConfigService();
|
|
||||||
const authService = new AuthService();
|
|
||||||
|
|
||||||
export const SignInView = observer(() => {
|
|
||||||
const { user: userStore } = useMobxStore();
|
|
||||||
// router
|
|
||||||
const router = useRouter();
|
|
||||||
// toast
|
|
||||||
const { setToastAlert } = useToast();
|
|
||||||
// fetch app config
|
|
||||||
const { data } = useSWR("APP_CONFIG", () => appConfigService.envConfig());
|
|
||||||
// fetch user info
|
|
||||||
const { data: user, error } = useSWR("USER_INFO", () => userStore.fetchCurrentUser());
|
|
||||||
// computed
|
|
||||||
const enableEmailPassword =
|
|
||||||
data?.email_password_login || !(data?.email_password_login || data?.magic_login || data?.google || data?.github);
|
|
||||||
|
|
||||||
const handleLoginRedirection = () =>
|
|
||||||
userStore.fetchCurrentUserSettings().then((userSettings: IUserSettings) => {
|
|
||||||
const workspaceSlug =
|
|
||||||
userSettings?.workspace?.last_workspace_slug || userSettings?.workspace?.fallback_workspace_slug;
|
|
||||||
router.push(`/${workspaceSlug}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleGoogleSignIn = async ({ clientId, credential }: any) => {
|
|
||||||
try {
|
|
||||||
if (clientId && credential) {
|
|
||||||
const socialAuthPayload = {
|
|
||||||
medium: "google",
|
|
||||||
credential,
|
|
||||||
clientId,
|
|
||||||
};
|
|
||||||
const response = await authService.socialAuth(socialAuthPayload);
|
|
||||||
if (response) {
|
|
||||||
handleLoginRedirection();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw Error("Cant find credentials");
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
setToastAlert({
|
|
||||||
title: "Error signing in!",
|
|
||||||
type: "error",
|
|
||||||
message: err?.error || "Something went wrong. Please try again later or contact the support team.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleGitHubSignIn = async (credential: string) => {
|
|
||||||
try {
|
|
||||||
if (data && data.github && credential) {
|
|
||||||
const socialAuthPayload = {
|
|
||||||
medium: "github",
|
|
||||||
credential,
|
|
||||||
clientId: data.github,
|
|
||||||
};
|
|
||||||
const response = await authService.socialAuth(socialAuthPayload);
|
|
||||||
if (response) {
|
|
||||||
handleLoginRedirection();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw Error("Cant find credentials");
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
setToastAlert({
|
|
||||||
title: "Error signing in!",
|
|
||||||
type: "error",
|
|
||||||
message: err?.error || "Something went wrong. Please try again later or contact the support team.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePasswordSignIn = async (formData: EmailPasswordFormValues) => {
|
|
||||||
await authService
|
|
||||||
.emailLogin(formData)
|
|
||||||
.then((response) => {
|
|
||||||
if (response) {
|
|
||||||
handleLoginRedirection();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) =>
|
|
||||||
setToastAlert({
|
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message: err?.error || "Something went wrong. Please try again later or contact the support team.",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEmailCodeSignIn = async (response: any) => {
|
|
||||||
try {
|
|
||||||
if (response) {
|
|
||||||
handleLoginRedirection();
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
setToastAlert({
|
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message: err?.error || "Something went wrong. Please try again later or contact the support team.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!user && !error ? (
|
|
||||||
<div className="grid place-items-center h-screen">
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<>
|
|
||||||
<div className="hidden sm:block sm:fixed border-r-[0.5px] border-custom-border-200 h-screen w-[0.5px] top-0 left-20 lg:left-32" />
|
|
||||||
<div className="fixed grid place-items-center bg-custom-background-100 sm:py-5 top-11 sm:top-12 left-7 sm:left-16 lg:left-28">
|
|
||||||
<div className="grid place-items-center bg-custom-background-100">
|
|
||||||
<div className="h-[30px] w-[30px]">
|
|
||||||
<Image src={BluePlaneLogoWithoutText} alt="Plane Logo" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
<div className="grid place-items-center h-full overflow-y-auto py-5 px-7">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-center text-2xl sm:text-2.5xl font-semibold text-custom-text-100">
|
|
||||||
Sign in to Plane
|
|
||||||
</h1>
|
|
||||||
<>
|
|
||||||
{enableEmailPassword && <EmailPasswordForm onSubmit={handlePasswordSignIn} />}
|
|
||||||
{data?.magic_login && (
|
|
||||||
<div className="flex flex-col divide-y divide-custom-border-200">
|
|
||||||
<div className="pb-7">
|
|
||||||
<EmailCodeForm handleSignIn={handleEmailCodeSignIn} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="flex flex-col items-center justify-center gap-4 pt-7 sm:w-[360px] mx-auto overflow-hidden">
|
|
||||||
{data?.google && <GoogleLoginButton clientId={data?.google} handleSignIn={handleGoogleSignIn} />}
|
|
||||||
{data?.github && <GithubLoginButton clientId={data?.github} handleSignIn={handleGitHubSignIn} />}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
<p className="pt-16 text-custom-text-200 text-sm text-center">
|
|
||||||
By signing up, you agree to the{" "}
|
|
||||||
<a
|
|
||||||
href="https://plane.so/terms-and-conditions"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="font-medium underline"
|
|
||||||
>
|
|
||||||
Terms & Conditions
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
@ -11,7 +11,7 @@ import { Button, CustomSelect, Input } from "@plane/ui";
|
|||||||
// icons
|
// icons
|
||||||
import { Plus, X } from "lucide-react";
|
import { Plus, X } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import { IUser } from "types";
|
import { IUser, TUserWorkspaceRole } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { ROLE } from "constants/workspace";
|
import { ROLE } from "constants/workspace";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
@ -27,7 +27,7 @@ type Props = {
|
|||||||
|
|
||||||
type EmailRole = {
|
type EmailRole = {
|
||||||
email: string;
|
email: string;
|
||||||
role: 5 | 10 | 15 | 20;
|
role: TUserWorkspaceRole;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FormValues = {
|
type FormValues = {
|
||||||
|
@ -15,6 +15,7 @@ import { CustomSelect, Tooltip } from "@plane/ui";
|
|||||||
import { ChevronDown, XCircle } from "lucide-react";
|
import { ChevronDown, XCircle } from "lucide-react";
|
||||||
// constants
|
// constants
|
||||||
import { ROLE } from "constants/workspace";
|
import { ROLE } from "constants/workspace";
|
||||||
|
import { TUserWorkspaceRole } from "types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
member: {
|
member: {
|
||||||
@ -25,7 +26,7 @@ type Props = {
|
|||||||
last_name: string;
|
last_name: string;
|
||||||
email: string | undefined;
|
email: string | undefined;
|
||||||
display_name: string;
|
display_name: string;
|
||||||
role: 5 | 10 | 15 | 20;
|
role: TUserWorkspaceRole;
|
||||||
status: boolean;
|
status: boolean;
|
||||||
member: boolean;
|
member: boolean;
|
||||||
accountCreated: boolean;
|
accountCreated: boolean;
|
||||||
@ -153,7 +154,7 @@ export const WorkspaceMembersListItem: FC<Props> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
value={member.role}
|
value={member.role}
|
||||||
onChange={(value: 5 | 10 | 15 | 20 | undefined) => {
|
onChange={(value: TUserWorkspaceRole | undefined) => {
|
||||||
if (!workspaceSlug || !value) return;
|
if (!workspaceSlug || !value) return;
|
||||||
|
|
||||||
workspaceStore
|
workspaceStore
|
||||||
|
@ -8,10 +8,8 @@ import { Check, LogOut, Plus, Settings, UserCircle2 } from "lucide-react";
|
|||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// services
|
// services
|
||||||
import { UserService } from "services/user.service";
|
|
||||||
import { AuthService } from "services/auth.service";
|
import { AuthService } from "services/auth.service";
|
||||||
// ui
|
// ui
|
||||||
import { Avatar, Loader } from "@plane/ui";
|
import { Avatar, Loader } from "@plane/ui";
|
||||||
@ -47,30 +45,26 @@ const profileLinks = (workspaceSlug: string, userId: string) => [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const userService = new UserService();
|
|
||||||
const authService = new AuthService();
|
const authService = new AuthService();
|
||||||
|
|
||||||
export const WorkspaceSidebarDropdown = observer(() => {
|
export const WorkspaceSidebarDropdown = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
const { theme: themeStore, workspace: workspaceStore } = useMobxStore();
|
const { theme: themeStore, workspace: workspaceStore, user: userStore } = useMobxStore();
|
||||||
|
|
||||||
const { workspaces, currentWorkspace: activeWorkspace } = workspaceStore;
|
const { workspaces, currentWorkspace: activeWorkspace } = workspaceStore;
|
||||||
|
const user = userStore.currentUser;
|
||||||
const { user, mutateUser } = useUser();
|
|
||||||
|
|
||||||
const { setTheme } = useTheme();
|
const { setTheme } = useTheme();
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const handleWorkspaceNavigation = (workspace: IWorkspace) => {
|
const handleWorkspaceNavigation = (workspace: IWorkspace) => {
|
||||||
userService
|
userStore
|
||||||
.updateUser({
|
.updateCurrentUser({
|
||||||
last_workspace_id: workspace?.id,
|
last_workspace_id: workspace?.id,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
mutateUser();
|
|
||||||
router.push(`/${workspace.slug}/`);
|
router.push(`/${workspace.slug}/`);
|
||||||
})
|
})
|
||||||
.catch(() =>
|
.catch(() =>
|
||||||
@ -86,7 +80,6 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
|||||||
await authService
|
await authService
|
||||||
.signOut()
|
.signOut()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
mutateUser(undefined);
|
|
||||||
router.push("/");
|
router.push("/");
|
||||||
setTheme("system");
|
setTheme("system");
|
||||||
})
|
})
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
import { createContext, useContext } from "react";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import useSWR from "swr";
|
|
||||||
|
|
||||||
// services
|
|
||||||
import { ProjectService } from "services/project";
|
|
||||||
// keys
|
|
||||||
import { USER_PROJECT_VIEW } from "constants/fetch-keys";
|
|
||||||
// types
|
|
||||||
import { IProjectMember } from "types";
|
|
||||||
|
|
||||||
type ContextType = {
|
|
||||||
loading: boolean;
|
|
||||||
memberDetails?: IProjectMember;
|
|
||||||
error: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
// services
|
|
||||||
const projectService = new ProjectService();
|
|
||||||
|
|
||||||
export const ProjectMemberContext = createContext<ContextType>({} as ContextType);
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
children: React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ProjectMemberProvider: React.FC<Props> = (props) => {
|
|
||||||
const { children } = props;
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const { workspaceSlug, projectId } = router.query;
|
|
||||||
|
|
||||||
const { data: memberDetails, error } = useSWR(
|
|
||||||
workspaceSlug && projectId ? USER_PROJECT_VIEW(projectId.toString()) : null,
|
|
||||||
workspaceSlug && projectId
|
|
||||||
? () => projectService.projectMemberMe(workspaceSlug.toString(), projectId.toString())
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const loading = !memberDetails && !error;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ProjectMemberContext.Provider value={{ loading, memberDetails, error }}>{children}</ProjectMemberContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useProjectMyMembership = () => {
|
|
||||||
const context = useContext(ProjectMemberContext);
|
|
||||||
|
|
||||||
if (context === undefined) throw new Error(`useProjectMember must be used within a ProjectMemberProvider.`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...context,
|
|
||||||
memberRole: {
|
|
||||||
isOwner: context.memberDetails?.role === 20,
|
|
||||||
isMember: context.memberDetails?.role === 15,
|
|
||||||
isViewer: context.memberDetails?.role === 10,
|
|
||||||
isGuest: context.memberDetails?.role === 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,43 +0,0 @@
|
|||||||
import useSWR from "swr";
|
|
||||||
// services
|
|
||||||
import { IssueService } from "services/issue";
|
|
||||||
// hooks
|
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
|
||||||
// fetch-keys
|
|
||||||
import { PROJECT_ISSUES_LIST_WITH_PARAMS } from "constants/fetch-keys";
|
|
||||||
|
|
||||||
const issueService = new IssueService();
|
|
||||||
|
|
||||||
const useGanttChartIssues = (workspaceSlug: string | undefined, projectId: string | undefined) => {
|
|
||||||
const { displayFilters, filters } = useIssuesView();
|
|
||||||
|
|
||||||
const params: any = {
|
|
||||||
order_by: displayFilters.order_by,
|
|
||||||
type: displayFilters?.type ? displayFilters?.type : undefined,
|
|
||||||
sub_issue: displayFilters.sub_issue,
|
|
||||||
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
|
|
||||||
state: filters?.state ? filters?.state.join(",") : undefined,
|
|
||||||
priority: filters?.priority ? filters?.priority.join(",") : undefined,
|
|
||||||
labels: filters?.labels ? filters?.labels.join(",") : undefined,
|
|
||||||
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
|
|
||||||
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,
|
|
||||||
target_date: filters?.target_date ? filters?.target_date.join(",") : undefined,
|
|
||||||
start_target_date: true, // to fetch only issues with a start and target date
|
|
||||||
};
|
|
||||||
|
|
||||||
// all issues under the workspace and project
|
|
||||||
const { data: ganttIssues, mutate: mutateGanttIssues } = useSWR(
|
|
||||||
workspaceSlug && projectId ? PROJECT_ISSUES_LIST_WITH_PARAMS(projectId, params) : null,
|
|
||||||
workspaceSlug && projectId
|
|
||||||
? () => issueService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString(), params)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
ganttIssues,
|
|
||||||
mutateGanttIssues,
|
|
||||||
params,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useGanttChartIssues;
|
|
@ -1,178 +0,0 @@
|
|||||||
import { useEffect, useCallback } from "react";
|
|
||||||
import useSWR, { mutate } from "swr";
|
|
||||||
// services
|
|
||||||
import { WorkspaceService } from "services/workspace.service";
|
|
||||||
// types
|
|
||||||
import {
|
|
||||||
IIssueDisplayFilterOptions,
|
|
||||||
IIssueFilterOptions,
|
|
||||||
IWorkspaceMemberMe,
|
|
||||||
IWorkspaceViewProps,
|
|
||||||
Properties,
|
|
||||||
} from "types";
|
|
||||||
// fetch-keys
|
|
||||||
import { WORKSPACE_MEMBERS_ME } from "constants/fetch-keys";
|
|
||||||
|
|
||||||
const workspaceService = new WorkspaceService();
|
|
||||||
|
|
||||||
const initialValues: IWorkspaceViewProps = {
|
|
||||||
display_filters: {
|
|
||||||
group_by: null,
|
|
||||||
layout: "list",
|
|
||||||
order_by: "-created_at",
|
|
||||||
show_empty_groups: true,
|
|
||||||
sub_issue: true,
|
|
||||||
type: null,
|
|
||||||
},
|
|
||||||
display_properties: {
|
|
||||||
assignee: true,
|
|
||||||
start_date: true,
|
|
||||||
due_date: true,
|
|
||||||
key: true,
|
|
||||||
labels: true,
|
|
||||||
priority: true,
|
|
||||||
state: true,
|
|
||||||
sub_issue_count: true,
|
|
||||||
attachment_count: true,
|
|
||||||
link: true,
|
|
||||||
estimate: true,
|
|
||||||
created_on: true,
|
|
||||||
updated_on: true,
|
|
||||||
},
|
|
||||||
filters: {
|
|
||||||
assignees: null,
|
|
||||||
created_by: null,
|
|
||||||
labels: null,
|
|
||||||
priority: null,
|
|
||||||
state_group: null,
|
|
||||||
subscriber: null,
|
|
||||||
start_date: null,
|
|
||||||
target_date: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const useMyIssuesFilters = (workspaceSlug: string | undefined) => {
|
|
||||||
const { data: myWorkspace } = useSWR(
|
|
||||||
workspaceSlug ? WORKSPACE_MEMBERS_ME(workspaceSlug as string) : null,
|
|
||||||
workspaceSlug ? () => workspaceService.workspaceMemberMe(workspaceSlug as string) : null,
|
|
||||||
{
|
|
||||||
shouldRetryOnError: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const saveData = useCallback(
|
|
||||||
(data: Partial<IWorkspaceViewProps>) => {
|
|
||||||
if (!workspaceSlug || !myWorkspace) return;
|
|
||||||
|
|
||||||
const oldData = { ...myWorkspace };
|
|
||||||
|
|
||||||
mutate<IWorkspaceMemberMe>(
|
|
||||||
WORKSPACE_MEMBERS_ME(workspaceSlug.toString()),
|
|
||||||
(prevData) => {
|
|
||||||
if (!prevData) return;
|
|
||||||
return {
|
|
||||||
...prevData,
|
|
||||||
view_props: {
|
|
||||||
...prevData?.view_props,
|
|
||||||
...data,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
workspaceService.updateWorkspaceView(workspaceSlug, {
|
|
||||||
view_props: {
|
|
||||||
...oldData.view_props,
|
|
||||||
...data,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[myWorkspace, workspaceSlug]
|
|
||||||
);
|
|
||||||
|
|
||||||
const groupBy = (myWorkspace?.view_props ?? initialValues).display_filters?.group_by;
|
|
||||||
const filters = (myWorkspace?.view_props ?? initialValues).filters;
|
|
||||||
|
|
||||||
const setDisplayFilters = useCallback(
|
|
||||||
(displayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
|
||||||
const payload: Partial<IWorkspaceViewProps> = {
|
|
||||||
display_filters: {
|
|
||||||
...myWorkspace?.view_props?.display_filters,
|
|
||||||
...displayFilter,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (displayFilter.layout && displayFilter.layout === "kanban" && groupBy === null && payload.display_filters)
|
|
||||||
payload.display_filters.group_by = "state_detail.group";
|
|
||||||
|
|
||||||
saveData(payload);
|
|
||||||
},
|
|
||||||
[groupBy, myWorkspace?.view_props.display_filters, saveData]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setProperty = useCallback(
|
|
||||||
(key: keyof Properties) => {
|
|
||||||
if (!myWorkspace?.view_props.display_properties) return;
|
|
||||||
|
|
||||||
saveData({
|
|
||||||
display_properties: {
|
|
||||||
...myWorkspace.view_props?.display_properties,
|
|
||||||
[key]: !myWorkspace.view_props?.display_properties[key],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[myWorkspace, saveData]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setFilters = useCallback(
|
|
||||||
(updatedFilter: Partial<IIssueFilterOptions>) => {
|
|
||||||
if (!myWorkspace) return;
|
|
||||||
|
|
||||||
saveData({
|
|
||||||
filters: {
|
|
||||||
...myWorkspace.view_props?.filters,
|
|
||||||
...updatedFilter,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[myWorkspace, saveData]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!myWorkspace || !workspaceSlug) return;
|
|
||||||
|
|
||||||
if (!myWorkspace.view_props) {
|
|
||||||
workspaceService.updateWorkspaceView(workspaceSlug, {
|
|
||||||
view_props: { ...initialValues },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [myWorkspace, workspaceSlug]);
|
|
||||||
|
|
||||||
const newProperties: Properties = {
|
|
||||||
assignee: myWorkspace?.view_props.display_properties?.assignee ?? true,
|
|
||||||
start_date: myWorkspace?.view_props.display_properties?.start_date ?? true,
|
|
||||||
due_date: myWorkspace?.view_props.display_properties?.due_date ?? true,
|
|
||||||
key: myWorkspace?.view_props.display_properties?.key ?? true,
|
|
||||||
labels: myWorkspace?.view_props.display_properties?.labels ?? true,
|
|
||||||
priority: myWorkspace?.view_props.display_properties?.priority ?? true,
|
|
||||||
state: myWorkspace?.view_props.display_properties?.state ?? true,
|
|
||||||
sub_issue_count: myWorkspace?.view_props.display_properties?.sub_issue_count ?? true,
|
|
||||||
attachment_count: myWorkspace?.view_props.display_properties?.attachment_count ?? true,
|
|
||||||
link: myWorkspace?.view_props.display_properties?.link ?? true,
|
|
||||||
estimate: myWorkspace?.view_props.display_properties?.estimate ?? true,
|
|
||||||
created_on: myWorkspace?.view_props.display_properties?.created_on ?? true,
|
|
||||||
updated_on: myWorkspace?.view_props.display_properties?.updated_on ?? true,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
displayFilters: myWorkspace?.view_props?.display_filters,
|
|
||||||
setDisplayFilters,
|
|
||||||
properties: newProperties,
|
|
||||||
setProperty,
|
|
||||||
filters,
|
|
||||||
setFilters,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useMyIssuesFilters;
|
|
@ -1,87 +0,0 @@
|
|||||||
import { useMemo } from "react";
|
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
import useSWR from "swr";
|
|
||||||
|
|
||||||
// services
|
|
||||||
import { UserService } from "services/user.service";
|
|
||||||
// hooks
|
|
||||||
import useMyIssuesFilters from "hooks/my-issues/use-my-issues-filter";
|
|
||||||
// types
|
|
||||||
import { IIssue } from "types";
|
|
||||||
// fetch-keys
|
|
||||||
import { USER_ISSUES } from "constants/fetch-keys";
|
|
||||||
|
|
||||||
// services
|
|
||||||
const userService = new UserService();
|
|
||||||
|
|
||||||
const useMyIssues = (workspaceSlug: string | undefined) => {
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const { filters, displayFilters } = useMyIssuesFilters(workspaceSlug);
|
|
||||||
|
|
||||||
const params: any = {
|
|
||||||
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
|
|
||||||
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
|
|
||||||
group_by: displayFilters?.group_by,
|
|
||||||
labels: filters?.labels ? filters?.labels.join(",") : undefined,
|
|
||||||
order_by: displayFilters?.order_by,
|
|
||||||
priority: filters?.priority ? filters?.priority.join(",") : undefined,
|
|
||||||
state_group: filters?.state_group ? filters?.state_group.join(",") : undefined,
|
|
||||||
subscriber: filters?.subscriber ? filters?.subscriber.join(",") : undefined,
|
|
||||||
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,
|
|
||||||
target_date: filters?.target_date ? filters?.target_date.join(",") : undefined,
|
|
||||||
type: displayFilters?.type,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data: myIssues, mutate: mutateMyIssues } = useSWR(
|
|
||||||
workspaceSlug && router.pathname.includes("my-issues") ? USER_ISSUES(workspaceSlug.toString(), params) : null,
|
|
||||||
workspaceSlug && router.pathname.includes("my-issues")
|
|
||||||
? () => userService.userIssues(workspaceSlug.toString(), params)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const groupedIssues:
|
|
||||||
| {
|
|
||||||
[key: string]: IIssue[];
|
|
||||||
}
|
|
||||||
| undefined = useMemo(() => {
|
|
||||||
if (!myIssues) return undefined;
|
|
||||||
|
|
||||||
if (Array.isArray(myIssues))
|
|
||||||
return {
|
|
||||||
allIssues: myIssues,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (displayFilters?.group_by === "state_detail.group") {
|
|
||||||
return myIssues
|
|
||||||
? Object.assign(
|
|
||||||
{
|
|
||||||
backlog: [],
|
|
||||||
unstarted: [],
|
|
||||||
started: [],
|
|
||||||
completed: [],
|
|
||||||
cancelled: [],
|
|
||||||
},
|
|
||||||
myIssues
|
|
||||||
)
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return myIssues;
|
|
||||||
}, [displayFilters, myIssues]);
|
|
||||||
|
|
||||||
const isEmpty =
|
|
||||||
Object.values(groupedIssues ?? {}).every((group) => group.length === 0) ||
|
|
||||||
Object.keys(groupedIssues ?? {}).length === 0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
groupedIssues,
|
|
||||||
isEmpty,
|
|
||||||
mutateMyIssues,
|
|
||||||
params,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useMyIssues;
|
|
@ -1,19 +1,15 @@
|
|||||||
import { IMentionHighlight, IMentionSuggestion } from "@plane/rich-text-editor";
|
import { IMentionHighlight, IMentionSuggestion } from "@plane/rich-text-editor";
|
||||||
import useProjectMembers from "./use-project-members";
|
|
||||||
import useUser from "./use-user";
|
import useUser from "./use-user";
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
import { RootStore } from "store/root";
|
import { RootStore } from "store/root";
|
||||||
|
|
||||||
const useEditorSuggestions = (
|
const useEditorSuggestions = (_workspaceSlug: string | undefined, _projectId: string | undefined) => {
|
||||||
_workspaceSlug: string | undefined,
|
const { mentionsStore }: RootStore = useMobxStore();
|
||||||
_projectId: string | undefined,
|
|
||||||
) => {
|
|
||||||
const { mentionsStore }: RootStore = useMobxStore()
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mentionSuggestions: mentionsStore.mentionSuggestions,
|
mentionSuggestions: mentionsStore.mentionSuggestions,
|
||||||
mentionHighlights: mentionsStore.mentionHighlights
|
mentionHighlights: mentionsStore.mentionHighlights,
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useEditorSuggestions;
|
export default useEditorSuggestions;
|
||||||
|
@ -1,211 +0,0 @@
|
|||||||
import { useContext, useMemo } from "react";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import useSWR from "swr";
|
|
||||||
// contexts
|
|
||||||
import { issueViewContext } from "contexts/issue-view.context";
|
|
||||||
// services
|
|
||||||
import { IssueService, IssueArchiveService, IssueDraftService } from "services/issue";
|
|
||||||
import { CycleService } from "services/cycle.service";
|
|
||||||
import { ModuleService } from "services/module.service";
|
|
||||||
import { ProjectStateService } from "services/project";
|
|
||||||
// helpers
|
|
||||||
import { getStatesList } from "helpers/state.helper";
|
|
||||||
// types
|
|
||||||
import type { IIssue } from "types";
|
|
||||||
// fetch-keys
|
|
||||||
import {
|
|
||||||
CYCLE_ISSUES_WITH_PARAMS,
|
|
||||||
MODULE_ISSUES_WITH_PARAMS,
|
|
||||||
PROJECT_ARCHIVED_ISSUES_LIST_WITH_PARAMS,
|
|
||||||
PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS,
|
|
||||||
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
|
||||||
STATES_LIST,
|
|
||||||
VIEW_ISSUES,
|
|
||||||
} from "constants/fetch-keys";
|
|
||||||
|
|
||||||
// services
|
|
||||||
const issueService = new IssueService();
|
|
||||||
const issueArchiveService = new IssueArchiveService();
|
|
||||||
const issueDraftService = new IssueDraftService();
|
|
||||||
const cycleService = new CycleService();
|
|
||||||
const moduleService = new ModuleService();
|
|
||||||
const projectStateService = new ProjectStateService();
|
|
||||||
|
|
||||||
const useIssuesView = () => {
|
|
||||||
const {
|
|
||||||
display_filters: displayFilters,
|
|
||||||
setDisplayFilters,
|
|
||||||
filters,
|
|
||||||
setFilters,
|
|
||||||
resetFilterToDefault,
|
|
||||||
setNewFilterDefaultView,
|
|
||||||
} = useContext(issueViewContext);
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId, viewId, archivedIssueId } = router.query;
|
|
||||||
const isArchivedIssues = router.pathname.includes("archived-issues");
|
|
||||||
const isDraftIssues = router.pathname.includes("draft-issues");
|
|
||||||
|
|
||||||
const params: any = {
|
|
||||||
order_by: displayFilters?.order_by,
|
|
||||||
group_by: displayFilters?.group_by,
|
|
||||||
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
|
|
||||||
mentions: filters?.mentions ? filters?.mentions.join(",") : undefined,
|
|
||||||
state: filters?.state ? filters?.state.join(",") : undefined,
|
|
||||||
priority: filters?.priority ? filters?.priority.join(",") : undefined,
|
|
||||||
type: !isArchivedIssues ? (displayFilters?.type ? displayFilters?.type : undefined) : undefined,
|
|
||||||
labels: filters?.labels ? filters?.labels.join(",") : undefined,
|
|
||||||
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
|
|
||||||
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,
|
|
||||||
target_date: filters?.target_date ? filters?.target_date.join(",") : undefined,
|
|
||||||
sub_issue: displayFilters?.sub_issue,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data: projectIssues, mutate: mutateProjectIssues } = useSWR(
|
|
||||||
workspaceSlug && projectId && params ? PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params) : null,
|
|
||||||
workspaceSlug && projectId && params
|
|
||||||
? () => issueService.getIssuesWithParams(workspaceSlug as string, projectId as string, params)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: projectArchivedIssues, mutate: mutateProjectArchivedIssues } = useSWR(
|
|
||||||
workspaceSlug && projectId && params && isArchivedIssues && !archivedIssueId
|
|
||||||
? PROJECT_ARCHIVED_ISSUES_LIST_WITH_PARAMS(projectId as string, params)
|
|
||||||
: null,
|
|
||||||
workspaceSlug && projectId && params && isArchivedIssues && !archivedIssueId
|
|
||||||
? () => issueArchiveService.getArchivedIssues(workspaceSlug as string, projectId as string, params)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: draftIssues, mutate: mutateDraftIssues } = useSWR(
|
|
||||||
workspaceSlug && projectId && params && isDraftIssues && !archivedIssueId
|
|
||||||
? PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(projectId as string, params)
|
|
||||||
: null,
|
|
||||||
workspaceSlug && projectId && params && isDraftIssues && !archivedIssueId
|
|
||||||
? () => issueDraftService.getDraftIssues(workspaceSlug as string, projectId as string, params)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: cycleIssues, mutate: mutateCycleIssues } = useSWR(
|
|
||||||
workspaceSlug && projectId && cycleId && params ? CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params) : null,
|
|
||||||
workspaceSlug && projectId && cycleId && params
|
|
||||||
? () =>
|
|
||||||
cycleService.getCycleIssuesWithParams(workspaceSlug as string, projectId as string, cycleId as string, params)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: moduleIssues, mutate: mutateModuleIssues } = useSWR(
|
|
||||||
workspaceSlug && projectId && moduleId && params ? MODULE_ISSUES_WITH_PARAMS(moduleId as string, params) : null,
|
|
||||||
workspaceSlug && projectId && moduleId && params
|
|
||||||
? () =>
|
|
||||||
moduleService.getModuleIssuesWithParams(
|
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
moduleId as string,
|
|
||||||
params
|
|
||||||
)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: viewIssues, mutate: mutateViewIssues } = useSWR(
|
|
||||||
workspaceSlug && projectId && viewId && params ? VIEW_ISSUES(viewId.toString(), params) : null,
|
|
||||||
workspaceSlug && projectId && viewId && params
|
|
||||||
? () => issueService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString(), params)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: states } = useSWR(
|
|
||||||
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
|
|
||||||
workspaceSlug && projectId
|
|
||||||
? () => projectStateService.getStates(workspaceSlug as string, projectId as string)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
const statesList = getStatesList(states);
|
|
||||||
const activeStatesList = statesList?.filter((state) => state.group === "started" || state.group === "unstarted");
|
|
||||||
const backlogStatesList = statesList?.filter((state) => state.group === "backlog");
|
|
||||||
|
|
||||||
const stateIds =
|
|
||||||
displayFilters && displayFilters?.type === "active"
|
|
||||||
? activeStatesList?.map((state) => state.id)
|
|
||||||
: displayFilters?.type === "backlog"
|
|
||||||
? backlogStatesList?.map((state) => state.id)
|
|
||||||
: statesList?.map((state) => state.id);
|
|
||||||
|
|
||||||
const filteredStateIds =
|
|
||||||
(filters && filters?.state ? stateIds?.filter((s) => filters.state?.includes(s)) : stateIds) ?? [];
|
|
||||||
|
|
||||||
const emptyStatesObject: { [key: string]: [] } = {};
|
|
||||||
for (let i = 0; i < filteredStateIds.length; i++) {
|
|
||||||
emptyStatesObject[filteredStateIds[i]] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const groupedByIssues:
|
|
||||||
| {
|
|
||||||
[key: string]: IIssue[];
|
|
||||||
}
|
|
||||||
| undefined = useMemo(() => {
|
|
||||||
const issuesToGroup = cycleId
|
|
||||||
? cycleIssues
|
|
||||||
: moduleId
|
|
||||||
? moduleIssues
|
|
||||||
: viewId
|
|
||||||
? viewIssues
|
|
||||||
: isArchivedIssues
|
|
||||||
? projectArchivedIssues
|
|
||||||
: isDraftIssues
|
|
||||||
? draftIssues
|
|
||||||
: projectIssues;
|
|
||||||
|
|
||||||
if (Array.isArray(issuesToGroup)) return { allIssues: issuesToGroup };
|
|
||||||
if (displayFilters?.group_by === "state")
|
|
||||||
return issuesToGroup ? Object.assign(emptyStatesObject, issuesToGroup) : undefined;
|
|
||||||
|
|
||||||
return issuesToGroup;
|
|
||||||
}, [
|
|
||||||
displayFilters?.group_by,
|
|
||||||
projectIssues,
|
|
||||||
cycleIssues,
|
|
||||||
moduleIssues,
|
|
||||||
viewIssues,
|
|
||||||
projectArchivedIssues,
|
|
||||||
cycleId,
|
|
||||||
moduleId,
|
|
||||||
viewId,
|
|
||||||
isArchivedIssues,
|
|
||||||
isDraftIssues,
|
|
||||||
draftIssues,
|
|
||||||
emptyStatesObject,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const isEmpty =
|
|
||||||
Object.values(groupedByIssues ?? {}).every((group) => group.length === 0) ||
|
|
||||||
Object.keys(groupedByIssues ?? {}).length === 0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
displayFilters: {
|
|
||||||
...displayFilters,
|
|
||||||
layout: isArchivedIssues ? "list" : displayFilters?.layout,
|
|
||||||
},
|
|
||||||
setDisplayFilters,
|
|
||||||
groupedByIssues,
|
|
||||||
mutateIssues: cycleId
|
|
||||||
? mutateCycleIssues
|
|
||||||
: moduleId
|
|
||||||
? mutateModuleIssues
|
|
||||||
: viewId
|
|
||||||
? mutateViewIssues
|
|
||||||
: isArchivedIssues
|
|
||||||
? mutateProjectArchivedIssues
|
|
||||||
: isDraftIssues
|
|
||||||
? mutateDraftIssues
|
|
||||||
: mutateProjectIssues,
|
|
||||||
filters,
|
|
||||||
setFilters,
|
|
||||||
params,
|
|
||||||
isEmpty,
|
|
||||||
resetFilterToDefault,
|
|
||||||
setNewFilterDefaultView,
|
|
||||||
} as const;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useIssuesView;
|
|
@ -1,123 +0,0 @@
|
|||||||
import { useContext, useEffect, useMemo } from "react";
|
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
import useSWR from "swr";
|
|
||||||
|
|
||||||
// services
|
|
||||||
import { UserService } from "services/user.service";
|
|
||||||
// contexts
|
|
||||||
import { profileIssuesContext } from "contexts/profile-issues-context";
|
|
||||||
import { useWorkspaceMyMembership } from "contexts/workspace-member.context";
|
|
||||||
// types
|
|
||||||
import { IIssue } from "types";
|
|
||||||
// fetch-keys
|
|
||||||
import { USER_PROFILE_ISSUES } from "constants/fetch-keys";
|
|
||||||
|
|
||||||
const userService = new UserService();
|
|
||||||
|
|
||||||
const useProfileIssues = (workspaceSlug: string | undefined, userId: string | undefined) => {
|
|
||||||
const {
|
|
||||||
display_filters: displayFilters,
|
|
||||||
setDisplayFilters,
|
|
||||||
filters,
|
|
||||||
setFilters,
|
|
||||||
display_properties: displayProperties,
|
|
||||||
setProperties,
|
|
||||||
} = useContext(profileIssuesContext);
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const { memberRole } = useWorkspaceMyMembership();
|
|
||||||
|
|
||||||
const params: any = {
|
|
||||||
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
|
|
||||||
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
|
|
||||||
group_by: displayFilters?.group_by,
|
|
||||||
labels: filters?.labels ? filters?.labels.join(",") : undefined,
|
|
||||||
order_by: displayFilters?.order_by,
|
|
||||||
priority: filters?.priority ? filters?.priority.join(",") : undefined,
|
|
||||||
state_group: filters?.state_group ? filters?.state_group.join(",") : undefined,
|
|
||||||
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,
|
|
||||||
target_date: filters?.target_date ? filters?.target_date.join(",") : undefined,
|
|
||||||
type: displayFilters?.type ? displayFilters?.type : undefined,
|
|
||||||
subscriber: filters?.subscriber ? filters?.subscriber.join(",") : undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data: userProfileIssues, mutate: mutateProfileIssues } = useSWR(
|
|
||||||
workspaceSlug && userId && (memberRole.isOwner || memberRole.isMember || memberRole.isViewer)
|
|
||||||
? USER_PROFILE_ISSUES(workspaceSlug.toString(), userId.toString(), params)
|
|
||||||
: null,
|
|
||||||
workspaceSlug && userId && (memberRole.isOwner || memberRole.isMember || memberRole.isViewer)
|
|
||||||
? () => userService.getUserProfileIssues(workspaceSlug.toString(), userId.toString(), params)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const groupedIssues:
|
|
||||||
| {
|
|
||||||
[key: string]: IIssue[];
|
|
||||||
}
|
|
||||||
| undefined = useMemo(() => {
|
|
||||||
if (!userProfileIssues) return undefined;
|
|
||||||
|
|
||||||
if (Array.isArray(userProfileIssues))
|
|
||||||
return {
|
|
||||||
allIssues: userProfileIssues,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (displayFilters?.group_by === "state_detail.group") {
|
|
||||||
return userProfileIssues
|
|
||||||
? Object.assign(
|
|
||||||
{
|
|
||||||
backlog: [],
|
|
||||||
unstarted: [],
|
|
||||||
started: [],
|
|
||||||
completed: [],
|
|
||||||
cancelled: [],
|
|
||||||
},
|
|
||||||
userProfileIssues
|
|
||||||
)
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return userProfileIssues;
|
|
||||||
}, [displayFilters?.group_by, userProfileIssues]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!userId || !filters) return;
|
|
||||||
|
|
||||||
if (router.pathname.includes("assigned") && (!filters.assignees || !filters.assignees.includes(userId))) {
|
|
||||||
setFilters({ assignees: [...(filters.assignees ?? []), userId] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (router.pathname.includes("created") && (!filters.created_by || !filters.created_by.includes(userId))) {
|
|
||||||
setFilters({ created_by: [...(filters.created_by ?? []), userId] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (router.pathname.includes("subscribed") && filters.subscriber === null) {
|
|
||||||
setFilters({ subscriber: [userId] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}, [filters, router, setFilters, userId]);
|
|
||||||
|
|
||||||
const isEmpty =
|
|
||||||
Object.values(groupedIssues ?? {}).every((group) => group.length === 0) ||
|
|
||||||
Object.keys(groupedIssues ?? {}).length === 0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
groupedIssues,
|
|
||||||
displayFilters,
|
|
||||||
setDisplayFilters,
|
|
||||||
filters,
|
|
||||||
setFilters,
|
|
||||||
displayProperties,
|
|
||||||
setProperties,
|
|
||||||
isEmpty,
|
|
||||||
mutateProfileIssues,
|
|
||||||
params,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useProfileIssues;
|
|
@ -1,44 +0,0 @@
|
|||||||
import useSWR from "swr";
|
|
||||||
// services
|
|
||||||
import { ProjectService } from "services/project";
|
|
||||||
// fetch-keys
|
|
||||||
import { PROJECT_MEMBERS } from "constants/fetch-keys";
|
|
||||||
// hooks
|
|
||||||
import useUser from "./use-user";
|
|
||||||
|
|
||||||
const projectService = new ProjectService();
|
|
||||||
|
|
||||||
const useProjectMembers = (
|
|
||||||
workspaceSlug: string | undefined,
|
|
||||||
projectId: string | undefined,
|
|
||||||
fetchCondition?: boolean
|
|
||||||
) => {
|
|
||||||
fetchCondition = fetchCondition ?? true;
|
|
||||||
|
|
||||||
const { user } = useUser();
|
|
||||||
// fetching project members
|
|
||||||
const { data: members } = useSWR(
|
|
||||||
workspaceSlug && projectId && fetchCondition ? PROJECT_MEMBERS(projectId) : null,
|
|
||||||
workspaceSlug && projectId && fetchCondition
|
|
||||||
? () => projectService.fetchProjectMembers(workspaceSlug, projectId)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasJoined = members?.some((item: any) => item.member.id === (user as any)?.id);
|
|
||||||
|
|
||||||
const isOwner = members?.some((item) => item.member.id === (user as any)?.id && item.role === 20);
|
|
||||||
const isMember = members?.some((item) => item.member.id === (user as any)?.id && item.role === 15);
|
|
||||||
const isViewer = members?.some((item) => item.member.id === (user as any)?.id && item.role === 10);
|
|
||||||
const isGuest = members?.some((item) => item.member.id === (user as any)?.id && item.role === 5);
|
|
||||||
|
|
||||||
return {
|
|
||||||
members,
|
|
||||||
hasJoined,
|
|
||||||
isOwner,
|
|
||||||
isMember,
|
|
||||||
isViewer,
|
|
||||||
isGuest,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useProjectMembers;
|
|
@ -1,39 +0,0 @@
|
|||||||
import useSWR from "swr";
|
|
||||||
// services
|
|
||||||
import { WorkspaceService } from "services/workspace.service";
|
|
||||||
// fetch-keys
|
|
||||||
import { WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
|
||||||
// hooks
|
|
||||||
import useUser from "./use-user";
|
|
||||||
|
|
||||||
const workspaceService = new WorkspaceService();
|
|
||||||
|
|
||||||
const useWorkspaceMembers = (workspaceSlug: string | undefined, fetchCondition?: boolean) => {
|
|
||||||
fetchCondition = fetchCondition ?? true;
|
|
||||||
|
|
||||||
const { user } = useUser();
|
|
||||||
|
|
||||||
const { data: workspaceMembers, error: workspaceMemberErrors } = useSWR(
|
|
||||||
workspaceSlug && fetchCondition ? WORKSPACE_MEMBERS(workspaceSlug) : null,
|
|
||||||
workspaceSlug && fetchCondition ? () => workspaceService.fetchWorkspaceMembers(workspaceSlug) : null
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasJoined = workspaceMembers?.some((item: any) => item.member.id === (user as any)?.id);
|
|
||||||
|
|
||||||
const isOwner = workspaceMembers?.some((item) => item.member.id === (user as any)?.id && item.role === 20);
|
|
||||||
const isMember = workspaceMembers?.some((item) => item.member.id === (user as any)?.id && item.role === 15);
|
|
||||||
const isViewer = workspaceMembers?.some((item) => item.member.id === (user as any)?.id && item.role === 10);
|
|
||||||
const isGuest = workspaceMembers?.some((item) => item.member.id === (user as any)?.id && item.role === 5);
|
|
||||||
|
|
||||||
return {
|
|
||||||
workspaceMembers,
|
|
||||||
workspaceMemberErrors,
|
|
||||||
hasJoined,
|
|
||||||
isOwner,
|
|
||||||
isMember,
|
|
||||||
isViewer,
|
|
||||||
isGuest,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useWorkspaceMembers;
|
|
@ -2,9 +2,9 @@ import React, { useState } from "react";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
// hooks
|
import { observer } from "mobx-react-lite";
|
||||||
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
import useUser from "hooks/use-user";
|
|
||||||
// layouts
|
// layouts
|
||||||
import DefaultLayout from "layouts/default-layout";
|
import DefaultLayout from "layouts/default-layout";
|
||||||
import { UserAuthWrapper } from "layouts/auth-layout";
|
import { UserAuthWrapper } from "layouts/auth-layout";
|
||||||
@ -17,7 +17,7 @@ import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-l
|
|||||||
import { IWorkspace } from "types";
|
import { IWorkspace } from "types";
|
||||||
import type { NextPage } from "next";
|
import type { NextPage } from "next";
|
||||||
|
|
||||||
const CreateWorkspace: NextPage = () => {
|
const CreateWorkspace: NextPage = observer(() => {
|
||||||
const [defaultValues, setDefaultValues] = useState({
|
const [defaultValues, setDefaultValues] = useState({
|
||||||
name: "",
|
name: "",
|
||||||
slug: "",
|
slug: "",
|
||||||
@ -27,11 +27,10 @@ const CreateWorkspace: NextPage = () => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const { user: userStore } = useMobxStore();
|
const { user: userStore } = useMobxStore();
|
||||||
|
const user = userStore.currentUser;
|
||||||
|
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
const { user } = useUser();
|
|
||||||
|
|
||||||
const onSubmit = async (workspace: IWorkspace) => {
|
const onSubmit = async (workspace: IWorkspace) => {
|
||||||
await userStore
|
await userStore
|
||||||
.updateCurrentUser({ last_workspace_id: workspace.id })
|
.updateCurrentUser({ last_workspace_id: workspace.id })
|
||||||
@ -76,6 +75,6 @@ const CreateWorkspace: NextPage = () => {
|
|||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
</UserAuthWrapper>
|
</UserAuthWrapper>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default CreateWorkspace;
|
export default CreateWorkspace;
|
@ -6,7 +6,7 @@ import { UserService } from "services/user.service";
|
|||||||
import { WorkspaceService } from "services/workspace.service";
|
import { WorkspaceService } from "services/workspace.service";
|
||||||
// interfaces
|
// interfaces
|
||||||
import { IUser, IUserSettings } from "types/users";
|
import { IUser, IUserSettings } from "types/users";
|
||||||
import { IWorkspaceMemberMe, IProjectMember } from "types";
|
import { IWorkspaceMemberMe, IProjectMember, TUserProjectRole, TUserWorkspaceRole } from "types";
|
||||||
import { RootStore } from "./root";
|
import { RootStore } from "./root";
|
||||||
|
|
||||||
export interface IUserStore {
|
export interface IUserStore {
|
||||||
@ -34,8 +34,8 @@ export interface IUserStore {
|
|||||||
|
|
||||||
currentProjectMemberInfo: IProjectMember | undefined;
|
currentProjectMemberInfo: IProjectMember | undefined;
|
||||||
currentWorkspaceMemberInfo: IWorkspaceMemberMe | undefined;
|
currentWorkspaceMemberInfo: IWorkspaceMemberMe | undefined;
|
||||||
currentProjectRole: number | undefined;
|
currentProjectRole: TUserProjectRole | undefined;
|
||||||
currentWorkspaceRole: number | undefined;
|
currentWorkspaceRole: TUserWorkspaceRole | undefined;
|
||||||
|
|
||||||
hasPermissionToCurrentWorkspace: boolean | undefined;
|
hasPermissionToCurrentWorkspace: boolean | undefined;
|
||||||
hasPermissionToCurrentProject: boolean | undefined;
|
hasPermissionToCurrentProject: boolean | undefined;
|
||||||
@ -261,7 +261,15 @@ class UserStore implements IUserStore {
|
|||||||
|
|
||||||
updateCurrentUser = async (data: Partial<IUser>) => {
|
updateCurrentUser = async (data: Partial<IUser>) => {
|
||||||
try {
|
try {
|
||||||
|
runInAction(() => {
|
||||||
|
this.currentUser = {
|
||||||
|
...this.currentUser,
|
||||||
|
...data,
|
||||||
|
} as IUser;
|
||||||
|
});
|
||||||
|
|
||||||
const response = await this.userService.updateUser(data);
|
const response = await this.userService.updateUser(data);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.currentUser = response;
|
this.currentUser = response;
|
||||||
});
|
});
|
||||||
|
10
web/types/projects.d.ts
vendored
10
web/types/projects.d.ts
vendored
@ -1,5 +1,7 @@
|
|||||||
import type { IUserLite, IWorkspace, IWorkspaceLite, IUserMemberLite, TStateGroups, IProjectViewProps } from ".";
|
import type { IUserLite, IWorkspace, IWorkspaceLite, IUserMemberLite, TStateGroups, IProjectViewProps } from ".";
|
||||||
|
|
||||||
|
export type TUserProjectRole = 5 | 10 | 15 | 20;
|
||||||
|
|
||||||
export interface IProject {
|
export interface IProject {
|
||||||
archive_in: number;
|
archive_in: number;
|
||||||
close_in: number;
|
close_in: number;
|
||||||
@ -32,7 +34,7 @@ export interface IProject {
|
|||||||
is_deployed: boolean;
|
is_deployed: boolean;
|
||||||
is_favorite: boolean;
|
is_favorite: boolean;
|
||||||
is_member: boolean;
|
is_member: boolean;
|
||||||
member_role: 5 | 10 | 15 | 20 | null;
|
member_role: TUserProjectRole | null;
|
||||||
members: IProjectMemberLite[];
|
members: IProjectMemberLite[];
|
||||||
issue_views_view: boolean;
|
issue_views_view: boolean;
|
||||||
module_view: boolean;
|
module_view: boolean;
|
||||||
@ -76,7 +78,7 @@ export interface IProjectMember {
|
|||||||
project: IProjectLite;
|
project: IProjectLite;
|
||||||
workspace: IWorkspaceLite;
|
workspace: IWorkspaceLite;
|
||||||
comment: string;
|
comment: string;
|
||||||
role: 5 | 10 | 15 | 20;
|
role: TUserProjectRole;
|
||||||
|
|
||||||
preferences: ProjectPreferences;
|
preferences: ProjectPreferences;
|
||||||
|
|
||||||
@ -100,7 +102,7 @@ export interface IProjectMemberInvitation {
|
|||||||
token: string;
|
token: string;
|
||||||
message: string;
|
message: string;
|
||||||
responded_at: Date;
|
responded_at: Date;
|
||||||
role: 5 | 10 | 15 | 20;
|
role: TUserProjectRole;
|
||||||
|
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
@ -109,7 +111,7 @@ export interface IProjectMemberInvitation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IProjectBulkAddFormData {
|
export interface IProjectBulkAddFormData {
|
||||||
members: { role: 5 | 10 | 15 | 20; member_id: string }[];
|
members: { role: TUserProjectRole; member_id: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGithubRepository {
|
export interface IGithubRepository {
|
||||||
|
10
web/types/workspace.d.ts
vendored
10
web/types/workspace.d.ts
vendored
@ -1,5 +1,7 @@
|
|||||||
import type { IProjectMember, IUser, IUserLite, IUserMemberLite, IWorkspaceViewProps } from "types";
|
import type { IProjectMember, IUser, IUserLite, IUserMemberLite, IWorkspaceViewProps } from "types";
|
||||||
|
|
||||||
|
export type TUserWorkspaceRole = 5 | 10 | 15 | 20;
|
||||||
|
|
||||||
export interface IWorkspace {
|
export interface IWorkspace {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly owner: IUser;
|
readonly owner: IUser;
|
||||||
@ -30,13 +32,13 @@ export interface IWorkspaceMemberInvitation {
|
|||||||
token: string;
|
token: string;
|
||||||
message: string;
|
message: string;
|
||||||
responded_at: Date;
|
responded_at: Date;
|
||||||
role: 5 | 10 | 15 | 20;
|
role: TUserWorkspaceRole;
|
||||||
created_by_detail: IUser;
|
created_by_detail: IUser;
|
||||||
workspace: IWorkspace;
|
workspace: IWorkspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkspaceBulkInviteFormData {
|
export interface IWorkspaceBulkInviteFormData {
|
||||||
emails: { email: string; role: 5 | 10 | 15 | 20 }[];
|
emails: { email: string; role: TUserWorkspaceRole }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Properties = {
|
export type Properties = {
|
||||||
@ -61,7 +63,7 @@ export interface IWorkspaceMember {
|
|||||||
created_by: string;
|
created_by: string;
|
||||||
id: string;
|
id: string;
|
||||||
member: IUserLite;
|
member: IUserLite;
|
||||||
role: 5 | 10 | 15 | 20;
|
role: TUserWorkspaceRole;
|
||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
updated_by: string;
|
updated_by: string;
|
||||||
workspace: IWorkspaceLite;
|
workspace: IWorkspaceLite;
|
||||||
@ -74,7 +76,7 @@ export interface IWorkspaceMemberMe {
|
|||||||
default_props: IWorkspaceViewProps;
|
default_props: IWorkspaceViewProps;
|
||||||
id: string;
|
id: string;
|
||||||
member: string;
|
member: string;
|
||||||
role: 5 | 10 | 15 | 20;
|
role: TUserWorkspaceRole;
|
||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
updated_by: string;
|
updated_by: string;
|
||||||
view_props: IWorkspaceViewProps;
|
view_props: IWorkspaceViewProps;
|
||||||
|
Loading…
Reference in New Issue
Block a user