mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge pull request #857 from makeplane/develop
promote: develop to stage-release
This commit is contained in:
commit
718f62a898
@ -25,22 +25,18 @@ class IssueViewSerializer(BaseSerializer):
|
|||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
query_params = validated_data.get("query_data", {})
|
query_params = validated_data.get("query_data", {})
|
||||||
|
if bool(query_params):
|
||||||
if not bool(query_params):
|
validated_data["query"] = issue_filters(query_params, "POST")
|
||||||
raise serializers.ValidationError(
|
else:
|
||||||
{"query_data": ["Query data field cannot be empty"]}
|
validated_data["query"] = dict()
|
||||||
)
|
|
||||||
|
|
||||||
validated_data["query"] = issue_filters(query_params, "POST")
|
|
||||||
return IssueView.objects.create(**validated_data)
|
return IssueView.objects.create(**validated_data)
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
query_params = validated_data.get("query_data", {})
|
query_params = validated_data.get("query_data", {})
|
||||||
if not bool(query_params):
|
if bool(query_params):
|
||||||
raise serializers.ValidationError(
|
validated_data["query"] = issue_filters(query_params, "POST")
|
||||||
{"query_data": ["Query data field cannot be empty"]}
|
else:
|
||||||
)
|
validated_data["query"] = dict()
|
||||||
|
|
||||||
validated_data["query"] = issue_filters(query_params, "PATCH")
|
validated_data["query"] = issue_filters(query_params, "PATCH")
|
||||||
return super().update(instance, validated_data)
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
@ -246,6 +246,20 @@ class UserWorkSpaceIssues(BaseAPIView):
|
|||||||
.prefetch_related("assignees")
|
.prefetch_related("assignees")
|
||||||
.prefetch_related("labels")
|
.prefetch_related("labels")
|
||||||
.order_by("-created_at")
|
.order_by("-created_at")
|
||||||
|
.annotate(
|
||||||
|
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
||||||
|
.order_by()
|
||||||
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
|
.values("count")
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
attachment_count=IssueAttachment.objects.filter(
|
||||||
|
issue=OuterRef("id")
|
||||||
|
)
|
||||||
|
.order_by()
|
||||||
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
|
.values("count")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
serializer = IssueLiteSerializer(issues, many=True)
|
serializer = IssueLiteSerializer(issues, many=True)
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
@ -5,15 +5,16 @@ import { useRouter } from "next/router";
|
|||||||
|
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
|
|
||||||
|
// services
|
||||||
|
import projectService from "services/project.service";
|
||||||
// ui
|
// ui
|
||||||
import { PrimaryButton } from "components/ui";
|
import { PrimaryButton } from "components/ui";
|
||||||
// icon
|
// icons
|
||||||
import { AssignmentClipboardIcon } from "components/icons";
|
import { AssignmentClipboardIcon } from "components/icons";
|
||||||
// img
|
// images
|
||||||
import JoinProjectImg from "public/auth/project-not-authorized.svg";
|
import JoinProjectImg from "public/auth/project-not-authorized.svg";
|
||||||
import projectService from "services/project.service";
|
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_MEMBERS } from "constants/fetch-keys";
|
import { USER_PROJECT_VIEW } from "constants/fetch-keys";
|
||||||
|
|
||||||
export const JoinProject: React.FC = () => {
|
export const JoinProject: React.FC = () => {
|
||||||
const [isJoiningProject, setIsJoiningProject] = useState(false);
|
const [isJoiningProject, setIsJoiningProject] = useState(false);
|
||||||
@ -22,13 +23,16 @@ export const JoinProject: React.FC = () => {
|
|||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
const handleJoin = () => {
|
const handleJoin = () => {
|
||||||
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
setIsJoiningProject(true);
|
setIsJoiningProject(true);
|
||||||
projectService
|
projectService
|
||||||
.joinProject(workspaceSlug as string, {
|
.joinProject(workspaceSlug as string, {
|
||||||
project_ids: [projectId as string],
|
project_ids: [projectId as string],
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(async () => {
|
||||||
mutate(PROJECT_MEMBERS(projectId as string));
|
await mutate(USER_PROJECT_VIEW(workspaceSlug.toString()));
|
||||||
|
setIsJoiningProject(false);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
@ -19,7 +19,12 @@ import { LayerDiagonalIcon } from "components/icons";
|
|||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { CYCLE_ISSUES_WITH_PARAMS, MODULE_ISSUES_WITH_PARAMS } from "constants/fetch-keys";
|
import {
|
||||||
|
CYCLE_DETAILS,
|
||||||
|
CYCLE_ISSUES_WITH_PARAMS,
|
||||||
|
MODULE_DETAILS,
|
||||||
|
MODULE_ISSUES_WITH_PARAMS,
|
||||||
|
} from "constants/fetch-keys";
|
||||||
|
|
||||||
type FormInput = {
|
type FormInput = {
|
||||||
issues: string[];
|
issues: string[];
|
||||||
@ -76,8 +81,14 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await handleOnSubmit(data);
|
await handleOnSubmit(data);
|
||||||
if (cycleId) mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params));
|
if (cycleId) {
|
||||||
if (moduleId) mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
|
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();
|
||||||
|
|
||||||
|
@ -73,6 +73,10 @@ const activityDetails: {
|
|||||||
message: "updated the description.",
|
message: "updated the description.",
|
||||||
icon: <ChatBubbleBottomCenterTextIcon className="h-3 w-3 text-gray-500" aria-hidden="true" />,
|
icon: <ChatBubbleBottomCenterTextIcon className="h-3 w-3 text-gray-500" aria-hidden="true" />,
|
||||||
},
|
},
|
||||||
|
estimate_point: {
|
||||||
|
message: "set the estimate point to",
|
||||||
|
icon: <PlayIcon className="h-3 w-3 text-gray-500 -rotate-90" aria-hidden="true" />,
|
||||||
|
},
|
||||||
target_date: {
|
target_date: {
|
||||||
message: "set the due date to",
|
message: "set the due date to",
|
||||||
icon: <CalendarDaysIcon className="h-3 w-3 text-gray-500" aria-hidden="true" />,
|
icon: <CalendarDaysIcon className="h-3 w-3 text-gray-500" aria-hidden="true" />,
|
||||||
@ -136,8 +140,6 @@ export const Feeds: React.FC<any> = ({ activities }) => (
|
|||||||
action = `${activity.verb} the`;
|
action = `${activity.verb} the`;
|
||||||
} else if (activity.field === "link") {
|
} else if (activity.field === "link") {
|
||||||
action = `${activity.verb} the`;
|
action = `${activity.verb} the`;
|
||||||
} else if (activity.field === "estimate") {
|
|
||||||
action = "updated the";
|
|
||||||
}
|
}
|
||||||
// for values that are after the action clause
|
// for values that are after the action clause
|
||||||
let value: any = activity.new_value ? activity.new_value : activity.old_value;
|
let value: any = activity.new_value ? activity.new_value : activity.old_value;
|
||||||
@ -188,8 +190,10 @@ export const Feeds: React.FC<any> = ({ activities }) => (
|
|||||||
value = "attachment";
|
value = "attachment";
|
||||||
} else if (activity.field === "link") {
|
} else if (activity.field === "link") {
|
||||||
value = "link";
|
value = "link";
|
||||||
} else if (activity.field === "estimate") {
|
} else if (activity.field === "estimate_point") {
|
||||||
value = "estimate";
|
value = activity.new_value
|
||||||
|
? activity.new_value + ` Point${parseInt(activity.new_value ?? "", 10) > 1 ? "s" : ""}`
|
||||||
|
: "None";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activity.field === "comment") {
|
if (activity.field === "comment") {
|
||||||
|
@ -309,7 +309,19 @@ export const FilterList: React.FC<any> = ({ filters, setFilters }) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<span className="capitalize">{filters[key as keyof typeof filters]}</span>
|
<div className="flex items-center gap-x-1 capitalize">
|
||||||
|
{filters[key as keyof typeof filters]}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setFilters({
|
||||||
|
[key]: null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<XMarkIcon className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -319,6 +331,7 @@ export const FilterList: React.FC<any> = ({ filters, setFilters }) => {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setFilters({
|
setFilters({
|
||||||
|
type: null,
|
||||||
state: null,
|
state: null,
|
||||||
priority: null,
|
priority: null,
|
||||||
assignees: null,
|
assignees: null,
|
||||||
|
@ -234,7 +234,7 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
)
|
)
|
||||||
trackEventServices.trackIssueMarkedAsDoneEvent({
|
trackEventServices.trackIssueMarkedAsDoneEvent({
|
||||||
workspaceSlug,
|
workspaceSlug,
|
||||||
workspaceId: draggedItem.workspace_detail.id,
|
workspaceId: draggedItem.workspace,
|
||||||
projectName: draggedItem.project_detail.name,
|
projectName: draggedItem.project_detail.name,
|
||||||
projectIdentifier: draggedItem.project_detail.identifier,
|
projectIdentifier: draggedItem.project_detail.identifier,
|
||||||
projectId,
|
projectId,
|
||||||
@ -359,12 +359,15 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
(key) => filters[key as keyof IIssueFilterOptions] === null
|
(key) => filters[key as keyof IIssueFilterOptions] === null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const areFiltersApplied =
|
||||||
|
Object.keys(filters).length > 0 && nullFilters.length !== Object.keys(filters).length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CreateUpdateViewModal
|
<CreateUpdateViewModal
|
||||||
isOpen={createViewModal !== null}
|
isOpen={createViewModal !== null}
|
||||||
handleClose={() => setCreateViewModal(null)}
|
handleClose={() => setCreateViewModal(null)}
|
||||||
data={createViewModal}
|
preLoadedData={createViewModal}
|
||||||
/>
|
/>
|
||||||
<CreateUpdateIssueModal
|
<CreateUpdateIssueModal
|
||||||
isOpen={createIssueModal && preloadedData?.actionType === "createIssue"}
|
isOpen={createIssueModal && preloadedData?.actionType === "createIssue"}
|
||||||
@ -388,11 +391,15 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
handleClose={() => setTransferIssuesModal(false)}
|
handleClose={() => setTransferIssuesModal(false)}
|
||||||
isOpen={transferIssuesModal}
|
isOpen={transferIssuesModal}
|
||||||
/>
|
/>
|
||||||
<div>
|
{issueView !== "calendar" && (
|
||||||
<div className="flex items-center justify-between gap-2">
|
<>
|
||||||
<FilterList filters={filters} setFilters={setFilters} />
|
<div
|
||||||
{Object.keys(filters).length > 0 &&
|
className={`flex items-center justify-between gap-2 ${
|
||||||
nullFilters.length !== Object.keys(filters).length && (
|
issueView === "list" && areFiltersApplied ? "px-8 mt-6" : "-mt-2"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<FilterList filters={filters} setFilters={setFilters} />
|
||||||
|
{areFiltersApplied && (
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (viewId) {
|
if (viewId) {
|
||||||
@ -413,20 +420,19 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
{viewId ? "Update" : "Save"} view
|
{viewId ? "Update" : "Save"} view
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{areFiltersApplied && (
|
||||||
|
<div className={` ${issueView === "list" ? "mt-4" : "my-4"} border-t`} />
|
||||||
{Object.keys(filters).length > 0 && nullFilters.length !== Object.keys(filters).length && (
|
)}
|
||||||
<div className="mb-5 border-t" />
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<DragDropContext onDragEnd={handleOnDragEnd}>
|
<DragDropContext onDragEnd={handleOnDragEnd}>
|
||||||
<StrictModeDroppable droppableId="trashBox">
|
<StrictModeDroppable droppableId="trashBox">
|
||||||
{(provided, snapshot) => (
|
{(provided, snapshot) => (
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
trashBox ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0"
|
trashBox ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0"
|
||||||
} fixed top-9 right-9 z-20 flex h-28 w-96 flex-col items-center justify-center gap-2 rounded border-2 border-red-500 bg-red-100 p-3 text-xs font-medium italic text-red-500 ${
|
} fixed top-9 right-9 z-30 flex h-28 w-96 flex-col items-center justify-center gap-2 rounded border-2 border-red-500 bg-red-100 p-3 text-xs font-medium italic text-red-500 ${
|
||||||
snapshot.isDraggingOver ? "bg-red-500 text-white" : ""
|
snapshot.isDraggingOver ? "bg-red-500 text-white" : ""
|
||||||
} duration-200`}
|
} duration-200`}
|
||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
@ -434,7 +440,6 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
>
|
>
|
||||||
<TrashIcon className="h-4 w-4" />
|
<TrashIcon className="h-4 w-4" />
|
||||||
Drop issue here to delete
|
Drop issue here to delete
|
||||||
{provided.placeholder}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</StrictModeDroppable>
|
</StrictModeDroppable>
|
||||||
|
@ -36,7 +36,7 @@ export const AllLists: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{groupedByIssues && (
|
{groupedByIssues && (
|
||||||
<div className="flex flex-col space-y-5 gap-4 bg-white">
|
<div className="flex flex-col space-y-5 bg-white">
|
||||||
{Object.keys(groupedByIssues).map((singleGroup) => {
|
{Object.keys(groupedByIssues).map((singleGroup) => {
|
||||||
const currentState =
|
const currentState =
|
||||||
selectedGroup === "state" ? states?.find((s) => s.id === singleGroup) : null;
|
selectedGroup === "state" ? states?.find((s) => s.id === singleGroup) : null;
|
||||||
|
@ -216,9 +216,9 @@ export const SingleListIssue: React.FC<Props> = ({
|
|||||||
</ContextMenu.Item>
|
</ContextMenu.Item>
|
||||||
</a>
|
</a>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
<div className="border-b mx-4 border-gray-300 last:border-b-0">
|
<div className="border-b mx-6 border-gray-300 last:border-b-0">
|
||||||
<div
|
<div
|
||||||
className="flex items-center justify-between gap-2 px-4 py-3"
|
className="flex items-center justify-between gap-2 py-3"
|
||||||
onContextMenu={(e) => {
|
onContextMenu={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setContextMenu(true);
|
setContextMenu(true);
|
||||||
|
@ -152,14 +152,16 @@ export const SingleEstimate: React.FC<Props> = ({
|
|||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
{estimatePoints && estimatePoints.length > 0 ? (
|
{estimatePoints && estimatePoints.length > 0 ? (
|
||||||
<div className="flex gap-2 text-sm text-gray-400">
|
<div className="flex text-sm text-gray-400">
|
||||||
Estimate points(
|
Estimate points (
|
||||||
{estimatePoints.map((point, index) => (
|
<span className="flex gap-1">
|
||||||
<h6 key={point.id}>
|
{estimatePoints.map((point, index) => (
|
||||||
{point.value}
|
<h6 key={point.id}>
|
||||||
{index !== estimatePoints.length - 1 && ","}{" "}
|
{point.value}
|
||||||
</h6>
|
{index !== estimatePoints.length - 1 && ","}{" "}
|
||||||
))}
|
</h6>
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
)
|
)
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
// react-hook-form
|
// react-hook-form
|
||||||
import { UseFormWatch } from "react-hook-form";
|
import { UseFormWatch } from "react-hook-form";
|
||||||
// ui
|
// ui
|
||||||
@ -22,8 +20,6 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const GithubImportUsers: FC<Props> = ({ handleStepChange, users, setUsers, watch }) => {
|
export const GithubImportUsers: FC<Props> = ({ handleStepChange, users, setUsers, watch }) => {
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const isInvalid = users.filter((u) => u.import !== false && u.email === "").length > 0;
|
const isInvalid = users.filter((u) => u.import !== false && u.email === "").length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -44,7 +40,6 @@ export const GithubImportUsers: FC<Props> = ({ handleStepChange, users, setUsers
|
|||||||
index={index}
|
index={index}
|
||||||
users={users}
|
users={users}
|
||||||
setUsers={setUsers}
|
setUsers={setUsers}
|
||||||
project={watch("project")}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,21 +4,20 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import projectService from "services/project.service";
|
import workspaceService from "services/workspace.service";
|
||||||
// ui
|
// ui
|
||||||
import { Avatar, CustomSearchSelect, CustomSelect, Input } from "components/ui";
|
import { Avatar, CustomSearchSelect, CustomSelect, Input } from "components/ui";
|
||||||
// types
|
// types
|
||||||
import { IGithubRepoCollaborator } from "types";
|
import { IGithubRepoCollaborator } from "types";
|
||||||
import { IUserDetails } from "./root";
|
import { IUserDetails } from "./root";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_MEMBERS } from "constants/fetch-keys";
|
import { WORKSPACE_MEMBERS } from "constants/fetch-keys";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
collaborator: IGithubRepoCollaborator;
|
collaborator: IGithubRepoCollaborator;
|
||||||
index: number;
|
index: number;
|
||||||
users: IUserDetails[];
|
users: IUserDetails[];
|
||||||
setUsers: React.Dispatch<React.SetStateAction<IUserDetails[]>>;
|
setUsers: React.Dispatch<React.SetStateAction<IUserDetails[]>>;
|
||||||
project: string | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const importOptions = [
|
const importOptions = [
|
||||||
@ -36,21 +35,13 @@ const importOptions = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const SingleUserSelect: React.FC<Props> = ({
|
export const SingleUserSelect: React.FC<Props> = ({ collaborator, index, users, setUsers }) => {
|
||||||
collaborator,
|
|
||||||
index,
|
|
||||||
users,
|
|
||||||
setUsers,
|
|
||||||
project,
|
|
||||||
}) => {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
const { data: members } = useSWR(
|
const { data: members } = useSWR(
|
||||||
workspaceSlug && project ? PROJECT_MEMBERS(project) : null,
|
workspaceSlug ? WORKSPACE_MEMBERS(workspaceSlug.toString()) : null,
|
||||||
workspaceSlug && project
|
workspaceSlug ? () => workspaceService.workspaceMembers(workspaceSlug.toString()) : null
|
||||||
? () => projectService.projectMembers(workspaceSlug as string, project)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const options =
|
const options =
|
||||||
|
@ -29,6 +29,7 @@ import { addSpaceIfCamelCase } from "helpers/string.helper";
|
|||||||
// types
|
// types
|
||||||
import { IIssueComment, IIssueLabels } from "types";
|
import { IIssueComment, IIssueLabels } from "types";
|
||||||
import { PROJECT_ISSUES_ACTIVITY, PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
|
import { PROJECT_ISSUES_ACTIVITY, PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
|
||||||
|
import useEstimateOption from "hooks/use-estimate-option";
|
||||||
|
|
||||||
const activityDetails: {
|
const activityDetails: {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
@ -56,6 +57,10 @@ const activityDetails: {
|
|||||||
message: "set the cycle to",
|
message: "set the cycle to",
|
||||||
icon: <CyclesIcon height="12" width="12" color="#6b7280" />,
|
icon: <CyclesIcon height="12" width="12" color="#6b7280" />,
|
||||||
},
|
},
|
||||||
|
estimate_point: {
|
||||||
|
message: "set the estimate point to",
|
||||||
|
icon: <PlayIcon className="h-3 w-3 text-gray-500 -rotate-90" aria-hidden="true" />,
|
||||||
|
},
|
||||||
labels: {
|
labels: {
|
||||||
icon: <TagIcon height="12" width="12" color="#6b7280" />,
|
icon: <TagIcon height="12" width="12" color="#6b7280" />,
|
||||||
},
|
},
|
||||||
@ -107,6 +112,8 @@ export const IssueActivitySection: React.FC<Props> = () => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, issueId } = router.query;
|
const { workspaceSlug, projectId, issueId } = router.query;
|
||||||
|
|
||||||
|
const { isEstimateActive, estimatePoints } = useEstimateOption();
|
||||||
|
|
||||||
const { data: issueActivities, mutate: mutateIssueActivities } = useSWR(
|
const { data: issueActivities, mutate: mutateIssueActivities } = useSWR(
|
||||||
workspaceSlug && projectId && issueId ? PROJECT_ISSUES_ACTIVITY(issueId as string) : null,
|
workspaceSlug && projectId && issueId ? PROJECT_ISSUES_ACTIVITY(issueId as string) : null,
|
||||||
workspaceSlug && projectId && issueId
|
workspaceSlug && projectId && issueId
|
||||||
@ -278,8 +285,14 @@ export const IssueActivitySection: React.FC<Props> = () => {
|
|||||||
value = "attachment";
|
value = "attachment";
|
||||||
} else if (activityItem.field === "link") {
|
} else if (activityItem.field === "link") {
|
||||||
value = "link";
|
value = "link";
|
||||||
} else if (activityItem.field === "estimate") {
|
} else if (activityItem.field === "estimate_point") {
|
||||||
value = "estimate";
|
value = activityItem.new_value
|
||||||
|
? isEstimateActive
|
||||||
|
? estimatePoints.find((e) => e.key === parseInt(activityItem.new_value ?? "", 10))
|
||||||
|
?.value
|
||||||
|
: activityItem.new_value +
|
||||||
|
` Point${parseInt(activityItem.new_value ?? "", 10) > 1 ? "s" : ""}`
|
||||||
|
: "None";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("field" in activityItem && activityItem.field !== "updated_by") {
|
if ("field" in activityItem && activityItem.field !== "updated_by") {
|
||||||
|
@ -46,9 +46,9 @@ const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor
|
|||||||
const defaultValues: Partial<IIssue> = {
|
const defaultValues: Partial<IIssue> = {
|
||||||
project: "",
|
project: "",
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: { type: "doc", content: [] },
|
||||||
description_html: "<p></p>",
|
description_html: "<p></p>",
|
||||||
estimate_point: 0,
|
estimate_point: null,
|
||||||
state: "",
|
state: "",
|
||||||
cycle: null,
|
cycle: null,
|
||||||
priority: null,
|
priority: null,
|
||||||
@ -137,7 +137,7 @@ export const IssueForm: FC<IssueFormProps> = ({
|
|||||||
setValue("description_html", `${watch("description_html")}<p>${response}</p>`);
|
setValue("description_html", `${watch("description_html")}<p>${response}</p>`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handelAutoGenerateDescription = async () => {
|
const handleAutoGenerateDescription = async () => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
setIAmFeelingLucky(true);
|
setIAmFeelingLucky(true);
|
||||||
@ -301,7 +301,7 @@ export const IssueForm: FC<IssueFormProps> = ({
|
|||||||
className={`flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-gray-100 ${
|
className={`flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-gray-100 ${
|
||||||
iAmFeelingLucky ? "cursor-wait" : ""
|
iAmFeelingLucky ? "cursor-wait" : ""
|
||||||
}`}
|
}`}
|
||||||
onClick={handelAutoGenerateDescription}
|
onClick={handleAutoGenerateDescription}
|
||||||
disabled={iAmFeelingLucky}
|
disabled={iAmFeelingLucky}
|
||||||
>
|
>
|
||||||
{iAmFeelingLucky ? (
|
{iAmFeelingLucky ? (
|
||||||
|
@ -29,6 +29,8 @@ import {
|
|||||||
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
||||||
CYCLE_ISSUES_WITH_PARAMS,
|
CYCLE_ISSUES_WITH_PARAMS,
|
||||||
MODULE_ISSUES_WITH_PARAMS,
|
MODULE_ISSUES_WITH_PARAMS,
|
||||||
|
CYCLE_DETAILS,
|
||||||
|
MODULE_DETAILS,
|
||||||
} from "constants/fetch-keys";
|
} from "constants/fetch-keys";
|
||||||
|
|
||||||
export interface IssuesModalProps {
|
export interface IssuesModalProps {
|
||||||
@ -101,7 +103,10 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
issues: [issueId],
|
issues: [issueId],
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId, params));
|
if (cycleId) {
|
||||||
|
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId, params));
|
||||||
|
mutate(CYCLE_DETAILS(cycleId as string));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -113,7 +118,10 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
issues: [issueId],
|
issues: [issueId],
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
|
if (moduleId) {
|
||||||
|
mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
|
||||||
|
mutate(MODULE_DETAILS(moduleId as string));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ import { useRouter } from "next/router";
|
|||||||
|
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
|
|
||||||
|
// hooks
|
||||||
|
import useToast from "hooks/use-toast";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issues.service";
|
||||||
// components
|
// components
|
||||||
@ -13,16 +15,17 @@ import {
|
|||||||
ViewPrioritySelect,
|
ViewPrioritySelect,
|
||||||
ViewStateSelect,
|
ViewStateSelect,
|
||||||
} from "components/issues/view-select";
|
} from "components/issues/view-select";
|
||||||
|
// icon
|
||||||
|
import { LinkIcon, PaperClipIcon } from "@heroicons/react/24/outline";
|
||||||
// ui
|
// ui
|
||||||
import { AssigneesList } from "components/ui/avatar";
|
import { AssigneesList } from "components/ui/avatar";
|
||||||
import { CustomMenu, Tooltip } from "components/ui";
|
import { CustomMenu, Tooltip } from "components/ui";
|
||||||
// types
|
// types
|
||||||
import { IIssue, Properties } from "types";
|
import { IIssue, Properties } from "types";
|
||||||
|
// helper
|
||||||
|
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { USER_ISSUE } from "constants/fetch-keys";
|
import { USER_ISSUE } from "constants/fetch-keys";
|
||||||
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
|
||||||
import useToast from "hooks/use-toast";
|
|
||||||
import { LinkIcon } from "@heroicons/react/24/outline";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
@ -79,8 +82,8 @@ export const MyIssuesListItem: React.FC<Props> = ({ issue, properties, projectId
|
|||||||
const isNotAllowed = false;
|
const isNotAllowed = false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-b border-gray-300 last:border-b-0">
|
<div className="border-b border-gray-300 last:border-b-0 mx-6">
|
||||||
<div key={issue.id} className="flex items-center justify-between gap-2 px-4 py-3">
|
<div key={issue.id} className="flex items-center justify-between gap-2 py-3">
|
||||||
<Link href={`/${workspaceSlug}/projects/${issue?.project_detail?.id}/issues/${issue.id}`}>
|
<Link href={`/${workspaceSlug}/projects/${issue?.project_detail?.id}/issues/${issue.id}`}>
|
||||||
<a className="group relative flex items-center gap-2">
|
<a className="group relative flex items-center gap-2">
|
||||||
{properties?.key && (
|
{properties?.key && (
|
||||||
@ -167,6 +170,26 @@ export const MyIssuesListItem: React.FC<Props> = ({ issue, properties, projectId
|
|||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
{properties.link && (
|
||||||
|
<div className="flex items-center rounded-md shadow-sm px-2.5 py-1 cursor-default text-xs border border-gray-200">
|
||||||
|
<Tooltip tooltipHeading="Link" tooltipContent={`${issue.link_count}`}>
|
||||||
|
<div className="flex items-center gap-1 text-gray-500">
|
||||||
|
<LinkIcon className="h-3.5 w-3.5 text-gray-500" />
|
||||||
|
{issue.link_count}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{properties.attachment_count && (
|
||||||
|
<div className="flex items-center rounded-md shadow-sm px-2.5 py-1 cursor-default text-xs border border-gray-200">
|
||||||
|
<Tooltip tooltipHeading="Attachment" tooltipContent={`${issue.attachment_count}`}>
|
||||||
|
<div className="flex items-center gap-1 text-gray-500">
|
||||||
|
<PaperClipIcon className="h-3.5 w-3.5 text-gray-500 -rotate-45" />
|
||||||
|
{issue.attachment_count}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<CustomMenu width="auto" ellipsis>
|
<CustomMenu width="auto" ellipsis>
|
||||||
<CustomMenu.MenuItem onClick={handleCopyText}>
|
<CustomMenu.MenuItem onClick={handleCopyText}>
|
||||||
<span className="flex items-center justify-start gap-2">
|
<span className="flex items-center justify-start gap-2">
|
||||||
|
@ -8,8 +8,8 @@ import { PlayIcon } from "@heroicons/react/24/outline";
|
|||||||
import useEstimateOption from "hooks/use-estimate-option";
|
import useEstimateOption from "hooks/use-estimate-option";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value: number;
|
value: number | null;
|
||||||
onChange: (value: number) => void;
|
onChange: (value: number | null) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueEstimateSelect: React.FC<Props> = ({ value, onChange }) => {
|
export const IssueEstimateSelect: React.FC<Props> = ({ value, onChange }) => {
|
||||||
@ -24,18 +24,26 @@ export const IssueEstimateSelect: React.FC<Props> = ({ value, onChange }) => {
|
|||||||
<div className="flex items-center gap-2 text-xs">
|
<div className="flex items-center gap-2 text-xs">
|
||||||
<PlayIcon className="h-4 w-4 text-gray-500 -rotate-90" />
|
<PlayIcon className="h-4 w-4 text-gray-500 -rotate-90" />
|
||||||
<span className={`${value ? "text-gray-600" : "text-gray-500"}`}>
|
<span className={`${value ? "text-gray-600" : "text-gray-500"}`}>
|
||||||
{estimatePoints?.find((e) => e.key === value)?.value ?? "Estimate points"}
|
{estimatePoints?.find((e) => e.key === value)?.value ?? "Estimate"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
position="right"
|
position="right"
|
||||||
width="w-full min-w-[6rem]"
|
width="w-full min-w-[8rem]"
|
||||||
noChevron
|
noChevron
|
||||||
>
|
>
|
||||||
|
<CustomSelect.Option value={null}>
|
||||||
|
<>
|
||||||
|
<span>
|
||||||
|
<PlayIcon className="h-4 w-4 -rotate-90" />
|
||||||
|
</span>
|
||||||
|
None
|
||||||
|
</>
|
||||||
|
</CustomSelect.Option>
|
||||||
{estimatePoints &&
|
{estimatePoints &&
|
||||||
estimatePoints.map((point) => (
|
estimatePoints.map((point) => (
|
||||||
<CustomSelect.Option className="w-full " key={point.key} value={point.key}>
|
<CustomSelect.Option key={point.key} value={point.key}>
|
||||||
<>
|
<>
|
||||||
<span>
|
<span>
|
||||||
<PlayIcon className="h-4 w-4 -rotate-90" />
|
<PlayIcon className="h-4 w-4 -rotate-90" />
|
||||||
|
@ -48,6 +48,7 @@ export const IssueStateSelect: React.FC<Props> = ({ setIsOpen, value, onChange,
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const selectedOption = states?.find((s) => s.id === value);
|
const selectedOption = states?.find((s) => s.id === value);
|
||||||
|
const currentDefaultState = states.find((s) => s.default);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomSearchSelect
|
<CustomSearchSelect
|
||||||
@ -58,11 +59,12 @@ export const IssueStateSelect: React.FC<Props> = ({ setIsOpen, value, onChange,
|
|||||||
<div className="flex items-center gap-2 text-gray-500">
|
<div className="flex items-center gap-2 text-gray-500">
|
||||||
{selectedOption ? (
|
{selectedOption ? (
|
||||||
getStateGroupIcon(selectedOption.group, "16", "16", selectedOption.color)
|
getStateGroupIcon(selectedOption.group, "16", "16", selectedOption.color)
|
||||||
|
) : currentDefaultState ? (
|
||||||
|
getStateGroupIcon(currentDefaultState.group, "16", "16", currentDefaultState.color)
|
||||||
) : (
|
) : (
|
||||||
<Squares2X2Icon className="h-4 w-4" />
|
<Squares2X2Icon className="h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
|
{selectedOption?.name ? selectedOption.name : currentDefaultState?.name ?? "State"}
|
||||||
{selectedOption?.name ?? "State"}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
footerOption={
|
footerOption={
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
// hooks
|
||||||
|
import useEstimateOption from "hooks/use-estimate-option";
|
||||||
// ui
|
// ui
|
||||||
import { CustomSelect } from "components/ui";
|
import { CustomSelect } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
import { BanknotesIcon, PlayIcon } from "@heroicons/react/24/outline";
|
import { PlayIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import { UserAuth } from "types";
|
import { UserAuth } from "types";
|
||||||
import useEstimateOption from "hooks/use-estimate-option";
|
|
||||||
// constants
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value: number;
|
value: number | null;
|
||||||
onChange: (val: number) => void;
|
onChange: (val: number | null) => void;
|
||||||
userAuth: UserAuth;
|
userAuth: UserAuth;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ export const SidebarEstimateSelect: React.FC<Props> = ({ value, onChange, userAu
|
|||||||
<div className="flex items-center gap-2 text-xs">
|
<div className="flex items-center gap-2 text-xs">
|
||||||
<PlayIcon className="h-4 w-4 text-gray-700 -rotate-90" />
|
<PlayIcon className="h-4 w-4 text-gray-700 -rotate-90" />
|
||||||
<span className={`${value ? "text-gray-600" : "text-gray-500"}`}>
|
<span className={`${value ? "text-gray-600" : "text-gray-500"}`}>
|
||||||
{estimatePoints?.find((e) => e.key === value)?.value ?? "Estimate points"}
|
{estimatePoints?.find((e) => e.key === value)?.value ?? "Estimate"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -44,9 +44,17 @@ export const SidebarEstimateSelect: React.FC<Props> = ({ value, onChange, userAu
|
|||||||
width="w-full"
|
width="w-full"
|
||||||
disabled={isNotAllowed}
|
disabled={isNotAllowed}
|
||||||
>
|
>
|
||||||
|
<CustomSelect.Option value={null}>
|
||||||
|
<>
|
||||||
|
<span>
|
||||||
|
<PlayIcon className="h-4 w-4 -rotate-90" />
|
||||||
|
</span>
|
||||||
|
None
|
||||||
|
</>
|
||||||
|
</CustomSelect.Option>
|
||||||
{estimatePoints &&
|
{estimatePoints &&
|
||||||
estimatePoints.map((point) => (
|
estimatePoints.map((point) => (
|
||||||
<CustomSelect.Option className="w-full " key={point.key} value={point.key}>
|
<CustomSelect.Option key={point.key} value={point.key}>
|
||||||
<>
|
<>
|
||||||
<span>
|
<span>
|
||||||
<PlayIcon className="h-4 w-4 -rotate-90" />
|
<PlayIcon className="h-4 w-4 -rotate-90" />
|
||||||
|
@ -294,7 +294,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
render={({ field: { value } }) => (
|
render={({ field: { value } }) => (
|
||||||
<SidebarEstimateSelect
|
<SidebarEstimateSelect
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(val: number) => submitChanges({ estimate_point: val })}
|
onChange={(val: number | null) => submitChanges({ estimate_point: val })}
|
||||||
userAuth={memberRole}
|
userAuth={memberRole}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -75,8 +75,8 @@ export const ViewAssigneeSelect: React.FC<Props> = ({
|
|||||||
|
|
||||||
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
||||||
{
|
{
|
||||||
workspaceSlug: issue.workspace_detail.slug,
|
workspaceSlug,
|
||||||
workspaceId: issue.workspace_detail.id,
|
workspaceId: issue.workspace,
|
||||||
projectId: issue.project_detail.id,
|
projectId: issue.project_detail.id,
|
||||||
projectIdentifier: issue.project_detail.identifier,
|
projectIdentifier: issue.project_detail.identifier,
|
||||||
projectName: issue.project_detail.name,
|
projectName: issue.project_detail.name,
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
// ui
|
// ui
|
||||||
import { CustomDatePicker, Tooltip } from "components/ui";
|
import { CustomDatePicker, Tooltip } from "components/ui";
|
||||||
// helpers
|
// helpers
|
||||||
@ -13,41 +15,46 @@ type Props = {
|
|||||||
isNotAllowed: boolean;
|
isNotAllowed: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ViewDueDateSelect: React.FC<Props> = ({ issue, partialUpdateIssue, isNotAllowed }) => (
|
export const ViewDueDateSelect: React.FC<Props> = ({ issue, partialUpdateIssue, isNotAllowed }) => {
|
||||||
<Tooltip tooltipHeading="Due Date" tooltipContent={issue.target_date ?? "N/A"}>
|
const router = useRouter();
|
||||||
<div
|
const { workspaceSlug } = router.query;
|
||||||
className={`group relative ${
|
|
||||||
issue.target_date === null
|
return (
|
||||||
? ""
|
<Tooltip tooltipHeading="Due Date" tooltipContent={issue.target_date ?? "N/A"}>
|
||||||
: issue.target_date < new Date().toISOString()
|
<div
|
||||||
? "text-red-600"
|
className={`group relative ${
|
||||||
: findHowManyDaysLeft(issue.target_date) <= 3 && "text-orange-400"
|
issue.target_date === null
|
||||||
}`}
|
? ""
|
||||||
>
|
: issue.target_date < new Date().toISOString()
|
||||||
<CustomDatePicker
|
? "text-red-600"
|
||||||
placeholder="N/A"
|
: findHowManyDaysLeft(issue.target_date) <= 3 && "text-orange-400"
|
||||||
value={issue?.target_date}
|
}`}
|
||||||
onChange={(val) => {
|
>
|
||||||
partialUpdateIssue({
|
<CustomDatePicker
|
||||||
target_date: val,
|
placeholder="N/A"
|
||||||
priority: issue.priority,
|
value={issue?.target_date}
|
||||||
state: issue.state,
|
onChange={(val) => {
|
||||||
});
|
partialUpdateIssue({
|
||||||
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
target_date: val,
|
||||||
{
|
priority: issue.priority,
|
||||||
workspaceSlug: issue.workspace_detail.slug,
|
state: issue.state,
|
||||||
workspaceId: issue.workspace_detail.id,
|
});
|
||||||
projectId: issue.project_detail.id,
|
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
||||||
projectIdentifier: issue.project_detail.identifier,
|
{
|
||||||
projectName: issue.project_detail.name,
|
workspaceSlug,
|
||||||
issueId: issue.id,
|
workspaceId: issue.workspace,
|
||||||
},
|
projectId: issue.project_detail.id,
|
||||||
"ISSUE_PROPERTY_UPDATE_DUE_DATE"
|
projectIdentifier: issue.project_detail.identifier,
|
||||||
);
|
projectName: issue.project_detail.name,
|
||||||
}}
|
issueId: issue.id,
|
||||||
className={issue?.target_date ? "w-[6.5rem]" : "w-[3rem] text-center"}
|
},
|
||||||
disabled={isNotAllowed}
|
"ISSUE_PROPERTY_UPDATE_DUE_DATE"
|
||||||
/>
|
);
|
||||||
</div>
|
}}
|
||||||
</Tooltip>
|
className={issue?.target_date ? "w-[6.5rem]" : "w-[3rem] text-center"}
|
||||||
);
|
disabled={isNotAllowed}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
|
// services
|
||||||
|
import trackEventServices from "services/track-event.service";
|
||||||
|
// hooks
|
||||||
|
import useEstimateOption from "hooks/use-estimate-option";
|
||||||
// ui
|
// ui
|
||||||
import { CustomSelect, Tooltip } from "components/ui";
|
import { CustomSelect, Tooltip } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
import { getPriorityIcon } from "components/icons/priority-icon";
|
import { PlayIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue } from "types";
|
||||||
// constants
|
|
||||||
import { PRIORITIES } from "constants/project";
|
|
||||||
// services
|
|
||||||
import trackEventServices from "services/track-event.service";
|
|
||||||
import useEstimateOption from "hooks/use-estimate-option";
|
|
||||||
import { PlayIcon } from "@heroicons/react/24/outline";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
@ -28,6 +28,9 @@ export const ViewEstimateSelect: React.FC<Props> = ({
|
|||||||
selfPositioned = false,
|
selfPositioned = false,
|
||||||
isNotAllowed,
|
isNotAllowed,
|
||||||
}) => {
|
}) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
const { isEstimateActive, estimatePoints } = useEstimateOption(issue.estimate_point);
|
const { isEstimateActive, estimatePoints } = useEstimateOption(issue.estimate_point);
|
||||||
|
|
||||||
const estimateValue = estimatePoints?.find((e) => e.key === issue.estimate_point)?.value;
|
const estimateValue = estimatePoints?.find((e) => e.key === issue.estimate_point)?.value;
|
||||||
@ -41,8 +44,8 @@ export const ViewEstimateSelect: React.FC<Props> = ({
|
|||||||
partialUpdateIssue({ estimate_point: val });
|
partialUpdateIssue({ estimate_point: val });
|
||||||
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
||||||
{
|
{
|
||||||
workspaceSlug: issue.workspace_detail.slug,
|
workspaceSlug,
|
||||||
workspaceId: issue.workspace_detail.id,
|
workspaceId: issue.workspace,
|
||||||
projectId: issue.project_detail.id,
|
projectId: issue.project_detail.id,
|
||||||
projectIdentifier: issue.project_detail.identifier,
|
projectIdentifier: issue.project_detail.identifier,
|
||||||
projectName: issue.project_detail.name,
|
projectName: issue.project_detail.name,
|
||||||
@ -55,7 +58,7 @@ export const ViewEstimateSelect: React.FC<Props> = ({
|
|||||||
<Tooltip tooltipHeading="Estimate" tooltipContent={estimateValue}>
|
<Tooltip tooltipHeading="Estimate" tooltipContent={estimateValue}>
|
||||||
<div className="flex items-center gap-1 text-gray-500">
|
<div className="flex items-center gap-1 text-gray-500">
|
||||||
<PlayIcon className="h-3.5 w-3.5 -rotate-90" />
|
<PlayIcon className="h-3.5 w-3.5 -rotate-90" />
|
||||||
{estimateValue}
|
{estimateValue ?? "Estimate"}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
@ -64,11 +67,24 @@ export const ViewEstimateSelect: React.FC<Props> = ({
|
|||||||
disabled={isNotAllowed}
|
disabled={isNotAllowed}
|
||||||
position={position}
|
position={position}
|
||||||
selfPositioned={selfPositioned}
|
selfPositioned={selfPositioned}
|
||||||
width="w-full min-w-[6rem]"
|
width="w-full min-w-[8rem]"
|
||||||
>
|
>
|
||||||
|
<CustomSelect.Option value={null}>
|
||||||
|
<>
|
||||||
|
<span>
|
||||||
|
<PlayIcon className="h-4 w-4 -rotate-90" />
|
||||||
|
</span>
|
||||||
|
None
|
||||||
|
</>
|
||||||
|
</CustomSelect.Option>
|
||||||
{estimatePoints?.map((estimate) => (
|
{estimatePoints?.map((estimate) => (
|
||||||
<CustomSelect.Option key={estimate.id} value={estimate.key} className="capitalize">
|
<CustomSelect.Option key={estimate.id} value={estimate.key}>
|
||||||
<>{estimate.value}</>
|
<>
|
||||||
|
<span>
|
||||||
|
<PlayIcon className="h-4 w-4 -rotate-90" />
|
||||||
|
</span>
|
||||||
|
{estimate.value}
|
||||||
|
</>
|
||||||
</CustomSelect.Option>
|
</CustomSelect.Option>
|
||||||
))}
|
))}
|
||||||
</CustomSelect>
|
</CustomSelect>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
// ui
|
// ui
|
||||||
import { CustomSelect, Tooltip } from "components/ui";
|
import { CustomSelect, Tooltip } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
@ -25,63 +27,68 @@ export const ViewPrioritySelect: React.FC<Props> = ({
|
|||||||
position = "left",
|
position = "left",
|
||||||
selfPositioned = false,
|
selfPositioned = false,
|
||||||
isNotAllowed,
|
isNotAllowed,
|
||||||
}) => (
|
}) => {
|
||||||
<CustomSelect
|
const router = useRouter();
|
||||||
value={issue.priority}
|
const { workspaceSlug } = router.query;
|
||||||
onChange={(data: string) => {
|
|
||||||
partialUpdateIssue({ priority: data });
|
return (
|
||||||
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
<CustomSelect
|
||||||
{
|
value={issue.priority}
|
||||||
workspaceSlug: issue.workspace_detail.slug,
|
onChange={(data: string) => {
|
||||||
workspaceId: issue.workspace_detail.id,
|
partialUpdateIssue({ priority: data });
|
||||||
projectId: issue.project_detail.id,
|
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
||||||
projectIdentifier: issue.project_detail.identifier,
|
{
|
||||||
projectName: issue.project_detail.name,
|
workspaceSlug,
|
||||||
issueId: issue.id,
|
workspaceId: issue.workspace,
|
||||||
},
|
projectId: issue.project_detail.id,
|
||||||
"ISSUE_PROPERTY_UPDATE_PRIORITY"
|
projectIdentifier: issue.project_detail.identifier,
|
||||||
);
|
projectName: issue.project_detail.name,
|
||||||
}}
|
issueId: issue.id,
|
||||||
maxHeight="md"
|
},
|
||||||
customButton={
|
"ISSUE_PROPERTY_UPDATE_PRIORITY"
|
||||||
<button
|
);
|
||||||
type="button"
|
}}
|
||||||
className={`grid h-6 w-6 place-items-center rounded ${
|
maxHeight="md"
|
||||||
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
|
customButton={
|
||||||
} items-center shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${
|
<button
|
||||||
issue.priority === "urgent"
|
type="button"
|
||||||
? "bg-red-100 text-red-600 hover:bg-red-100"
|
className={`grid h-6 w-6 place-items-center rounded ${
|
||||||
: issue.priority === "high"
|
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
|
||||||
? "bg-orange-100 text-orange-500 hover:bg-orange-100"
|
} items-center shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${
|
||||||
: issue.priority === "medium"
|
issue.priority === "urgent"
|
||||||
? "bg-yellow-100 text-yellow-500 hover:bg-yellow-100"
|
? "bg-red-100 text-red-600 hover:bg-red-100"
|
||||||
: issue.priority === "low"
|
: issue.priority === "high"
|
||||||
? "bg-green-100 text-green-500 hover:bg-green-100"
|
? "bg-orange-100 text-orange-500 hover:bg-orange-100"
|
||||||
: "bg-gray-100"
|
: issue.priority === "medium"
|
||||||
} border-none`}
|
? "bg-yellow-100 text-yellow-500 hover:bg-yellow-100"
|
||||||
>
|
: issue.priority === "low"
|
||||||
<Tooltip tooltipHeading="Priority" tooltipContent={issue.priority ?? "None"}>
|
? "bg-green-100 text-green-500 hover:bg-green-100"
|
||||||
<span>
|
: "bg-gray-100"
|
||||||
{getPriorityIcon(
|
} border-none`}
|
||||||
issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None",
|
>
|
||||||
"text-sm"
|
<Tooltip tooltipHeading="Priority" tooltipContent={issue.priority ?? "None"}>
|
||||||
)}
|
<span>
|
||||||
</span>
|
{getPriorityIcon(
|
||||||
</Tooltip>
|
issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None",
|
||||||
</button>
|
"text-sm"
|
||||||
}
|
)}
|
||||||
noChevron
|
</span>
|
||||||
disabled={isNotAllowed}
|
</Tooltip>
|
||||||
position={position}
|
</button>
|
||||||
selfPositioned={selfPositioned}
|
}
|
||||||
>
|
noChevron
|
||||||
{PRIORITIES?.map((priority) => (
|
disabled={isNotAllowed}
|
||||||
<CustomSelect.Option key={priority} value={priority} className="capitalize">
|
position={position}
|
||||||
<>
|
selfPositioned={selfPositioned}
|
||||||
{getPriorityIcon(priority, "text-sm")}
|
>
|
||||||
{priority ?? "None"}
|
{PRIORITIES?.map((priority) => (
|
||||||
</>
|
<CustomSelect.Option key={priority} value={priority} className="capitalize">
|
||||||
</CustomSelect.Option>
|
<>
|
||||||
))}
|
{getPriorityIcon(priority, "text-sm")}
|
||||||
</CustomSelect>
|
{priority ?? "None"}
|
||||||
);
|
</>
|
||||||
|
</CustomSelect.Option>
|
||||||
|
))}
|
||||||
|
</CustomSelect>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -67,8 +67,8 @@ export const ViewStateSelect: React.FC<Props> = ({
|
|||||||
});
|
});
|
||||||
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
||||||
{
|
{
|
||||||
workspaceSlug: issue.workspace_detail.slug,
|
workspaceSlug,
|
||||||
workspaceId: issue.workspace_detail.id,
|
workspaceId: issue.workspace,
|
||||||
projectId: issue.project_detail.id,
|
projectId: issue.project_detail.id,
|
||||||
projectIdentifier: issue.project_detail.identifier,
|
projectIdentifier: issue.project_detail.identifier,
|
||||||
projectName: issue.project_detail.name,
|
projectName: issue.project_detail.name,
|
||||||
|
@ -32,7 +32,8 @@ type Props = {
|
|||||||
|
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
name: "",
|
name: "",
|
||||||
description: "<p></p>",
|
description: { type: "doc", content: [] },
|
||||||
|
description_html: "<p></p>",
|
||||||
};
|
};
|
||||||
|
|
||||||
const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), {
|
const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), {
|
||||||
@ -167,7 +168,7 @@ export const CreateUpdateBlockInline: React.FC<Props> = ({
|
|||||||
});
|
});
|
||||||
else {
|
else {
|
||||||
setValue("description", {});
|
setValue("description", {});
|
||||||
setValue("description_html", `${watch("description_html")}<p>${res.response}</p>`);
|
setValue("description_html", `${watch("description_html") ?? ""}<p>${res.response}</p>`);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
@ -199,9 +199,11 @@ const RemirrorRichTextEditor: FC<IRemirrorRichTextEditor> = (props) => {
|
|||||||
onBlur(jsonValue, htmlValue);
|
onBlur(jsonValue, htmlValue);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(!value || value === "" || value?.content?.[0]?.content === undefined) && placeholder && (
|
{(!value || value === "" || value?.content?.[0]?.content === undefined) &&
|
||||||
<p className="absolute pointer-events-none top-4 left-4 text-gray-300">{placeholder}</p>
|
!(typeof value === "string" && value.includes("<")) &&
|
||||||
)}
|
placeholder && (
|
||||||
|
<p className="absolute pointer-events-none top-4 left-4 text-gray-300">{placeholder}</p>
|
||||||
|
)}
|
||||||
<EditorComponent />
|
<EditorComponent />
|
||||||
|
|
||||||
{imageLoader && (
|
{imageLoader && (
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
// ui
|
// ui
|
||||||
import { Input, PrimaryButton, SecondaryButton, TextArea } from "components/ui";
|
import { Input, PrimaryButton, SecondaryButton, TextArea } from "components/ui";
|
||||||
@ -16,6 +15,7 @@ type Props = {
|
|||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
status: boolean;
|
status: boolean;
|
||||||
data?: IView | null;
|
data?: IView | null;
|
||||||
|
preLoadedData?: Partial<IView> | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultValues: Partial<IView> = {
|
const defaultValues: Partial<IView> = {
|
||||||
@ -23,7 +23,13 @@ const defaultValues: Partial<IView> = {
|
|||||||
description: "",
|
description: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ViewForm: React.FC<Props> = ({ handleFormSubmit, handleClose, status, data }) => {
|
export const ViewForm: React.FC<Props> = ({
|
||||||
|
handleFormSubmit,
|
||||||
|
handleClose,
|
||||||
|
status,
|
||||||
|
data,
|
||||||
|
preLoadedData,
|
||||||
|
}) => {
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
@ -47,9 +53,10 @@ export const ViewForm: React.FC<Props> = ({ handleFormSubmit, handleClose, statu
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reset({
|
reset({
|
||||||
...defaultValues,
|
...defaultValues,
|
||||||
|
...preLoadedData,
|
||||||
...data,
|
...data,
|
||||||
});
|
});
|
||||||
}, [data, reset]);
|
}, [data, preLoadedData, reset]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status && data) {
|
if (status && data) {
|
||||||
|
@ -21,9 +21,10 @@ type Props = {
|
|||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
data?: IView | null;
|
data?: IView | null;
|
||||||
|
preLoadedData?: Partial<IView> | null ;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateUpdateViewModal: React.FC<Props> = ({ isOpen, handleClose, data }) => {
|
export const CreateUpdateViewModal: React.FC<Props> = ({ isOpen, handleClose, data, preLoadedData }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
@ -133,6 +134,7 @@ export const CreateUpdateViewModal: React.FC<Props> = ({ isOpen, handleClose, da
|
|||||||
handleClose={handleClose}
|
handleClose={handleClose}
|
||||||
status={data ? true : false}
|
status={data ? true : false}
|
||||||
data={data}
|
data={data}
|
||||||
|
preLoadedData={preLoadedData}
|
||||||
/>
|
/>
|
||||||
</Dialog.Panel>
|
</Dialog.Panel>
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
@ -439,12 +439,11 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> =
|
|||||||
payload: {
|
payload: {
|
||||||
...myViewProps?.view_props,
|
...myViewProps?.view_props,
|
||||||
filters: {
|
filters: {
|
||||||
...myViewProps?.view_props?.filters,
|
...(viewId ? viewDetails?.query_data : myViewProps?.view_props?.filters),
|
||||||
...viewDetails?.query_data,
|
|
||||||
} as any,
|
} as any,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, [myViewProps, viewDetails]);
|
}, [myViewProps, viewDetails, viewId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<issueViewContext.Provider
|
<issueViewContext.Provider
|
||||||
|
@ -13,7 +13,7 @@ import { orderArrayBy } from "helpers/array.helper";
|
|||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { ESTIMATE_POINTS_LIST } from "constants/fetch-keys";
|
import { ESTIMATE_POINTS_LIST } from "constants/fetch-keys";
|
||||||
|
|
||||||
const useEstimateOption = (estimateKey?: number) => {
|
const useEstimateOption = (estimateKey?: number | null) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
|
|
||||||
const getValueFromLocalStorage = (key: string, defaultValue: any) => {
|
const getValueFromLocalStorage = (key: string, defaultValue: any) => {
|
||||||
|
if (typeof window === undefined || typeof window === "undefined") return defaultValue;
|
||||||
try {
|
try {
|
||||||
const item = window.localStorage.getItem(key);
|
const item = window.localStorage.getItem(key);
|
||||||
return item ? JSON.parse(item) : defaultValue;
|
return item ? JSON.parse(item) : defaultValue;
|
||||||
|
@ -46,7 +46,7 @@ const WorkspacePage: NextPage = () => {
|
|||||||
// style={{ background: "linear-gradient(90deg, #8e2de2 0%, #4a00e0 100%)" }}
|
// style={{ background: "linear-gradient(90deg, #8e2de2 0%, #4a00e0 100%)" }}
|
||||||
>
|
>
|
||||||
<p className="font-semibold">
|
<p className="font-semibold">
|
||||||
Plane is a open source application, to support us you can star us on GitHub!
|
Plane is open source, support us by staring us on GitHub.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{/* <a href="#" target="_blank" rel="noopener noreferrer">
|
{/* <a href="#" target="_blank" rel="noopener noreferrer">
|
||||||
|
@ -43,6 +43,7 @@ const MyIssuesPage: NextPage = () => {
|
|||||||
<BreadcrumbItem title="My Issues" />
|
<BreadcrumbItem title="My Issues" />
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
}
|
}
|
||||||
|
noPadding
|
||||||
right={
|
right={
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{myIssues && myIssues.length > 0 && (
|
{myIssues && myIssues.length > 0 && (
|
||||||
@ -72,20 +73,24 @@ const MyIssuesPage: NextPage = () => {
|
|||||||
<div className="relative flex flex-col gap-1">
|
<div className="relative flex flex-col gap-1">
|
||||||
<h4 className="text-base text-gray-600">Properties</h4>
|
<h4 className="text-base text-gray-600">Properties</h4>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{Object.keys(properties).map((key) => (
|
{Object.keys(properties).map((key) => {
|
||||||
<button
|
if (key === "estimate") return null;
|
||||||
key={key}
|
|
||||||
type="button"
|
return (
|
||||||
className={`rounded border border-theme px-2 py-1 text-xs capitalize ${
|
<button
|
||||||
properties[key as keyof Properties]
|
key={key}
|
||||||
? "border-theme bg-theme text-white"
|
type="button"
|
||||||
: ""
|
className={`rounded border border-theme px-2 py-1 text-xs capitalize ${
|
||||||
}`}
|
properties[key as keyof Properties]
|
||||||
onClick={() => setProperties(key as keyof Properties)}
|
? "border-theme bg-theme text-white"
|
||||||
>
|
: ""
|
||||||
{key === "key" ? "ID" : replaceUnderscoreIfSnakeCase(key)}
|
}`}
|
||||||
</button>
|
onClick={() => setProperties(key as keyof Properties)}
|
||||||
))}
|
>
|
||||||
|
{key === "key" ? "ID" : replaceUnderscoreIfSnakeCase(key)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -115,10 +120,10 @@ const MyIssuesPage: NextPage = () => {
|
|||||||
<div className="flex flex-col space-y-5">
|
<div className="flex flex-col space-y-5">
|
||||||
<Disclosure as="div" defaultOpen>
|
<Disclosure as="div" defaultOpen>
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<div className="rounded-[10px] border border-gray-300 bg-white">
|
<div className="bg-white">
|
||||||
<div
|
<div
|
||||||
className={`flex items-center justify-start bg-gray-100 px-5 py-3 ${
|
className={`flex items-center justify-start bg-gray-100 px-5 py-3 ${
|
||||||
open ? "rounded-t-[10px]" : "rounded-[10px]"
|
open ? "" : "rounded-[10px]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Disclosure.Button>
|
<Disclosure.Button>
|
||||||
|
@ -34,6 +34,7 @@ const defaultValues = {
|
|||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
description_html: "",
|
description_html: "",
|
||||||
|
estimate_point: null,
|
||||||
state: "",
|
state: "",
|
||||||
assignees_list: [],
|
assignees_list: [],
|
||||||
priority: "low",
|
priority: "low",
|
||||||
|
@ -14,6 +14,7 @@ import projectService from "services/project.service";
|
|||||||
import pagesService from "services/pages.service";
|
import pagesService from "services/pages.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
|
import useLocalStorage from "hooks/use-local-storage";
|
||||||
// icons
|
// icons
|
||||||
import { PlusIcon } from "components/icons";
|
import { PlusIcon } from "components/icons";
|
||||||
// layouts
|
// layouts
|
||||||
@ -74,6 +75,8 @@ const ProjectPages: NextPage = () => {
|
|||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
|
const { storedValue: pageTab, setValue: setPageTab } = useLocalStorage("pageTab", "Recent");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
register,
|
register,
|
||||||
@ -145,6 +148,25 @@ const ProjectPages: NextPage = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const currentTabValue = (tab: string | null) => {
|
||||||
|
switch (tab) {
|
||||||
|
case "Recent":
|
||||||
|
return 0;
|
||||||
|
case "All":
|
||||||
|
return 1;
|
||||||
|
case "Favorites":
|
||||||
|
return 2;
|
||||||
|
case "Created by me":
|
||||||
|
return 3;
|
||||||
|
case "Created by others":
|
||||||
|
return 4;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CreateUpdatePageModal
|
<CreateUpdatePageModal
|
||||||
@ -193,7 +215,26 @@ const ProjectPages: NextPage = () => {
|
|||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
<div>
|
<div>
|
||||||
<Tab.Group>
|
<Tab.Group
|
||||||
|
defaultIndex={currentTabValue(pageTab)}
|
||||||
|
onChange={(i) => {
|
||||||
|
switch (i) {
|
||||||
|
case 0:
|
||||||
|
return setPageTab("Recent");
|
||||||
|
case 1:
|
||||||
|
return setPageTab("All");
|
||||||
|
case 2:
|
||||||
|
return setPageTab("Favorites");
|
||||||
|
case 3:
|
||||||
|
return setPageTab("Created by me");
|
||||||
|
case 4:
|
||||||
|
return setPageTab("Created by others");
|
||||||
|
|
||||||
|
default:
|
||||||
|
return setPageTab("Recent");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Tab.List as="div" className="flex items-center justify-between mb-6">
|
<Tab.List as="div" className="flex items-center justify-between mb-6">
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
{["Recent", "All", "Favorites", "Created by me", "Created by others"].map(
|
{["Recent", "All", "Favorites", "Created by me", "Created by others"].map(
|
||||||
|
2
apps/app/types/issues.d.ts
vendored
2
apps/app/types/issues.d.ts
vendored
@ -87,7 +87,7 @@ export interface IIssue {
|
|||||||
description: any;
|
description: any;
|
||||||
description_html: any;
|
description_html: any;
|
||||||
description_stripped: any;
|
description_stripped: any;
|
||||||
estimate_point: number;
|
estimate_point: number | null;
|
||||||
id: string;
|
id: string;
|
||||||
issue_cycle: IIssueCycle | null;
|
issue_cycle: IIssueCycle | null;
|
||||||
issue_link: {
|
issue_link: {
|
||||||
|
Loading…
Reference in New Issue
Block a user