mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge branch 'develop' of github.com:makeplane/plane into develop
This commit is contained in:
commit
20bbe74b57
109
apps/app/components/command-palette/change-issue-assignee.tsx
Normal file
109
apps/app/components/command-palette/change-issue-assignee.tsx
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import { useRouter } from "next/router";
|
||||||
|
import React, { Dispatch, SetStateAction, useCallback } from "react";
|
||||||
|
import useSWR, { mutate } from "swr";
|
||||||
|
|
||||||
|
// cmdk
|
||||||
|
import { Command } from "cmdk";
|
||||||
|
// services
|
||||||
|
import issuesService from "services/issues.service";
|
||||||
|
// types
|
||||||
|
import { IIssue } from "types";
|
||||||
|
// constants
|
||||||
|
import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY, PROJECT_MEMBERS } from "constants/fetch-keys";
|
||||||
|
// icons
|
||||||
|
import { CheckIcon } from "components/icons";
|
||||||
|
import projectService from "services/project.service";
|
||||||
|
import { Avatar } from "components/ui";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
setIsPaletteOpen: Dispatch<SetStateAction<boolean>>;
|
||||||
|
issue: IIssue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChangeIssueAssignee: React.FC<Props> = ({ setIsPaletteOpen, issue }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, projectId, issueId } = router.query;
|
||||||
|
|
||||||
|
const { data: members } = useSWR(
|
||||||
|
projectId ? PROJECT_MEMBERS(projectId as string) : null,
|
||||||
|
workspaceSlug && projectId
|
||||||
|
? () => projectService.projectMembers(workspaceSlug as string, projectId as string)
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
|
||||||
|
const options =
|
||||||
|
members?.map(({ member }) => ({
|
||||||
|
value: member.id,
|
||||||
|
query:
|
||||||
|
(member.first_name && member.first_name !== "" ? member.first_name : member.email) +
|
||||||
|
" " +
|
||||||
|
member.last_name ?? "",
|
||||||
|
content: (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Avatar user={member} />
|
||||||
|
{member.first_name && member.first_name !== "" ? member.first_name : member.email}
|
||||||
|
</div>
|
||||||
|
{issue.assignees.includes(member.id) && (
|
||||||
|
<div>
|
||||||
|
<CheckIcon className="h-3 w-3" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
})) ?? [];
|
||||||
|
|
||||||
|
const updateIssue = useCallback(
|
||||||
|
async (formData: Partial<IIssue>) => {
|
||||||
|
if (!workspaceSlug || !projectId || !issueId) return;
|
||||||
|
|
||||||
|
mutate(
|
||||||
|
ISSUE_DETAILS(issueId as string),
|
||||||
|
(prevData: IIssue) => ({
|
||||||
|
...prevData,
|
||||||
|
...formData,
|
||||||
|
}),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
const payload = { ...formData };
|
||||||
|
await issuesService
|
||||||
|
.patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload)
|
||||||
|
.then(() => {
|
||||||
|
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[workspaceSlug, issueId, projectId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleIssueAssignees = (assignee: string) => {
|
||||||
|
const updatedAssignees = issue.assignees ?? [];
|
||||||
|
|
||||||
|
if (updatedAssignees.includes(assignee)) {
|
||||||
|
updatedAssignees.splice(updatedAssignees.indexOf(assignee), 1);
|
||||||
|
} else {
|
||||||
|
updatedAssignees.push(assignee);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateIssue({ assignees_list: updatedAssignees });
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{options.map((option) => (
|
||||||
|
<Command.Item
|
||||||
|
key={option.value}
|
||||||
|
onSelect={() => handleIssueAssignees(option.value)}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
{option.content}
|
||||||
|
</Command.Item>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,75 @@
|
|||||||
|
import { useRouter } from "next/router";
|
||||||
|
import React, { Dispatch, SetStateAction, useCallback } from "react";
|
||||||
|
import { mutate } from "swr";
|
||||||
|
|
||||||
|
// cmdk
|
||||||
|
import { Command } from "cmdk";
|
||||||
|
// services
|
||||||
|
import issuesService from "services/issues.service";
|
||||||
|
// types
|
||||||
|
import { IIssue } from "types";
|
||||||
|
// constants
|
||||||
|
import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
|
||||||
|
import { PRIORITIES } from "constants/project";
|
||||||
|
// icons
|
||||||
|
import { CheckIcon, getPriorityIcon } from "components/icons";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
setIsPaletteOpen: Dispatch<SetStateAction<boolean>>;
|
||||||
|
issue: IIssue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChangeIssuePriority: React.FC<Props> = ({ setIsPaletteOpen, issue }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, projectId, issueId } = router.query;
|
||||||
|
|
||||||
|
const submitChanges = useCallback(
|
||||||
|
async (formData: Partial<IIssue>) => {
|
||||||
|
if (!workspaceSlug || !projectId || !issueId) return;
|
||||||
|
|
||||||
|
mutate(
|
||||||
|
ISSUE_DETAILS(issueId as string),
|
||||||
|
(prevData: IIssue) => ({
|
||||||
|
...prevData,
|
||||||
|
...formData,
|
||||||
|
}),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
const payload = { ...formData };
|
||||||
|
await issuesService
|
||||||
|
.patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload)
|
||||||
|
.then(() => {
|
||||||
|
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[workspaceSlug, issueId, projectId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleIssueState = (priority: string | null) => {
|
||||||
|
submitChanges({ priority });
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{PRIORITIES.map((priority) => (
|
||||||
|
<Command.Item
|
||||||
|
key={priority}
|
||||||
|
onSelect={() => handleIssueState(priority)}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
{getPriorityIcon(priority)}
|
||||||
|
<span className="capitalize">{priority ?? "None"}</span>
|
||||||
|
</div>
|
||||||
|
<div>{priority === issue.priority && <CheckIcon className="h-3 w-3" />}</div>
|
||||||
|
</Command.Item>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
96
apps/app/components/command-palette/change-issue-state.tsx
Normal file
96
apps/app/components/command-palette/change-issue-state.tsx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { useRouter } from "next/router";
|
||||||
|
import React, { Dispatch, SetStateAction, useCallback } from "react";
|
||||||
|
import useSWR, { mutate } from "swr";
|
||||||
|
|
||||||
|
// cmdk
|
||||||
|
import { Command } from "cmdk";
|
||||||
|
// ui
|
||||||
|
import { Spinner } from "components/ui";
|
||||||
|
// helpers
|
||||||
|
import { getStatesList } from "helpers/state.helper";
|
||||||
|
// services
|
||||||
|
import issuesService from "services/issues.service";
|
||||||
|
import stateService from "services/state.service";
|
||||||
|
// types
|
||||||
|
import { IIssue } from "types";
|
||||||
|
// fetch keys
|
||||||
|
import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY, STATE_LIST } from "constants/fetch-keys";
|
||||||
|
// icons
|
||||||
|
import { CheckIcon, getStateGroupIcon } from "components/icons";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
setIsPaletteOpen: Dispatch<SetStateAction<boolean>>;
|
||||||
|
issue: IIssue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChangeIssueState: React.FC<Props> = ({ setIsPaletteOpen, issue }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, projectId, issueId } = router.query;
|
||||||
|
|
||||||
|
const { data: stateGroups, mutate: mutateIssueDetails } = useSWR(
|
||||||
|
workspaceSlug && projectId ? STATE_LIST(projectId as string) : null,
|
||||||
|
workspaceSlug && projectId
|
||||||
|
? () => stateService.getStates(workspaceSlug as string, projectId as string)
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
const states = getStatesList(stateGroups ?? {});
|
||||||
|
|
||||||
|
const submitChanges = useCallback(
|
||||||
|
async (formData: Partial<IIssue>) => {
|
||||||
|
if (!workspaceSlug || !projectId || !issueId) return;
|
||||||
|
|
||||||
|
mutate(
|
||||||
|
ISSUE_DETAILS(issueId as string),
|
||||||
|
(prevData: IIssue) => ({
|
||||||
|
...prevData,
|
||||||
|
...formData,
|
||||||
|
}),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
const payload = { ...formData };
|
||||||
|
await issuesService
|
||||||
|
.patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload)
|
||||||
|
.then(() => {
|
||||||
|
mutateIssueDetails();
|
||||||
|
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[workspaceSlug, issueId, projectId, mutateIssueDetails]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleIssueState = (stateId: string) => {
|
||||||
|
submitChanges({ state: stateId });
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{states ? (
|
||||||
|
states.length > 0 ? (
|
||||||
|
states.map((state) => (
|
||||||
|
<Command.Item
|
||||||
|
key={state.id}
|
||||||
|
onSelect={() => handleIssueState(state.id)}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
{getStateGroupIcon(state.group, "16", "16", state.color)}
|
||||||
|
<p>{state.name}</p>
|
||||||
|
</div>
|
||||||
|
<div>{state.id === issue.state && <CheckIcon className="h-3 w-3" />}</div>
|
||||||
|
</Command.Item>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="text-center">No states found</div>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Spinner />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -1,100 +1,174 @@
|
|||||||
import React, { useState, useCallback, useEffect } from "react";
|
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import useSWR, { mutate } from "swr";
|
||||||
|
|
||||||
import useSWR from "swr";
|
// icons
|
||||||
|
import {
|
||||||
|
ArrowRightIcon,
|
||||||
|
ChartBarIcon,
|
||||||
|
ClipboardIcon,
|
||||||
|
FolderPlusIcon,
|
||||||
|
InboxIcon,
|
||||||
|
MagnifyingGlassIcon,
|
||||||
|
Squares2X2Icon,
|
||||||
|
TrashIcon,
|
||||||
|
UserMinusIcon,
|
||||||
|
UserPlusIcon,
|
||||||
|
UsersIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
|
import {
|
||||||
|
AssignmentClipboardIcon,
|
||||||
|
BoltIcon,
|
||||||
|
ContrastIcon,
|
||||||
|
DiscordIcon,
|
||||||
|
DocumentIcon,
|
||||||
|
GithubIcon,
|
||||||
|
LayerDiagonalIcon,
|
||||||
|
PeopleGroupIcon,
|
||||||
|
SettingIcon,
|
||||||
|
ViewListIcon,
|
||||||
|
PencilScribbleIcon
|
||||||
|
} from "components/icons";
|
||||||
// headless ui
|
// headless ui
|
||||||
import { Combobox, Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// cmdk
|
||||||
import userService from "services/user.service";
|
import { Command } from "cmdk";
|
||||||
// hooks
|
// hooks
|
||||||
import useTheme from "hooks/use-theme";
|
import useTheme from "hooks/use-theme";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
// components
|
// components
|
||||||
import { ShortcutsModal } from "components/command-palette";
|
|
||||||
import { BulkDeleteIssuesModal } from "components/core";
|
|
||||||
import { CreateProjectModal } from "components/project";
|
|
||||||
import { CreateUpdateIssueModal } from "components/issues";
|
|
||||||
import { CreateUpdateCycleModal } from "components/cycles";
|
|
||||||
import { CreateUpdateModuleModal } from "components/modules";
|
|
||||||
// ui
|
|
||||||
import { SecondaryButton } from "components/ui";
|
|
||||||
// icons
|
|
||||||
import {
|
import {
|
||||||
FolderIcon,
|
ShortcutsModal,
|
||||||
RectangleStackIcon,
|
ChangeIssueState,
|
||||||
ClipboardDocumentListIcon,
|
ChangeIssuePriority,
|
||||||
MagnifyingGlassIcon,
|
ChangeIssueAssignee,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "components/command-palette";
|
||||||
|
import { BulkDeleteIssuesModal } from "components/core";
|
||||||
|
import { CreateUpdateCycleModal } from "components/cycles";
|
||||||
|
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
|
||||||
|
import { CreateUpdateModuleModal } from "components/modules";
|
||||||
|
import { CreateProjectModal } from "components/project";
|
||||||
|
import { CreateUpdateViewModal } from "components/views";
|
||||||
// helpers
|
// helpers
|
||||||
import { copyTextToClipboard } from "helpers/string.helper";
|
import {
|
||||||
|
capitalizeFirstLetter,
|
||||||
|
copyTextToClipboard,
|
||||||
|
replaceUnderscoreIfSnakeCase,
|
||||||
|
} from "helpers/string.helper";
|
||||||
|
// services
|
||||||
|
import issuesService from "services/issues.service";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "types";
|
import { IIssue, IWorkspaceSearchResults } from "types";
|
||||||
// fetch-keys
|
// fetch keys
|
||||||
import { USER_ISSUE } from "constants/fetch-keys";
|
import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
|
||||||
|
import useDebounce from "hooks/use-debounce";
|
||||||
|
import workspaceService from "services/workspace.service";
|
||||||
|
|
||||||
export const CommandPalette: React.FC = () => {
|
export const CommandPalette: React.FC = () => {
|
||||||
const [query, setQuery] = useState("");
|
|
||||||
|
|
||||||
const [isPaletteOpen, setIsPaletteOpen] = useState(false);
|
const [isPaletteOpen, setIsPaletteOpen] = useState(false);
|
||||||
const [isIssueModalOpen, setIsIssueModalOpen] = useState(false);
|
const [isIssueModalOpen, setIsIssueModalOpen] = useState(false);
|
||||||
const [isProjectModalOpen, setIsProjectModalOpen] = useState(false);
|
const [isProjectModalOpen, setIsProjectModalOpen] = useState(false);
|
||||||
const [isShortcutsModalOpen, setIsShortcutsModalOpen] = useState(false);
|
const [isShortcutsModalOpen, setIsShortcutsModalOpen] = useState(false);
|
||||||
const [isCreateCycleModalOpen, setIsCreateCycleModalOpen] = useState(false);
|
const [isCreateCycleModalOpen, setIsCreateCycleModalOpen] = useState(false);
|
||||||
|
const [isCreateViewModalOpen, setIsCreateViewModalOpen] = useState(false);
|
||||||
const [isCreateModuleModalOpen, setIsCreateModuleModalOpen] = useState(false);
|
const [isCreateModuleModalOpen, setIsCreateModuleModalOpen] = useState(false);
|
||||||
const [isBulkDeleteIssuesModalOpen, setIsBulkDeleteIssuesModalOpen] = useState(false);
|
const [isBulkDeleteIssuesModalOpen, setIsBulkDeleteIssuesModalOpen] = useState(false);
|
||||||
|
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||||
|
|
||||||
|
const [searchTerm, setSearchTerm] = React.useState<string>("");
|
||||||
|
const [results, setResults] = useState<IWorkspaceSearchResults>({
|
||||||
|
results: {
|
||||||
|
workspace: [],
|
||||||
|
project: [],
|
||||||
|
issue: [],
|
||||||
|
cycle: [],
|
||||||
|
module: [],
|
||||||
|
issue_view: [],
|
||||||
|
page: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const [isPendingAPIRequest, setIsPendingAPIRequest] = useState(false);
|
||||||
|
const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
||||||
|
const [placeholder, setPlaceholder] = React.useState("Type a command or search...");
|
||||||
|
const [pages, setPages] = React.useState<string[]>([]);
|
||||||
|
const page = pages[pages.length - 1];
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId, issueId } = router.query;
|
||||||
|
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
const { toggleCollapsed } = useTheme();
|
const { toggleCollapsed } = useTheme();
|
||||||
|
|
||||||
const { data: myIssues } = useSWR<IIssue[]>(
|
const { data: issueDetails } = useSWR<IIssue | undefined>(
|
||||||
workspaceSlug ? USER_ISSUE(workspaceSlug as string) : null,
|
workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null,
|
||||||
workspaceSlug ? () => userService.userIssues(workspaceSlug as string) : null
|
workspaceSlug && projectId && issueId
|
||||||
|
? () =>
|
||||||
|
issuesService.retrieve(workspaceSlug as string, projectId as string, issueId as string)
|
||||||
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredIssues: IIssue[] =
|
const updateIssue = useCallback(
|
||||||
query === ""
|
async (formData: Partial<IIssue>) => {
|
||||||
? myIssues ?? []
|
if (!workspaceSlug || !projectId || !issueId) return;
|
||||||
: myIssues?.filter(
|
|
||||||
(issue) =>
|
|
||||||
issue.name.toLowerCase().includes(query.toLowerCase()) ||
|
|
||||||
`${issue.project_detail.identifier}-${issue.sequence_id}`
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(query.toLowerCase())
|
|
||||||
) ?? [];
|
|
||||||
|
|
||||||
const quickActions = [
|
mutate(
|
||||||
{
|
ISSUE_DETAILS(issueId as string),
|
||||||
name: "Add new issue...",
|
(prevData: IIssue) => ({
|
||||||
icon: RectangleStackIcon,
|
...prevData,
|
||||||
hide: !projectId,
|
...formData,
|
||||||
shortcut: "C",
|
}),
|
||||||
onClick: () => {
|
false
|
||||||
setIsIssueModalOpen(true);
|
);
|
||||||
},
|
|
||||||
},
|
const payload = { ...formData };
|
||||||
{
|
await issuesService
|
||||||
name: "Add new project...",
|
.patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload)
|
||||||
icon: ClipboardDocumentListIcon,
|
.then(() => {
|
||||||
hide: !workspaceSlug,
|
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
|
||||||
shortcut: "P",
|
mutate(ISSUE_DETAILS(issueId as string));
|
||||||
onClick: () => {
|
})
|
||||||
setIsProjectModalOpen(true);
|
.catch((e) => {
|
||||||
},
|
console.error(e);
|
||||||
},
|
});
|
||||||
];
|
},
|
||||||
|
[workspaceSlug, issueId, projectId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleIssueAssignees = (assignee: string) => {
|
||||||
|
if (!issueDetails) return;
|
||||||
|
|
||||||
const handleCommandPaletteClose = () => {
|
|
||||||
setIsPaletteOpen(false);
|
setIsPaletteOpen(false);
|
||||||
setQuery("");
|
const updatedAssignees = issueDetails.assignees ?? [];
|
||||||
|
|
||||||
|
if (updatedAssignees.includes(assignee)) {
|
||||||
|
updatedAssignees.splice(updatedAssignees.indexOf(assignee), 1);
|
||||||
|
} else {
|
||||||
|
updatedAssignees.push(assignee);
|
||||||
|
}
|
||||||
|
updateIssue({ assignees_list: updatedAssignees });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const copyIssueUrlToClipboard = useCallback(() => {
|
||||||
|
if (!router.query.issueId) return;
|
||||||
|
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
copyTextToClipboard(url.href)
|
||||||
|
.then(() => {
|
||||||
|
setToastAlert({
|
||||||
|
type: "success",
|
||||||
|
title: "Copied to clipboard",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Some error occurred",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, [router, setToastAlert]);
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
if (
|
if (
|
||||||
@ -108,22 +182,7 @@ export const CommandPalette: React.FC = () => {
|
|||||||
} else if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "c") {
|
} else if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "c") {
|
||||||
if (e.altKey) {
|
if (e.altKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!router.query.issueId) return;
|
copyIssueUrlToClipboard();
|
||||||
|
|
||||||
const url = new URL(window.location.href);
|
|
||||||
copyTextToClipboard(url.href)
|
|
||||||
.then(() => {
|
|
||||||
setToastAlert({
|
|
||||||
type: "success",
|
|
||||||
title: "Copied to clipboard",
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setToastAlert({
|
|
||||||
type: "error",
|
|
||||||
title: "Some error occurred",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else if (e.key.toLowerCase() === "c") {
|
} else if (e.key.toLowerCase() === "c") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -149,7 +208,7 @@ export const CommandPalette: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[toggleCollapsed, setToastAlert, router]
|
[toggleCollapsed, copyIssueUrlToClipboard]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -157,8 +216,69 @@ export const CommandPalette: React.FC = () => {
|
|||||||
return () => document.removeEventListener("keydown", handleKeyDown);
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
||||||
}, [handleKeyDown]);
|
}, [handleKeyDown]);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
|
// this is done prevent api request when user is clearing input
|
||||||
|
// or searchTerm has not been updated within last 500ms.
|
||||||
|
if (debouncedSearchTerm) {
|
||||||
|
setIsPendingAPIRequest(true);
|
||||||
|
workspaceService
|
||||||
|
.searchWorkspace(workspaceSlug as string, projectId as string, debouncedSearchTerm)
|
||||||
|
.then((results) => {
|
||||||
|
setIsPendingAPIRequest(false);
|
||||||
|
setResults(results);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setIsPendingAPIRequest(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[debouncedSearchTerm, workspaceSlug, projectId] // Only call effect if debounced search term changes
|
||||||
|
);
|
||||||
|
|
||||||
if (!user) return null;
|
if (!user) return null;
|
||||||
|
|
||||||
|
const createNewWorkspace = () => {
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
router.push("/create-workspace");
|
||||||
|
};
|
||||||
|
|
||||||
|
const createNewProject = () => {
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
setIsProjectModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createNewIssue = () => {
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
setIsIssueModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createNewCycle = () => {
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
setIsCreateCycleModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createNewView = () => {
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
setIsCreateViewModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createNewModule = () => {
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
setIsCreateModuleModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteIssue = () => {
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
setDeleteIssueModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToSettings = (path: string = "") => {
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
router.push(`/${workspaceSlug}/settings/${path}`);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ShortcutsModal isOpen={isShortcutsModalOpen} setIsOpen={setIsShortcutsModalOpen} />
|
<ShortcutsModal isOpen={isShortcutsModalOpen} setIsOpen={setIsShortcutsModalOpen} />
|
||||||
@ -175,8 +295,20 @@ export const CommandPalette: React.FC = () => {
|
|||||||
isOpen={isCreateModuleModalOpen}
|
isOpen={isCreateModuleModalOpen}
|
||||||
setIsOpen={setIsCreateModuleModalOpen}
|
setIsOpen={setIsCreateModuleModalOpen}
|
||||||
/>
|
/>
|
||||||
|
<CreateUpdateViewModal
|
||||||
|
handleClose={() => setIsCreateViewModalOpen(false)}
|
||||||
|
isOpen={isCreateViewModalOpen}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{issueId && issueDetails && (
|
||||||
|
<DeleteIssueModal
|
||||||
|
handleClose={() => setDeleteIssueModal(false)}
|
||||||
|
isOpen={deleteIssueModal}
|
||||||
|
data={issueDetails}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<CreateUpdateIssueModal
|
<CreateUpdateIssueModal
|
||||||
isOpen={isIssueModalOpen}
|
isOpen={isIssueModalOpen}
|
||||||
handleClose={() => setIsIssueModalOpen(false)}
|
handleClose={() => setIsIssueModalOpen(false)}
|
||||||
@ -187,11 +319,12 @@ export const CommandPalette: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
<Transition.Root
|
<Transition.Root
|
||||||
show={isPaletteOpen}
|
show={isPaletteOpen}
|
||||||
|
afterLeave={() => {
|
||||||
|
setSearchTerm("");
|
||||||
|
}}
|
||||||
as={React.Fragment}
|
as={React.Fragment}
|
||||||
afterLeave={() => setQuery("")}
|
|
||||||
appear
|
|
||||||
>
|
>
|
||||||
<Dialog as="div" className="relative z-20" onClose={handleCommandPaletteClose}>
|
<Dialog as="div" className="relative z-30" onClose={() => setIsPaletteOpen(false)}>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={React.Fragment}
|
as={React.Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
@ -204,154 +337,479 @@ export const CommandPalette: React.FC = () => {
|
|||||||
<div className="fixed inset-0 bg-gray-500 bg-opacity-25 transition-opacity" />
|
<div className="fixed inset-0 bg-gray-500 bg-opacity-25 transition-opacity" />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
||||||
<div className="fixed inset-0 z-20 p-4 sm:p-6 md:p-20">
|
<div className="fixed inset-0 z-30 overflow-y-auto p-4 sm:p-6 md:p-20">
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={React.Fragment}
|
as={React.Fragment}
|
||||||
enter="ease-out duration-300"
|
enter="ease-out duration-300"
|
||||||
enterFrom="opacity-0 scale-95"
|
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
enterTo="opacity-100 scale-100"
|
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leave="ease-in duration-200"
|
leave="ease-in duration-200"
|
||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="relative mx-auto max-w-2xl transform divide-y divide-gray-500 divide-opacity-10 rounded-xl bg-white shadow-2xl ring-1 ring-black ring-opacity-5 transition-all">
|
<Dialog.Panel className="relative mx-auto max-w-2xl transform divide-y divide-gray-500 divide-opacity-10 rounded-xl bg-white shadow-2xl ring-1 ring-black ring-opacity-5 transition-all">
|
||||||
<Combobox
|
<Command
|
||||||
onChange={(value: any) => {
|
filter={(value, search) => {
|
||||||
if (value?.url) router.push(value.url);
|
if (value.toLowerCase().includes(search.toLowerCase())) return 1;
|
||||||
else if (value?.onClick) value.onClick();
|
return 0;
|
||||||
handleCommandPaletteClose();
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
// when seach is empty and page is undefined
|
||||||
|
// when user tries to close the modal with esc
|
||||||
|
if (e.key === "Escape" && !page && !searchTerm) {
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
}
|
||||||
|
// Escape goes to previous page
|
||||||
|
// Backspace goes to previous page when search is empty
|
||||||
|
if (e.key === "Escape" || (e.key === "Backspace" && !searchTerm)) {
|
||||||
|
e.preventDefault();
|
||||||
|
setPages((pages) => pages.slice(0, -1));
|
||||||
|
setPlaceholder("Type a command or search...");
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="relative m-1">
|
{issueId && issueDetails && (
|
||||||
|
<div className="p-3">
|
||||||
|
<span className="rounded-md bg-slate-100 p-1 px-2 text-xs font-medium text-slate-500">
|
||||||
|
{issueDetails.project_detail?.identifier}-{issueDetails.sequence_id}{" "}
|
||||||
|
{issueDetails?.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="relative">
|
||||||
<MagnifyingGlassIcon
|
<MagnifyingGlassIcon
|
||||||
className="pointer-events-none absolute top-3.5 left-4 h-5 w-5 text-gray-900 text-opacity-40"
|
className="pointer-events-none absolute top-3.5 left-4 h-5 w-5 text-gray-900 text-opacity-40"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<Combobox.Input
|
<Command.Input
|
||||||
className="h-12 w-full border-0 bg-transparent pl-11 pr-4 text-gray-900 placeholder-gray-500 outline-none focus:ring-0 sm:text-sm"
|
className="w-full border-0 border-b bg-transparent p-4 pl-11 text-gray-900 placeholder-gray-500 outline-none focus:ring-0 sm:text-sm"
|
||||||
placeholder="Search..."
|
placeholder={placeholder}
|
||||||
autoComplete="off"
|
value={searchTerm}
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
onValueChange={(e) => {
|
||||||
|
setSearchTerm(e);
|
||||||
|
}}
|
||||||
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<Command.List className="max-h-96 overflow-scroll p-2">
|
||||||
|
<Command.Empty className="my-4 text-center text-gray-500">
|
||||||
|
No results found.
|
||||||
|
</Command.Empty>
|
||||||
|
|
||||||
<Combobox.Options
|
{debouncedSearchTerm !== "" && (
|
||||||
static
|
|
||||||
className="max-h-80 scroll-py-2 divide-y divide-gray-500 divide-opacity-10 overflow-y-auto"
|
|
||||||
>
|
|
||||||
{filteredIssues.length > 0 && (
|
|
||||||
<>
|
<>
|
||||||
<li className="p-2">
|
{Object.keys(results.results).map((key) => {
|
||||||
{query === "" && (
|
const section = (results.results as any)[key];
|
||||||
<h2 className="mt-4 mb-2 px-3 text-xs font-semibold text-gray-900">
|
if (section.length > 0) {
|
||||||
Select issues
|
return (
|
||||||
</h2>
|
<Command.Group
|
||||||
)}
|
heading={capitalizeFirstLetter(replaceUnderscoreIfSnakeCase(key))}
|
||||||
<ul className="text-sm text-gray-700">
|
key={key}
|
||||||
{filteredIssues.map((issue) => (
|
|
||||||
<Combobox.Option
|
|
||||||
key={issue.id}
|
|
||||||
as="label"
|
|
||||||
htmlFor={`issue-${issue.id}`}
|
|
||||||
value={{
|
|
||||||
name: issue.name,
|
|
||||||
url: `/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`,
|
|
||||||
}}
|
|
||||||
className={({ active }) =>
|
|
||||||
`flex cursor-pointer select-none items-center justify-between rounded-md px-3 py-2 ${
|
|
||||||
active ? "bg-gray-500 bg-opacity-5 text-gray-900" : ""
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{({ active }) => (
|
{section.map((item: any) => {
|
||||||
<>
|
let path = "";
|
||||||
<div className="flex items-center gap-2">
|
let value = item.name;
|
||||||
<span
|
let Icon: any = ArrowRightIcon;
|
||||||
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
|
|
||||||
style={{
|
if (key === "workspace") {
|
||||||
backgroundColor: issue.state_detail.color,
|
path = `/${item.slug}`;
|
||||||
}}
|
Icon = FolderPlusIcon;
|
||||||
/>
|
} else if (key == "project") {
|
||||||
<span className="flex-shrink-0 text-xs text-gray-500">
|
path = `/${item.workspace__slug}/projects/${item.id}/issues`;
|
||||||
{issue.project_detail?.identifier}-{issue.sequence_id}
|
Icon = AssignmentClipboardIcon;
|
||||||
</span>
|
} else if (key === "issue") {
|
||||||
<span>{issue.name}</span>
|
path = `/${item.workspace__slug}/projects/${item.project_id}/issues/${item.id}`;
|
||||||
</div>
|
value = `${item.project__identifier}-${item.sequence_id} item.name`;
|
||||||
<button
|
Icon = LayerDiagonalIcon;
|
||||||
type="button"
|
} else if (key === "issue_view") {
|
||||||
className={`flex-shrink-0 rounded-md border border-gray-300 px-2 py-1 text-sm text-gray-500 transition-opacity duration-75 hover:bg-gray-500 hover:bg-opacity-5 ${
|
path = `/${item.workspace__slug}/projects/${item.project_id}/views/${item.id}`;
|
||||||
active
|
Icon = ViewListIcon;
|
||||||
? "pointer-events-auto opacity-100"
|
} else if (key === "module") {
|
||||||
: "pointer-events-none opacity-0"
|
path = `/${item.workspace__slug}/projects/${item.project_id}/modules/${item.id}`;
|
||||||
}`}
|
Icon = PeopleGroupIcon;
|
||||||
|
} else if (key === "page") {
|
||||||
|
path = `/${item.workspace__slug}/projects/${item.project_id}/pages/${item.id}`;
|
||||||
|
Icon = PencilScribbleIcon;
|
||||||
|
} else if (key === "cycle") {
|
||||||
|
path = `/${item.workspace__slug}/projects/${item.project_id}/cycles/${item.id}`;
|
||||||
|
Icon = ContrastIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Command.Item
|
||||||
|
key={item.id}
|
||||||
|
onSelect={() => {
|
||||||
|
router.push(path);
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
}}
|
||||||
|
value={value}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
Jump to
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
</button>
|
<Icon className="h-4 w-4" />
|
||||||
</>
|
{item.name}
|
||||||
)}
|
</div>
|
||||||
</Combobox.Option>
|
</Command.Item>
|
||||||
))}
|
);
|
||||||
</ul>
|
})}
|
||||||
</li>
|
</Command.Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{query === "" && (
|
|
||||||
<li className="p-2">
|
{!page && (
|
||||||
<h2 className="sr-only">Quick actions</h2>
|
<>
|
||||||
<ul className="text-sm text-gray-700">
|
{issueId && (
|
||||||
{quickActions.map(
|
<>
|
||||||
(action) =>
|
<Command.Item
|
||||||
!action.hide && (
|
onSelect={() => {
|
||||||
<Combobox.Option
|
setPlaceholder("Change state...");
|
||||||
key={action.shortcut}
|
setSearchTerm("");
|
||||||
value={{
|
setPages([...pages, "change-issue-state"]);
|
||||||
name: action.name,
|
}}
|
||||||
onClick: action.onClick,
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
}}
|
tabIndex={0}
|
||||||
className={({ active }) =>
|
>
|
||||||
`flex cursor-default select-none items-center rounded-md px-3 py-2 ${
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
active ? "bg-gray-500 bg-opacity-5 text-gray-900" : ""
|
<Squares2X2Icon className="h-4 w-4" />
|
||||||
}`
|
Change state...
|
||||||
}
|
</div>
|
||||||
>
|
</Command.Item>
|
||||||
{({ active }) => (
|
<Command.Item
|
||||||
<>
|
onSelect={() => {
|
||||||
<action.icon
|
setPlaceholder("Change priority...");
|
||||||
className={`h-6 w-6 flex-none text-gray-900 text-opacity-40 ${
|
setSearchTerm("");
|
||||||
active ? "text-opacity-100" : ""
|
setPages([...pages, "change-issue-priority"]);
|
||||||
}`}
|
}}
|
||||||
aria-hidden="true"
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
/>
|
tabIndex={0}
|
||||||
<span className="ml-3 flex-auto truncate">{action.name}</span>
|
>
|
||||||
<span className="ml-3 flex-none text-xs font-semibold text-gray-500">
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
<kbd className="font-sans">{action.shortcut}</kbd>
|
<ChartBarIcon className="h-4 w-4" />
|
||||||
</span>
|
Change priority...
|
||||||
</>
|
</div>
|
||||||
)}
|
</Command.Item>
|
||||||
</Combobox.Option>
|
<Command.Item
|
||||||
)
|
onSelect={() => {
|
||||||
)}
|
setPlaceholder("Assign to...");
|
||||||
</ul>
|
setSearchTerm("");
|
||||||
</li>
|
setPages([...pages, "change-issue-assignee"]);
|
||||||
|
}}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<UsersIcon className="h-4 w-4" />
|
||||||
|
Assign to...
|
||||||
|
</div>
|
||||||
|
</Command.Item>
|
||||||
|
<Command.Item
|
||||||
|
onSelect={() => {
|
||||||
|
handleIssueAssignees(user.id);
|
||||||
|
setSearchTerm("");
|
||||||
|
}}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
{issueDetails?.assignees.includes(user.id) ? (
|
||||||
|
<>
|
||||||
|
<UserMinusIcon className="h-4 w-4" />
|
||||||
|
Un-assign from me
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<UserPlusIcon className="h-4 w-4" />
|
||||||
|
Assign to me
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Command.Item>
|
||||||
|
|
||||||
|
<Command.Item
|
||||||
|
onSelect={deleteIssue}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<TrashIcon className="h-4 w-4" />
|
||||||
|
Delete issue
|
||||||
|
</div>
|
||||||
|
</Command.Item>
|
||||||
|
<Command.Item
|
||||||
|
onSelect={() => {
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
copyIssueUrlToClipboard();
|
||||||
|
}}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<ClipboardIcon className="h-4 w-4" />
|
||||||
|
Copy issue URL to clipboard
|
||||||
|
</div>
|
||||||
|
</Command.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Command.Group heading="Issue">
|
||||||
|
<Command.Item
|
||||||
|
onSelect={createNewIssue}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<LayerDiagonalIcon className="h-4 w-4" />
|
||||||
|
Create new issue
|
||||||
|
</div>
|
||||||
|
<kbd>C</kbd>
|
||||||
|
</Command.Item>
|
||||||
|
</Command.Group>
|
||||||
|
|
||||||
|
{workspaceSlug && (
|
||||||
|
<Command.Group heading="Project">
|
||||||
|
<Command.Item
|
||||||
|
onSelect={createNewProject}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<AssignmentClipboardIcon className="h-4 w-4" />
|
||||||
|
Create new project
|
||||||
|
</div>
|
||||||
|
<kbd>P</kbd>
|
||||||
|
</Command.Item>
|
||||||
|
</Command.Group>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{projectId && (
|
||||||
|
<>
|
||||||
|
<Command.Group heading="Cycle">
|
||||||
|
<Command.Item
|
||||||
|
onSelect={createNewCycle}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<ContrastIcon className="h-4 w-4" />
|
||||||
|
Create new cycle
|
||||||
|
</div>
|
||||||
|
<kbd>Q</kbd>
|
||||||
|
</Command.Item>
|
||||||
|
</Command.Group>
|
||||||
|
|
||||||
|
<Command.Group heading="Module">
|
||||||
|
<Command.Item
|
||||||
|
onSelect={createNewModule}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<PeopleGroupIcon className="h-4 w-4" />
|
||||||
|
Create new module
|
||||||
|
</div>
|
||||||
|
<kbd>M</kbd>
|
||||||
|
</Command.Item>
|
||||||
|
</Command.Group>
|
||||||
|
|
||||||
|
<Command.Group heading="View">
|
||||||
|
<Command.Item
|
||||||
|
onSelect={createNewView}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<ViewListIcon className="h-4 w-4" />
|
||||||
|
Create new view
|
||||||
|
</div>
|
||||||
|
<kbd>Q</kbd>
|
||||||
|
</Command.Item>
|
||||||
|
</Command.Group>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Command.Group heading="Workspace Settings">
|
||||||
|
<Command.Item
|
||||||
|
onSelect={() => {
|
||||||
|
setPlaceholder("Search workspace settings...");
|
||||||
|
setSearchTerm("");
|
||||||
|
setPages([...pages, "settings"]);
|
||||||
|
}}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<SettingIcon className="h-4 w-4" />
|
||||||
|
Search settings...
|
||||||
|
</div>
|
||||||
|
</Command.Item>
|
||||||
|
</Command.Group>
|
||||||
|
<Command.Group heading="Account">
|
||||||
|
<Command.Item
|
||||||
|
onSelect={createNewWorkspace}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<FolderPlusIcon className="h-4 w-4" />
|
||||||
|
Create new workspace
|
||||||
|
</div>
|
||||||
|
</Command.Item>
|
||||||
|
</Command.Group>
|
||||||
|
<Command.Group heading="Help">
|
||||||
|
<Command.Item
|
||||||
|
onSelect={() => {
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
const e = new KeyboardEvent("keydown", {
|
||||||
|
key: "h",
|
||||||
|
});
|
||||||
|
document.dispatchEvent(e);
|
||||||
|
}}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<BoltIcon className="h-4 w-4" />
|
||||||
|
Open keyboard shortcuts
|
||||||
|
</div>
|
||||||
|
</Command.Item>
|
||||||
|
<Command.Item
|
||||||
|
onSelect={() => {
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
window.open("https://docs.plane.so/", "_blank");
|
||||||
|
}}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<DocumentIcon className="h-4 w-4" />
|
||||||
|
Open Plane documentation
|
||||||
|
</div>
|
||||||
|
</Command.Item>
|
||||||
|
<Command.Item
|
||||||
|
onSelect={() => {
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
window.open("https://discord.com/invite/A92xrEGCge", "_blank");
|
||||||
|
}}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-600">
|
||||||
|
<DiscordIcon className="h-4 w-4" />
|
||||||
|
Join our discord
|
||||||
|
</div>
|
||||||
|
</Command.Item>
|
||||||
|
<Command.Item
|
||||||
|
onSelect={() => {
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
window.open(
|
||||||
|
"https://github.com/makeplane/plane/issues/new/choose",
|
||||||
|
"_blank"
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<GithubIcon className="h-4 w-4" />
|
||||||
|
Report a bug
|
||||||
|
</div>
|
||||||
|
</Command.Item>
|
||||||
|
<Command.Item
|
||||||
|
onSelect={() => {
|
||||||
|
setIsPaletteOpen(false);
|
||||||
|
window.open("mailto:hello@plane.so", "_blank");
|
||||||
|
}}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<InboxIcon className="h-4 w-4" />
|
||||||
|
Email us
|
||||||
|
</div>
|
||||||
|
</Command.Item>
|
||||||
|
</Command.Group>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Combobox.Options>
|
|
||||||
|
|
||||||
{query !== "" && filteredIssues.length === 0 && (
|
{page === "settings" && workspaceSlug && (
|
||||||
<div className="py-14 px-6 text-center sm:px-14">
|
<>
|
||||||
<FolderIcon
|
<Command.Item
|
||||||
className="mx-auto h-6 w-6 text-gray-500 text-opacity-40"
|
onSelect={() => goToSettings()}
|
||||||
aria-hidden="true"
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<SettingIcon className="h-4 w-4" />
|
||||||
|
General
|
||||||
|
</div>
|
||||||
|
</Command.Item>
|
||||||
|
<Command.Item
|
||||||
|
onSelect={() => goToSettings("members")}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<SettingIcon className="h-4 w-4" />
|
||||||
|
Members
|
||||||
|
</div>
|
||||||
|
</Command.Item>
|
||||||
|
<Command.Item
|
||||||
|
onSelect={() => goToSettings("billing")}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<SettingIcon className="h-4 w-4" />
|
||||||
|
Billings and Plans
|
||||||
|
</div>
|
||||||
|
</Command.Item>
|
||||||
|
<Command.Item
|
||||||
|
onSelect={() => goToSettings("integrations")}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<SettingIcon className="h-4 w-4" />
|
||||||
|
Integrations
|
||||||
|
</div>
|
||||||
|
</Command.Item>
|
||||||
|
<Command.Item
|
||||||
|
onSelect={() => goToSettings("import-export")}
|
||||||
|
className="focus:bg-slate-200 focus:outline-none"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 text-slate-700">
|
||||||
|
<SettingIcon className="h-4 w-4" />
|
||||||
|
Import/Export
|
||||||
|
</div>
|
||||||
|
</Command.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{page === "change-issue-state" && issueDetails && (
|
||||||
|
<>
|
||||||
|
<ChangeIssueState
|
||||||
|
issue={issueDetails}
|
||||||
|
setIsPaletteOpen={setIsPaletteOpen}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{page === "change-issue-priority" && issueDetails && (
|
||||||
|
<ChangeIssuePriority
|
||||||
|
issue={issueDetails}
|
||||||
|
setIsPaletteOpen={setIsPaletteOpen}
|
||||||
/>
|
/>
|
||||||
<p className="mt-4 text-sm text-gray-900">
|
)}
|
||||||
We couldn{"'"}t find any issue with that term. Please try again.
|
{page === "change-issue-assignee" && issueDetails && (
|
||||||
</p>
|
<ChangeIssueAssignee
|
||||||
</div>
|
issue={issueDetails}
|
||||||
)}
|
setIsPaletteOpen={setIsPaletteOpen}
|
||||||
</Combobox>
|
/>
|
||||||
|
)}
|
||||||
<div className="flex items-center justify-end gap-2 p-3">
|
</Command.List>
|
||||||
<SecondaryButton onClick={handleCommandPaletteClose}>Close</SecondaryButton>
|
</Command>
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
</Dialog.Panel>
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,2 +1,5 @@
|
|||||||
export * from "./command-pallette";
|
export * from "./command-pallette";
|
||||||
export * from "./shortcuts-modal";
|
export * from "./shortcuts-modal";
|
||||||
|
export * from "./change-issue-state";
|
||||||
|
export * from "./change-issue-priority";
|
||||||
|
export * from "./change-issue-assignee";
|
||||||
|
@ -55,29 +55,22 @@ export const SingleModuleCard: React.FC<Props> = ({ module, handleEditModule })
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAddToFavorites = () => {
|
const handleAddToFavorites = () => {
|
||||||
if (!workspaceSlug && !projectId && !module) return;
|
if (!workspaceSlug || !projectId || !module) return;
|
||||||
|
|
||||||
|
mutate<IModule[]>(
|
||||||
|
MODULE_LIST(projectId as string),
|
||||||
|
(prevData) =>
|
||||||
|
(prevData ?? []).map((m) => ({
|
||||||
|
...m,
|
||||||
|
is_favorite: m.id === module.id ? true : m.is_favorite,
|
||||||
|
})),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
modulesService
|
modulesService
|
||||||
.addModuleToFavorites(workspaceSlug as string, projectId as string, {
|
.addModuleToFavorites(workspaceSlug as string, projectId as string, {
|
||||||
module: module.id,
|
module: module.id,
|
||||||
})
|
})
|
||||||
.then(() => {
|
|
||||||
mutate<IModule[]>(
|
|
||||||
MODULE_LIST(projectId as string),
|
|
||||||
(prevData) =>
|
|
||||||
(prevData ?? []).map((m) => ({
|
|
||||||
...m,
|
|
||||||
is_favorite: m.id === module.id ? true : m.is_favorite,
|
|
||||||
})),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
setToastAlert({
|
|
||||||
type: "success",
|
|
||||||
title: "Success!",
|
|
||||||
message: "Successfully added the module to favorites.",
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
@ -88,26 +81,20 @@ export const SingleModuleCard: React.FC<Props> = ({ module, handleEditModule })
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveFromFavorites = () => {
|
const handleRemoveFromFavorites = () => {
|
||||||
if (!workspaceSlug || !module) return;
|
if (!workspaceSlug || !projectId || !module) return;
|
||||||
|
|
||||||
|
mutate<IModule[]>(
|
||||||
|
MODULE_LIST(projectId as string),
|
||||||
|
(prevData) =>
|
||||||
|
(prevData ?? []).map((m) => ({
|
||||||
|
...m,
|
||||||
|
is_favorite: m.id === module.id ? false : m.is_favorite,
|
||||||
|
})),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
modulesService
|
modulesService
|
||||||
.removeModuleFromFavorites(workspaceSlug as string, projectId as string, module.id)
|
.removeModuleFromFavorites(workspaceSlug as string, projectId as string, module.id)
|
||||||
.then(() => {
|
|
||||||
mutate<IModule[]>(
|
|
||||||
MODULE_LIST(projectId as string),
|
|
||||||
(prevData) =>
|
|
||||||
(prevData ?? []).map((m) => ({
|
|
||||||
...m,
|
|
||||||
is_favorite: m.id === module.id ? false : m.is_favorite,
|
|
||||||
})),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
setToastAlert({
|
|
||||||
type: "success",
|
|
||||||
title: "Success!",
|
|
||||||
message: "Successfully removed the module from favorites.",
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
@ -161,11 +148,11 @@ export const SingleModuleCard: React.FC<Props> = ({ module, handleEditModule })
|
|||||||
<span className="capitalize">{module?.status?.replace("-", " ")}</span>
|
<span className="capitalize">{module?.status?.replace("-", " ")}</span>
|
||||||
</div>
|
</div>
|
||||||
{module.is_favorite ? (
|
{module.is_favorite ? (
|
||||||
<button onClick={handleRemoveFromFavorites}>
|
<button type="button" onClick={handleRemoveFromFavorites}>
|
||||||
<StarIcon className="h-4 w-4 text-orange-400" fill="#f6ad55" />
|
<StarIcon className="h-4 w-4 text-orange-400" fill="#f6ad55" />
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button onClick={handleAddToFavorites}>
|
<button type="button" onClick={handleAddToFavorites}>
|
||||||
<StarIcon className="h-4 w-4 " color="#858E96" />
|
<StarIcon className="h-4 w-4 " color="#858E96" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
@ -2,3 +2,4 @@ export * from "./delete-view-modal";
|
|||||||
export * from "./form";
|
export * from "./form";
|
||||||
export * from "./modal";
|
export * from "./modal";
|
||||||
export * from "./select-filters";
|
export * from "./select-filters";
|
||||||
|
export * from "./single-view-item"
|
||||||
|
132
apps/app/components/views/single-view-item.tsx
Normal file
132
apps/app/components/views/single-view-item.tsx
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { IView } from "types";
|
||||||
|
|
||||||
|
// icons
|
||||||
|
import { TrashIcon, StarIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { StackedLayersIcon } from "components/icons";
|
||||||
|
|
||||||
|
//components
|
||||||
|
import { CustomMenu } from "components/ui";
|
||||||
|
|
||||||
|
import viewsService from "services/views.service";
|
||||||
|
|
||||||
|
import { mutate } from "swr";
|
||||||
|
|
||||||
|
import { VIEWS_LIST } from "constants/fetch-keys";
|
||||||
|
|
||||||
|
import useToast from "hooks/use-toast";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
view: IView,
|
||||||
|
setSelectedView: React.Dispatch<React.SetStateAction<IView | null>>,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const SingleViewItem: React.FC<Props> = ({
|
||||||
|
view,
|
||||||
|
setSelectedView,
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
|
const handleAddToFavorites = () => {
|
||||||
|
if (!workspaceSlug || !projectId || !view) return;
|
||||||
|
|
||||||
|
mutate<IView[]>(
|
||||||
|
VIEWS_LIST(projectId as string),
|
||||||
|
(prevData) =>
|
||||||
|
(prevData ?? []).map((v) => ({
|
||||||
|
...v,
|
||||||
|
is_favorite: v.id === view.id ? true : v.is_favorite,
|
||||||
|
})),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
viewsService
|
||||||
|
.addViewToFavorites(workspaceSlug as string, projectId as string, {
|
||||||
|
view: view.id,
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: "Couldn't add the view to favorites. Please try again.",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveFromFavorites = () => {
|
||||||
|
if (!workspaceSlug || !view) return;
|
||||||
|
|
||||||
|
mutate<IView[]>(
|
||||||
|
VIEWS_LIST(projectId as string),
|
||||||
|
(prevData) =>
|
||||||
|
(prevData ?? []).map((v) => ({
|
||||||
|
...v,
|
||||||
|
is_favorite: v.id === view.id ? false : v.is_favorite,
|
||||||
|
})),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
viewsService
|
||||||
|
.removeViewFromFavorites(workspaceSlug as string, projectId as string, view.id)
|
||||||
|
.catch(() => {
|
||||||
|
setToastAlert({
|
||||||
|
type: "error",
|
||||||
|
title: "Error!",
|
||||||
|
message: "Couldn't remove the view from favorites. Please try again.",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-between border-b bg-white p-4 first:rounded-t-[10px] last:rounded-b-[10px]"
|
||||||
|
>
|
||||||
|
<div className="flex flex-col w-full gap-3">
|
||||||
|
<div className="flex justify-between w-full">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<StackedLayersIcon height={18} width={18} />
|
||||||
|
<Link href={`/${workspaceSlug}/projects/${projectId}/views/${view.id}`}>
|
||||||
|
<a>{view.name}</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
{
|
||||||
|
view.is_favorite ? (
|
||||||
|
<button type="button" onClick={handleRemoveFromFavorites}>
|
||||||
|
<StarIcon className="h-4 w-4 text-orange-400" fill="#f6ad55" />
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button type="button" onClick={handleAddToFavorites}>
|
||||||
|
<StarIcon className="h-4 w-4 " color="#858E96" />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<CustomMenu width="auto" verticalEllipsis>
|
||||||
|
<CustomMenu.MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedView(view);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="flex items-center justify-start gap-2 text-gray-800">
|
||||||
|
<TrashIcon className="h-4 w-4" />
|
||||||
|
<span>Delete</span>
|
||||||
|
</span>
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
</CustomMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{view?.description && <p className="text-sm text-[#858E96] font-normal leading-5 px-[27px]">
|
||||||
|
{view.description}
|
||||||
|
</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -36,7 +36,7 @@ export const IssuesList: React.FC<Props> = ({ issues, type }) => {
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="mb-2 font-semibold capitalize">{type} Issues</h3>
|
<h3 className="mb-2 font-semibold capitalize">{type} Issues</h3>
|
||||||
{issues ? (
|
{issues ? (
|
||||||
<div className="rounded-[10px] border bg-white p-4 text-sm">
|
<div className="rounded-[10px] border bg-white p-4 text-sm h-[calc(100%-2.25rem)]">
|
||||||
<div
|
<div
|
||||||
className={`mb-2 grid grid-cols-4 gap-2 rounded-lg px-3 py-2 font-medium ${
|
className={`mb-2 grid grid-cols-4 gap-2 rounded-lg px-3 py-2 font-medium ${
|
||||||
type === "overdue" ? "bg-red-100" : "bg-gray-100"
|
type === "overdue" ? "bg-red-100" : "bg-gray-100"
|
||||||
|
@ -80,7 +80,7 @@ export const IssuesPieChart: React.FC<Props> = ({ groupedIssues }) => {
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="mb-2 font-semibold">Issues by States</h3>
|
<h3 className="mb-2 font-semibold">Issues by States</h3>
|
||||||
<div className="rounded-[10px] border bg-white p-4">
|
<div className="rounded-[10px] border bg-white p-4">
|
||||||
<ResponsiveContainer width="100%" height={250}>
|
<ResponsiveContainer width="100%" height={320}>
|
||||||
<PieChart>
|
<PieChart>
|
||||||
<Pie
|
<Pie
|
||||||
data={groupedIssues}
|
data={groupedIssues}
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
"@types/lodash.debounce": "^4.0.7",
|
"@types/lodash.debounce": "^4.0.7",
|
||||||
"@types/react-datepicker": "^4.8.0",
|
"@types/react-datepicker": "^4.8.0",
|
||||||
"axios": "^1.1.3",
|
"axios": "^1.1.3",
|
||||||
|
"cmdk": "^0.2.0",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"next": "12.3.2",
|
"next": "12.3.2",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
@ -16,19 +15,18 @@ import projectService from "services/project.service";
|
|||||||
import AppLayout from "layouts/app-layout";
|
import AppLayout from "layouts/app-layout";
|
||||||
// ui
|
// ui
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// icons
|
|
||||||
import { TrashIcon } from "@heroicons/react/24/outline";
|
|
||||||
// image
|
// image
|
||||||
import emptyView from "public/empty-state/empty-view.svg";
|
import emptyView from "public/empty-state/empty-view.svg";
|
||||||
// fetching keys
|
// fetching keys
|
||||||
import { PROJECT_DETAILS, VIEWS_LIST } from "constants/fetch-keys";
|
import { PROJECT_DETAILS, VIEWS_LIST } from "constants/fetch-keys";
|
||||||
// components
|
// components
|
||||||
import { CustomMenu, PrimaryButton, Loader, EmptyState } from "components/ui";
|
import { PrimaryButton, Loader, EmptyState } from "components/ui";
|
||||||
import { DeleteViewModal, CreateUpdateViewModal } from "components/views";
|
import { DeleteViewModal, CreateUpdateViewModal, SingleViewItem } from "components/views";
|
||||||
|
|
||||||
// types
|
// types
|
||||||
import { IView, UserAuth } from "types";
|
import { IView, UserAuth } from "types";
|
||||||
import type { NextPage, GetServerSidePropsContext } from "next";
|
import type { NextPage, GetServerSidePropsContext } from "next";
|
||||||
import { StackedLayersIcon } from "components/icons";
|
|
||||||
|
|
||||||
const ProjectViews: NextPage<UserAuth> = (props) => {
|
const ProjectViews: NextPage<UserAuth> = (props) => {
|
||||||
const [isCreateViewModalOpen, setIsCreateViewModalOpen] = useState(false);
|
const [isCreateViewModalOpen, setIsCreateViewModalOpen] = useState(false);
|
||||||
@ -52,6 +50,8 @@ const ProjectViews: NextPage<UserAuth> = (props) => {
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log(views)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout
|
<AppLayout
|
||||||
meta={{
|
meta={{
|
||||||
@ -88,29 +88,11 @@ const ProjectViews: NextPage<UserAuth> = (props) => {
|
|||||||
<h3 className="text-3xl font-semibold text-black">Views</h3>
|
<h3 className="text-3xl font-semibold text-black">Views</h3>
|
||||||
<div className="rounded-[10px] border">
|
<div className="rounded-[10px] border">
|
||||||
{views.map((view) => (
|
{views.map((view) => (
|
||||||
<div
|
<SingleViewItem
|
||||||
className="flex items-center justify-between border-b bg-white p-4 first:rounded-t-[10px] last:rounded-b-[10px]"
|
|
||||||
key={view.id}
|
key={view.id}
|
||||||
>
|
view={view}
|
||||||
<div className="flex items-center gap-2">
|
setSelectedView={setSelectedView}
|
||||||
<StackedLayersIcon height={18} width={18} />
|
/>
|
||||||
<Link href={`/${workspaceSlug}/projects/${projectId}/views/${view.id}`}>
|
|
||||||
<a>{view.name}</a>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<CustomMenu width="auto" verticalEllipsis>
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedView(view);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="flex items-center justify-start gap-2 text-gray-800">
|
|
||||||
<TrashIcon className="h-4 w-4" />
|
|
||||||
<span>Delete</span>
|
|
||||||
</span>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
</CustomMenu>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,6 +3,7 @@ import dynamic from "next/dynamic";
|
|||||||
// styles
|
// styles
|
||||||
import "styles/globals.css";
|
import "styles/globals.css";
|
||||||
import "styles/editor.css";
|
import "styles/editor.css";
|
||||||
|
import "styles/command-pallette.css";
|
||||||
import "styles/nprogress.css";
|
import "styles/nprogress.css";
|
||||||
|
|
||||||
// router
|
// router
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// services
|
// services
|
||||||
import APIService from "services/api.service";
|
import APIService from "services/api.service";
|
||||||
// types
|
// types
|
||||||
import type { IIssueViewOptions, IModule, ModuleIssueResponse, IIssue } from "types";
|
import type { IIssueViewOptions, IModule, IIssue } from "types";
|
||||||
|
|
||||||
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
|
||||||
|
|
||||||
|
@ -80,6 +80,38 @@ class ViewServices extends APIService {
|
|||||||
throw error?.response?.data;
|
throw error?.response?.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addViewToFavorites(
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
data: {
|
||||||
|
view: string;
|
||||||
|
}
|
||||||
|
): Promise<any> {
|
||||||
|
return this.post(
|
||||||
|
`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-views/`,
|
||||||
|
data
|
||||||
|
)
|
||||||
|
.then((response) => response?.data)
|
||||||
|
.catch((error) => {
|
||||||
|
throw error?.response?.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeViewFromFavorites(
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
viewId: string
|
||||||
|
): Promise<any> {
|
||||||
|
return this.delete(
|
||||||
|
`/api/workspaces/${workspaceSlug}/projects/${projectId}/user-favorite-views/${viewId}/`
|
||||||
|
)
|
||||||
|
.then((response) => response?.data)
|
||||||
|
.catch((error) => {
|
||||||
|
throw error?.response?.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ViewServices();
|
export default new ViewServices();
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
ILastActiveWorkspaceDetails,
|
ILastActiveWorkspaceDetails,
|
||||||
IAppIntegrations,
|
IAppIntegrations,
|
||||||
IWorkspaceIntegrations,
|
IWorkspaceIntegrations,
|
||||||
|
IWorkspaceSearchResults,
|
||||||
} from "types";
|
} from "types";
|
||||||
|
|
||||||
class WorkspaceService extends APIService {
|
class WorkspaceService extends APIService {
|
||||||
@ -197,6 +198,20 @@ class WorkspaceService extends APIService {
|
|||||||
throw error?.response?.data;
|
throw error?.response?.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async searchWorkspace(
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
query: string
|
||||||
|
): Promise<IWorkspaceSearchResults> {
|
||||||
|
return this.get(
|
||||||
|
`/api/workspaces/${workspaceSlug}/projects/${projectId}/search/?search=${query}`
|
||||||
|
)
|
||||||
|
.then((res) => res?.data)
|
||||||
|
.catch((error) => {
|
||||||
|
throw error?.response?.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new WorkspaceService();
|
export default new WorkspaceService();
|
||||||
|
35
apps/app/styles/command-pallette.css
Normal file
35
apps/app/styles/command-pallette.css
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
[cmdk-group]:not(:first-child) {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[cmdk-group-heading] {
|
||||||
|
color: rgb(107 114 128);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin: 0 0 0.25rem 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[cmdk-item] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
font-size: 0.825rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
[cmdk-item] kbd {
|
||||||
|
height: 1.25rem;
|
||||||
|
width: 1.25rem;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
background-color: rgb(229 231 235);
|
||||||
|
}
|
||||||
|
|
||||||
|
[cmdk-item]:hover {
|
||||||
|
background-color: rgb(243 244 246);
|
||||||
|
}
|
1
apps/app/types/views.d.ts
vendored
1
apps/app/types/views.d.ts
vendored
@ -3,6 +3,7 @@ export interface IView {
|
|||||||
access: string;
|
access: string;
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
|
is_favorite: boolean;
|
||||||
created_by: string;
|
created_by: string;
|
||||||
updated_by: string;
|
updated_by: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
32
apps/app/types/workspace.d.ts
vendored
32
apps/app/types/workspace.d.ts
vendored
@ -44,3 +44,35 @@ export interface ILastActiveWorkspaceDetails {
|
|||||||
workspace_details: IWorkspace;
|
workspace_details: IWorkspace;
|
||||||
project_details?: IProjectMember[];
|
project_details?: IProjectMember[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IWorkspaceDefaultSearchResult {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
project_id: string;
|
||||||
|
workspace__slug: string;
|
||||||
|
}
|
||||||
|
export interface IWorkspaceSearchResult {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
slug: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWorkspaceIssueSearchResult {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
sequence_id: number;
|
||||||
|
project__identifier: string;
|
||||||
|
project_id: string;
|
||||||
|
workspace__slug: string;
|
||||||
|
}
|
||||||
|
export interface IWorkspaceSearchResults {
|
||||||
|
results: {
|
||||||
|
workspace: IWorkspaceSearchResult[];
|
||||||
|
project: IWorkspaceDefaultSearchResult[];
|
||||||
|
issue: IWorkspaceIssueSearchResult[];
|
||||||
|
cycle: IWorkspaceDefaultSearchResult[];
|
||||||
|
module: IWorkspaceDefaultSearchResult[];
|
||||||
|
issue_view: IWorkspaceDefaultSearchResult[];
|
||||||
|
page: IWorkspaceDefaultSearchResult[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
224
yarn.lock
224
yarn.lock
@ -1766,6 +1766,148 @@
|
|||||||
resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz"
|
resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz"
|
||||||
integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==
|
integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==
|
||||||
|
|
||||||
|
"@radix-ui/primitive@1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.0.tgz#e1d8ef30b10ea10e69c76e896f608d9276352253"
|
||||||
|
integrity sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
|
"@radix-ui/react-compose-refs@1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz#37595b1f16ec7f228d698590e78eeed18ff218ae"
|
||||||
|
integrity sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
|
"@radix-ui/react-context@1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.0.tgz#f38e30c5859a9fb5e9aa9a9da452ee3ed9e0aee0"
|
||||||
|
integrity sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
|
"@radix-ui/react-dialog@1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.0.0.tgz#997e97cb183bc90bd888b26b8e23a355ac9fe5f0"
|
||||||
|
integrity sha512-Yn9YU+QlHYLWwV1XfKiqnGVpWYWk6MeBVM6x/bcoyPvxgjQGoeT35482viLPctTMWoMw0PoHgqfSox7Ig+957Q==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/primitive" "1.0.0"
|
||||||
|
"@radix-ui/react-compose-refs" "1.0.0"
|
||||||
|
"@radix-ui/react-context" "1.0.0"
|
||||||
|
"@radix-ui/react-dismissable-layer" "1.0.0"
|
||||||
|
"@radix-ui/react-focus-guards" "1.0.0"
|
||||||
|
"@radix-ui/react-focus-scope" "1.0.0"
|
||||||
|
"@radix-ui/react-id" "1.0.0"
|
||||||
|
"@radix-ui/react-portal" "1.0.0"
|
||||||
|
"@radix-ui/react-presence" "1.0.0"
|
||||||
|
"@radix-ui/react-primitive" "1.0.0"
|
||||||
|
"@radix-ui/react-slot" "1.0.0"
|
||||||
|
"@radix-ui/react-use-controllable-state" "1.0.0"
|
||||||
|
aria-hidden "^1.1.1"
|
||||||
|
react-remove-scroll "2.5.4"
|
||||||
|
|
||||||
|
"@radix-ui/react-dismissable-layer@1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz#35b7826fa262fd84370faef310e627161dffa76b"
|
||||||
|
integrity sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/primitive" "1.0.0"
|
||||||
|
"@radix-ui/react-compose-refs" "1.0.0"
|
||||||
|
"@radix-ui/react-primitive" "1.0.0"
|
||||||
|
"@radix-ui/react-use-callback-ref" "1.0.0"
|
||||||
|
"@radix-ui/react-use-escape-keydown" "1.0.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-focus-guards@1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz#339c1c69c41628c1a5e655f15f7020bf11aa01fa"
|
||||||
|
integrity sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
|
"@radix-ui/react-focus-scope@1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.0.tgz#95a0c1188276dc8933b1eac5f1cdb6471e01ade5"
|
||||||
|
integrity sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-compose-refs" "1.0.0"
|
||||||
|
"@radix-ui/react-primitive" "1.0.0"
|
||||||
|
"@radix-ui/react-use-callback-ref" "1.0.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-id@1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.0.tgz#8d43224910741870a45a8c9d092f25887bb6d11e"
|
||||||
|
integrity sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-use-layout-effect" "1.0.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-portal@1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.0.tgz#7220b66743394fabb50c55cb32381395cc4a276b"
|
||||||
|
integrity sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-primitive" "1.0.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-presence@1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.0.tgz#814fe46df11f9a468808a6010e3f3ca7e0b2e84a"
|
||||||
|
integrity sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-compose-refs" "1.0.0"
|
||||||
|
"@radix-ui/react-use-layout-effect" "1.0.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-primitive@1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz#376cd72b0fcd5e0e04d252ed33eb1b1f025af2b0"
|
||||||
|
integrity sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-slot" "1.0.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-slot@1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.0.tgz#7fa805b99891dea1e862d8f8fbe07f4d6d0fd698"
|
||||||
|
integrity sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-compose-refs" "1.0.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-use-callback-ref@1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz#9e7b8b6b4946fe3cbe8f748c82a2cce54e7b6a90"
|
||||||
|
integrity sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
|
"@radix-ui/react-use-controllable-state@1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz#a64deaafbbc52d5d407afaa22d493d687c538b7f"
|
||||||
|
integrity sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-use-callback-ref" "1.0.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-use-escape-keydown@1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.0.tgz#aef375db4736b9de38a5a679f6f49b45a060e5d1"
|
||||||
|
integrity sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-use-callback-ref" "1.0.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-use-layout-effect@1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz#2fc19e97223a81de64cd3ba1dc42ceffd82374dc"
|
||||||
|
integrity sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
"@reach/auto-id@^0.16.0":
|
"@reach/auto-id@^0.16.0":
|
||||||
version "0.16.0"
|
version "0.16.0"
|
||||||
resolved "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.16.0.tgz"
|
resolved "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.16.0.tgz"
|
||||||
@ -3556,6 +3698,13 @@ argparse@^2.0.1:
|
|||||||
resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
|
resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
|
||||||
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
||||||
|
|
||||||
|
aria-hidden@^1.1.1:
|
||||||
|
version "1.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.3.tgz#14aeb7fb692bbb72d69bebfa47279c1fd725e954"
|
||||||
|
integrity sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.0.0"
|
||||||
|
|
||||||
aria-query@^5.1.3:
|
aria-query@^5.1.3:
|
||||||
version "5.1.3"
|
version "5.1.3"
|
||||||
resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz"
|
resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz"
|
||||||
@ -3965,6 +4114,14 @@ clsx@^1.2.0, clsx@^1.2.1:
|
|||||||
resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz"
|
resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz"
|
||||||
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
|
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
|
||||||
|
|
||||||
|
cmdk@^0.2.0:
|
||||||
|
version "0.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cmdk/-/cmdk-0.2.0.tgz#53c52d56d8776c8bb8ced1055b5054100c388f7c"
|
||||||
|
integrity sha512-JQpKvEOb86SnvMZbYaFKYhvzFntWBeSZdyii0rZPhKJj9uwJBxu4DaVYDrRN7r3mPop56oPhRw+JYWTKs66TYw==
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/react-dialog" "1.0.0"
|
||||||
|
command-score "0.1.2"
|
||||||
|
|
||||||
code-point-at@^1.0.0:
|
code-point-at@^1.0.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz"
|
resolved "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz"
|
||||||
@ -4021,6 +4178,11 @@ comma-separated-tokens@^2.0.0:
|
|||||||
resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz"
|
resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz"
|
||||||
integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
|
integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
|
||||||
|
|
||||||
|
command-score@0.1.2:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/command-score/-/command-score-0.1.2.tgz#b986ad7e8c0beba17552a56636c44ae38363d381"
|
||||||
|
integrity sha512-VtDvQpIJBvBatnONUsPzXYFVKQQAhuf3XTNOAsdBxCNO/QCtUUd8LSgjn0GVarBkCad6aJCZfXgrjYbl/KRr7w==
|
||||||
|
|
||||||
commander@^2.20.0:
|
commander@^2.20.0:
|
||||||
version "2.20.3"
|
version "2.20.3"
|
||||||
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz"
|
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz"
|
||||||
@ -4356,6 +4518,11 @@ dequal@^2.0.0:
|
|||||||
resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz"
|
resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz"
|
||||||
integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
|
integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
|
||||||
|
|
||||||
|
detect-node-es@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493"
|
||||||
|
integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==
|
||||||
|
|
||||||
detective@^5.2.1:
|
detective@^5.2.1:
|
||||||
version "5.2.1"
|
version "5.2.1"
|
||||||
resolved "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz"
|
resolved "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz"
|
||||||
@ -5341,6 +5508,11 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3:
|
|||||||
has "^1.0.3"
|
has "^1.0.3"
|
||||||
has-symbols "^1.0.3"
|
has-symbols "^1.0.3"
|
||||||
|
|
||||||
|
get-nonce@^1.0.0:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3"
|
||||||
|
integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==
|
||||||
|
|
||||||
get-own-enumerable-property-symbols@^3.0.0:
|
get-own-enumerable-property-symbols@^3.0.0:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz"
|
resolved "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz"
|
||||||
@ -5680,6 +5852,13 @@ internal-slot@^1.0.3, internal-slot@^1.0.4:
|
|||||||
resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
|
resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
|
||||||
integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
|
integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
|
||||||
|
|
||||||
|
invariant@^2.2.4:
|
||||||
|
version "2.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
|
||||||
|
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
|
||||||
|
dependencies:
|
||||||
|
loose-envify "^1.0.0"
|
||||||
|
|
||||||
is-alphabetical@^1.0.0:
|
is-alphabetical@^1.0.0:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz"
|
resolved "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz"
|
||||||
@ -7831,6 +8010,25 @@ react-redux@^7.2.0:
|
|||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
react-is "^17.0.2"
|
react-is "^17.0.2"
|
||||||
|
|
||||||
|
react-remove-scroll-bar@^2.3.3:
|
||||||
|
version "2.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz#53e272d7a5cb8242990c7f144c44d8bd8ab5afd9"
|
||||||
|
integrity sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==
|
||||||
|
dependencies:
|
||||||
|
react-style-singleton "^2.2.1"
|
||||||
|
tslib "^2.0.0"
|
||||||
|
|
||||||
|
react-remove-scroll@2.5.4:
|
||||||
|
version "2.5.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.4.tgz#afe6491acabde26f628f844b67647645488d2ea0"
|
||||||
|
integrity sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==
|
||||||
|
dependencies:
|
||||||
|
react-remove-scroll-bar "^2.3.3"
|
||||||
|
react-style-singleton "^2.2.1"
|
||||||
|
tslib "^2.1.0"
|
||||||
|
use-callback-ref "^1.3.0"
|
||||||
|
use-sidecar "^1.1.2"
|
||||||
|
|
||||||
react-resize-detector@^7.1.2:
|
react-resize-detector@^7.1.2:
|
||||||
version "7.1.2"
|
version "7.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-7.1.2.tgz#8ef975dd8c3d56f9a5160ac382ef7136dcd2d86c"
|
resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-7.1.2.tgz#8ef975dd8c3d56f9a5160ac382ef7136dcd2d86c"
|
||||||
@ -7846,6 +8044,15 @@ react-smooth@^2.0.1:
|
|||||||
fast-equals "^4.0.3"
|
fast-equals "^4.0.3"
|
||||||
react-transition-group "2.9.0"
|
react-transition-group "2.9.0"
|
||||||
|
|
||||||
|
react-style-singleton@^2.2.1:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4"
|
||||||
|
integrity sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==
|
||||||
|
dependencies:
|
||||||
|
get-nonce "^1.0.0"
|
||||||
|
invariant "^2.2.4"
|
||||||
|
tslib "^2.0.0"
|
||||||
|
|
||||||
react-transition-group@2.9.0:
|
react-transition-group@2.9.0:
|
||||||
version "2.9.0"
|
version "2.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"
|
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"
|
||||||
@ -8875,7 +9082,7 @@ tslib@^1.8.1, tslib@^1.9.3:
|
|||||||
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
|
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
|
||||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||||
|
|
||||||
tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0:
|
tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0:
|
||||||
version "2.5.0"
|
version "2.5.0"
|
||||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz"
|
resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz"
|
||||||
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
|
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
|
||||||
@ -9145,6 +9352,13 @@ uri-js@^4.2.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
punycode "^2.1.0"
|
punycode "^2.1.0"
|
||||||
|
|
||||||
|
use-callback-ref@^1.3.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.0.tgz#772199899b9c9a50526fedc4993fc7fa1f7e32d5"
|
||||||
|
integrity sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.0.0"
|
||||||
|
|
||||||
use-isomorphic-layout-effect@^1.1.0, use-isomorphic-layout-effect@^1.1.1:
|
use-isomorphic-layout-effect@^1.1.0, use-isomorphic-layout-effect@^1.1.1:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz"
|
resolved "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz"
|
||||||
@ -9162,6 +9376,14 @@ use-previous@^1.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
use-isomorphic-layout-effect "^1.1.0"
|
use-isomorphic-layout-effect "^1.1.0"
|
||||||
|
|
||||||
|
use-sidecar@^1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2"
|
||||||
|
integrity sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==
|
||||||
|
dependencies:
|
||||||
|
detect-node-es "^1.1.0"
|
||||||
|
tslib "^2.0.0"
|
||||||
|
|
||||||
use-sync-external-store@1.2.0:
|
use-sync-external-store@1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"
|
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"
|
||||||
|
Loading…
Reference in New Issue
Block a user