chore: issue sidebar and project view improvement and validation (#3098)

* chore: project view header button validation

* chore: copy view link option added in project view list item and role action validation added

* chore: issue sidebar improvement
This commit is contained in:
Anmol Singh Bhatia 2023-12-13 23:04:33 +05:30 committed by GitHub
parent b78e83d81b
commit fe80ca3e1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 112 additions and 64 deletions

View File

@ -173,7 +173,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
handleDisplayPropertiesUpdate={handleDisplayProperties} handleDisplayPropertiesUpdate={handleDisplayProperties}
/> />
</FiltersDropdown> </FiltersDropdown>
{ {canUserCreateIssue && (
<Button <Button
onClick={() => { onClick={() => {
setTrackElement("PROJECT_VIEW_PAGE_HEADER"); setTrackElement("PROJECT_VIEW_PAGE_HEADER");
@ -184,7 +184,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
> >
Add Issue Add Issue
</Button> </Button>
} )}
</div> </div>
</div> </div>
); );

View File

@ -7,15 +7,24 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { Breadcrumbs, PhotoFilterIcon, Button } from "@plane/ui"; import { Breadcrumbs, PhotoFilterIcon, Button } from "@plane/ui";
// helpers // helpers
import { renderEmoji } from "helpers/emoji.helper"; import { renderEmoji } from "helpers/emoji.helper";
// constants
import { EUserWorkspaceRoles } from "constants/workspace";
export const ProjectViewsHeader: React.FC = observer(() => { export const ProjectViewsHeader: React.FC = observer(() => {
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
const { project: projectStore, commandPalette } = useMobxStore(); const {
project: projectStore,
commandPalette,
user: { currentProjectRole },
} = useMobxStore();
const { currentProjectDetails } = projectStore; const { currentProjectDetails } = projectStore;
const canUserCreateIssue =
currentProjectRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentProjectRole);
return ( return (
<> <>
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4"> <div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
@ -50,6 +59,7 @@ export const ProjectViewsHeader: React.FC = observer(() => {
</Breadcrumbs> </Breadcrumbs>
</div> </div>
</div> </div>
{canUserCreateIssue && (
<div className="flex flex-shrink-0 items-center gap-2"> <div className="flex flex-shrink-0 items-center gap-2">
<div> <div>
<Button <Button
@ -62,6 +72,7 @@ export const ProjectViewsHeader: React.FC = observer(() => {
</Button> </Button>
</div> </div>
</div> </div>
)}
</div> </div>
</> </>
); );

View File

@ -60,7 +60,9 @@ export const SidebarAssigneeSelect: React.FC<Props> = ({ value, onChange, disabl
) : ( ) : (
<button <button
type="button" type="button"
className="rounded bg-custom-background-80 px-2.5 py-0.5 text-xs text-custom-text-200" className={`rounded bg-custom-background-80 px-2.5 py-0.5 text-xs text-custom-text-200 ${
disabled ? "cursor-not-allowed" : ""
}`}
> >
No assignees No assignees
</button> </button>

View File

@ -572,7 +572,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
labelList={issueDetail?.labels ?? []} labelList={issueDetail?.labels ?? []}
submitChanges={submitChanges} submitChanges={submitChanges}
isNotAllowed={!isAllowed} isNotAllowed={!isAllowed}
uneditable={uneditable ?? false} uneditable={uneditable || !isAllowed}
/> />
</div> </div>
</div> </div>

View File

@ -2,17 +2,22 @@ import React, { useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { PencilIcon, StarIcon, TrashIcon } from "lucide-react"; import { LinkIcon, PencilIcon, StarIcon, TrashIcon } from "lucide-react";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import useToast from "hooks/use-toast";
// components // components
import { CreateUpdateProjectViewModal, DeleteProjectViewModal } from "components/views"; import { CreateUpdateProjectViewModal, DeleteProjectViewModal } from "components/views";
// ui // ui
import { CustomMenu, PhotoFilterIcon } from "@plane/ui"; import { CustomMenu, PhotoFilterIcon } from "@plane/ui";
// helpers // helpers
import { calculateTotalFilters } from "helpers/filter.helper"; import { calculateTotalFilters } from "helpers/filter.helper";
import { copyUrlToClipboard } from "helpers/string.helper";
// types // types
import { IProjectView } from "types"; import { IProjectView } from "types";
// constants
import { EUserWorkspaceRoles } from "constants/workspace";
type Props = { type Props = {
view: IProjectView; view: IProjectView;
@ -27,7 +32,12 @@ export const ProjectViewListItem: React.FC<Props> = observer((props) => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
const { projectViews: projectViewsStore } = useMobxStore(); const { setToastAlert } = useToast();
const {
projectViews: projectViewsStore,
user: { currentProjectRole },
} = useMobxStore();
const handleAddToFavorites = () => { const handleAddToFavorites = () => {
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId) return;
@ -41,8 +51,22 @@ export const ProjectViewListItem: React.FC<Props> = observer((props) => {
projectViewsStore.removeViewFromFavorites(workspaceSlug.toString(), projectId.toString(), view.id); projectViewsStore.removeViewFromFavorites(workspaceSlug.toString(), projectId.toString(), view.id);
}; };
const handleCopyText = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
e.preventDefault();
copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/views/${view.id}`).then(() => {
setToastAlert({
type: "success",
title: "Link Copied!",
message: "View link copied to clipboard.",
});
});
};
const totalFilters = calculateTotalFilters(view.query_data ?? {}); const totalFilters = calculateTotalFilters(view.query_data ?? {});
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
return ( return (
<> <>
{workspaceSlug && projectId && view && ( {workspaceSlug && projectId && view && (
@ -73,8 +97,8 @@ export const ProjectViewListItem: React.FC<Props> = observer((props) => {
<p className="hidden rounded bg-custom-background-80 px-2 py-1 text-xs text-custom-text-200 group-hover:block"> <p className="hidden rounded bg-custom-background-80 px-2 py-1 text-xs text-custom-text-200 group-hover:block">
{totalFilters} {totalFilters === 1 ? "filter" : "filters"} {totalFilters} {totalFilters === 1 ? "filter" : "filters"}
</p> </p>
{isEditingAllowed &&
{view.is_favorite ? ( (view.is_favorite ? (
<button <button
type="button" type="button"
onClick={(e) => { onClick={(e) => {
@ -98,8 +122,11 @@ export const ProjectViewListItem: React.FC<Props> = observer((props) => {
> >
<StarIcon size={14} strokeWidth={2} /> <StarIcon size={14} strokeWidth={2} />
</button> </button>
)} ))}
<CustomMenu width="auto" ellipsis> <CustomMenu width="auto" ellipsis>
{isEditingAllowed && (
<>
<CustomMenu.MenuItem <CustomMenu.MenuItem
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
@ -124,6 +151,14 @@ export const ProjectViewListItem: React.FC<Props> = observer((props) => {
<span>Delete View</span> <span>Delete View</span>
</span> </span>
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
</>
)}
<CustomMenu.MenuItem onClick={handleCopyText}>
<span className="flex items-center justify-start gap-2">
<LinkIcon className="h-3 w-3" />
<span>Copy view link</span>
</span>
</CustomMenu.MenuItem>
</CustomMenu> </CustomMenu>
</div> </div>
</div> </div>