chore: filter edit operation in views is disabled for lower roles (#3113)

* chore: edit/delete view options hidden for lower roles

* chore: project -> views access restriction for lower roles

* refactor: allowance condition
This commit is contained in:
Lakhan Baheti 2023-12-14 16:49:36 +05:30 committed by GitHub
parent aafac9ed1d
commit 4e2bf24e8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 99 additions and 61 deletions

View File

@ -155,7 +155,8 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
onChange={(layout) => handleLayoutChange(layout)} onChange={(layout) => handleLayoutChange(layout)}
selectedLayout={activeLayout} selectedLayout={activeLayout}
/> />
<FiltersDropdown title="Filters" placement="bottom-end">
<FiltersDropdown title="Filters" placement="bottom-end" disabled={!canUserCreateIssue}>
<FilterSelection <FilterSelection
filters={issueFilters?.filters ?? {}} filters={issueFilters?.filters ?? {}}
handleFiltersUpdate={handleFiltersUpdate} handleFiltersUpdate={handleFiltersUpdate}

View File

@ -1,5 +1,5 @@
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { import {
AppliedDateFilters, AppliedDateFilters,
@ -16,6 +16,8 @@ import { X } from "lucide-react";
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
// types // types
import { IIssueFilterOptions, IIssueLabel, IProject, IState, IUserLite } from "types"; import { IIssueFilterOptions, IIssueLabel, IProject, IState, IUserLite } from "types";
// constants
import { EUserWorkspaceRoles } from "constants/workspace";
type Props = { type Props = {
appliedFilters: IIssueFilterOptions; appliedFilters: IIssueFilterOptions;
@ -33,10 +35,16 @@ const dateFilters = ["start_date", "target_date"];
export const AppliedFiltersList: React.FC<Props> = observer((props) => { export const AppliedFiltersList: React.FC<Props> = observer((props) => {
const { appliedFilters, handleClearAllFilters, handleRemoveFilter, labels, members, projects, states } = props; const { appliedFilters, handleClearAllFilters, handleRemoveFilter, labels, members, projects, states } = props;
const {
user: { currentProjectRole },
} = useMobxStore();
if (!appliedFilters) return null; if (!appliedFilters) return null;
if (Object.keys(appliedFilters).length === 0) return null; if (Object.keys(appliedFilters).length === 0) return null;
const isEditingAllowed = currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
return ( return (
<div className="flex flex-wrap items-stretch gap-2 bg-custom-background-100"> <div className="flex flex-wrap items-stretch gap-2 bg-custom-background-100">
{Object.entries(appliedFilters).map(([key, value]) => { {Object.entries(appliedFilters).map(([key, value]) => {
@ -53,6 +61,7 @@ export const AppliedFiltersList: React.FC<Props> = observer((props) => {
<div className="flex flex-wrap items-center gap-1"> <div className="flex flex-wrap items-center gap-1">
{membersFilters.includes(filterKey) && ( {membersFilters.includes(filterKey) && (
<AppliedMembersFilters <AppliedMembersFilters
editable={isEditingAllowed}
handleRemove={(val) => handleRemoveFilter(filterKey, val)} handleRemove={(val) => handleRemoveFilter(filterKey, val)}
members={members} members={members}
values={value} values={value}
@ -63,16 +72,22 @@ export const AppliedFiltersList: React.FC<Props> = observer((props) => {
)} )}
{filterKey === "labels" && ( {filterKey === "labels" && (
<AppliedLabelsFilters <AppliedLabelsFilters
editable={isEditingAllowed}
handleRemove={(val) => handleRemoveFilter("labels", val)} handleRemove={(val) => handleRemoveFilter("labels", val)}
labels={labels} labels={labels}
values={value} values={value}
/> />
)} )}
{filterKey === "priority" && ( {filterKey === "priority" && (
<AppliedPriorityFilters handleRemove={(val) => handleRemoveFilter("priority", val)} values={value} /> <AppliedPriorityFilters
editable={isEditingAllowed}
handleRemove={(val) => handleRemoveFilter("priority", val)}
values={value}
/>
)} )}
{filterKey === "state" && states && ( {filterKey === "state" && states && (
<AppliedStateFilters <AppliedStateFilters
editable={isEditingAllowed}
handleRemove={(val) => handleRemoveFilter("state", val)} handleRemove={(val) => handleRemoveFilter("state", val)}
states={states} states={states}
values={value} values={value}
@ -86,11 +101,13 @@ export const AppliedFiltersList: React.FC<Props> = observer((props) => {
)} )}
{filterKey === "project" && ( {filterKey === "project" && (
<AppliedProjectFilters <AppliedProjectFilters
editable={isEditingAllowed}
handleRemove={(val) => handleRemoveFilter("project", val)} handleRemove={(val) => handleRemoveFilter("project", val)}
projects={projects} projects={projects}
values={value} values={value}
/> />
)} )}
{isEditingAllowed && (
<button <button
type="button" type="button"
className="grid place-items-center text-custom-text-300 hover:text-custom-text-200" className="grid place-items-center text-custom-text-300 hover:text-custom-text-200"
@ -98,10 +115,12 @@ export const AppliedFiltersList: React.FC<Props> = observer((props) => {
> >
<X size={12} strokeWidth={2} /> <X size={12} strokeWidth={2} />
</button> </button>
)}
</div> </div>
</div> </div>
); );
})} })}
{isEditingAllowed && (
<button <button
type="button" type="button"
onClick={handleClearAllFilters} onClick={handleClearAllFilters}
@ -110,6 +129,7 @@ export const AppliedFiltersList: React.FC<Props> = observer((props) => {
Clear all Clear all
<X size={12} strokeWidth={2} /> <X size={12} strokeWidth={2} />
</button> </button>
)}
</div> </div>
); );
}); });

View File

@ -9,10 +9,11 @@ type Props = {
handleRemove: (val: string) => void; handleRemove: (val: string) => void;
labels: IIssueLabel[] | undefined; labels: IIssueLabel[] | undefined;
values: string[]; values: string[];
editable: boolean | undefined;
}; };
export const AppliedLabelsFilters: React.FC<Props> = observer((props) => { export const AppliedLabelsFilters: React.FC<Props> = observer((props) => {
const { handleRemove, labels, values } = props; const { handleRemove, labels, values, editable } = props;
return ( return (
<> <>
@ -30,6 +31,7 @@ export const AppliedLabelsFilters: React.FC<Props> = observer((props) => {
}} }}
/> />
<span className="normal-case">{labelDetails.name}</span> <span className="normal-case">{labelDetails.name}</span>
{editable && (
<button <button
type="button" type="button"
className="grid place-items-center text-custom-text-300 hover:text-custom-text-200" className="grid place-items-center text-custom-text-300 hover:text-custom-text-200"
@ -37,6 +39,7 @@ export const AppliedLabelsFilters: React.FC<Props> = observer((props) => {
> >
<X size={10} strokeWidth={2} /> <X size={10} strokeWidth={2} />
</button> </button>
)}
</div> </div>
); );
})} })}

View File

@ -9,10 +9,11 @@ type Props = {
handleRemove: (val: string) => void; handleRemove: (val: string) => void;
members: IUserLite[] | undefined; members: IUserLite[] | undefined;
values: string[]; values: string[];
editable: boolean | undefined;
}; };
export const AppliedMembersFilters: React.FC<Props> = observer((props) => { export const AppliedMembersFilters: React.FC<Props> = observer((props) => {
const { handleRemove, members, values } = props; const { handleRemove, members, values, editable } = props;
return ( return (
<> <>
@ -25,6 +26,7 @@ export const AppliedMembersFilters: React.FC<Props> = observer((props) => {
<div key={memberId} className="flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs"> <div key={memberId} className="flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs">
<Avatar name={memberDetails.display_name} src={memberDetails.avatar} showTooltip={false} /> <Avatar name={memberDetails.display_name} src={memberDetails.avatar} showTooltip={false} />
<span className="normal-case">{memberDetails.display_name}</span> <span className="normal-case">{memberDetails.display_name}</span>
{editable && (
<button <button
type="button" type="button"
className="grid place-items-center text-custom-text-300 hover:text-custom-text-200" className="grid place-items-center text-custom-text-300 hover:text-custom-text-200"
@ -32,6 +34,7 @@ export const AppliedMembersFilters: React.FC<Props> = observer((props) => {
> >
<X size={10} strokeWidth={2} /> <X size={10} strokeWidth={2} />
</button> </button>
)}
</div> </div>
); );
})} })}

View File

@ -9,10 +9,11 @@ import { TIssuePriorities } from "types";
type Props = { type Props = {
handleRemove: (val: string) => void; handleRemove: (val: string) => void;
values: string[]; values: string[];
editable: boolean | undefined;
}; };
export const AppliedPriorityFilters: React.FC<Props> = observer((props) => { export const AppliedPriorityFilters: React.FC<Props> = observer((props) => {
const { handleRemove, values } = props; const { handleRemove, values, editable } = props;
return ( return (
<> <>
@ -20,6 +21,7 @@ export const AppliedPriorityFilters: React.FC<Props> = observer((props) => {
<div key={priority} className="flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs"> <div key={priority} className="flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs">
<PriorityIcon priority={priority as TIssuePriorities} className={`h-3 w-3`} /> <PriorityIcon priority={priority as TIssuePriorities} className={`h-3 w-3`} />
{priority} {priority}
{editable && (
<button <button
type="button" type="button"
className="grid place-items-center text-custom-text-300 hover:text-custom-text-200" className="grid place-items-center text-custom-text-300 hover:text-custom-text-200"
@ -27,6 +29,7 @@ export const AppliedPriorityFilters: React.FC<Props> = observer((props) => {
> >
<X size={10} strokeWidth={2} /> <X size={10} strokeWidth={2} />
</button> </button>
)}
</div> </div>
))} ))}
</> </>

View File

@ -10,10 +10,11 @@ type Props = {
handleRemove: (val: string) => void; handleRemove: (val: string) => void;
projects: IProject[] | undefined; projects: IProject[] | undefined;
values: string[]; values: string[];
editable: boolean | undefined;
}; };
export const AppliedProjectFilters: React.FC<Props> = observer((props) => { export const AppliedProjectFilters: React.FC<Props> = observer((props) => {
const { handleRemove, projects, values } = props; const { handleRemove, projects, values, editable } = props;
return ( return (
<> <>
@ -34,6 +35,7 @@ export const AppliedProjectFilters: React.FC<Props> = observer((props) => {
</span> </span>
)} )}
<span className="normal-case">{projectDetails.name}</span> <span className="normal-case">{projectDetails.name}</span>
{editable && (
<button <button
type="button" type="button"
className="grid place-items-center text-custom-text-300 hover:text-custom-text-200" className="grid place-items-center text-custom-text-300 hover:text-custom-text-200"
@ -41,6 +43,7 @@ export const AppliedProjectFilters: React.FC<Props> = observer((props) => {
> >
<X size={10} strokeWidth={2} /> <X size={10} strokeWidth={2} />
</button> </button>
)}
</div> </div>
); );
})} })}

View File

@ -10,10 +10,11 @@ type Props = {
handleRemove: (val: string) => void; handleRemove: (val: string) => void;
states: IState[]; states: IState[];
values: string[]; values: string[];
editable: boolean | undefined;
}; };
export const AppliedStateFilters: React.FC<Props> = observer((props) => { export const AppliedStateFilters: React.FC<Props> = observer((props) => {
const { handleRemove, states, values } = props; const { handleRemove, states, values, editable } = props;
return ( return (
<> <>
@ -26,6 +27,7 @@ export const AppliedStateFilters: React.FC<Props> = observer((props) => {
<div key={stateId} className="flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs"> <div key={stateId} className="flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs">
<StateGroupIcon color={stateDetails.color} stateGroup={stateDetails.group} height="12px" width="12px" /> <StateGroupIcon color={stateDetails.color} stateGroup={stateDetails.group} height="12px" width="12px" />
{stateDetails.name} {stateDetails.name}
{editable && (
<button <button
type="button" type="button"
className="grid place-items-center text-custom-text-300 hover:text-custom-text-200" className="grid place-items-center text-custom-text-300 hover:text-custom-text-200"
@ -33,6 +35,7 @@ export const AppliedStateFilters: React.FC<Props> = observer((props) => {
> >
<X size={10} strokeWidth={2} /> <X size={10} strokeWidth={2} />
</button> </button>
)}
</div> </div>
); );
})} })}

View File

@ -11,10 +11,11 @@ type Props = {
children: React.ReactNode; children: React.ReactNode;
title?: string; title?: string;
placement?: Placement; placement?: Placement;
disabled?: boolean;
}; };
export const FiltersDropdown: React.FC<Props> = (props) => { export const FiltersDropdown: React.FC<Props> = (props) => {
const { children, title = "Dropdown", placement } = props; const { children, title = "Dropdown", placement, disabled = false } = props;
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null); const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null); const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
@ -32,6 +33,7 @@ export const FiltersDropdown: React.FC<Props> = (props) => {
<> <>
<Popover.Button as={React.Fragment}> <Popover.Button as={React.Fragment}>
<Button <Button
disabled={disabled}
ref={setReferenceElement} ref={setReferenceElement}
variant="neutral-primary" variant="neutral-primary"
size="sm" size="sm"

View File

@ -56,11 +56,11 @@ export const CreateUpdateProjectViewModal: FC<Props> = observer((props) => {
await projectViewsStore await projectViewsStore
.updateView(workspaceSlug, projectId, data?.id as string, payload) .updateView(workspaceSlug, projectId, data?.id as string, payload)
.then(() => handleClose()) .then(() => handleClose())
.catch(() => .catch((err) =>
setToastAlert({ setToastAlert({
type: "error", type: "error",
title: "Error!", title: "Error!",
message: "Something went wrong. Please try again.", message: err.detail ?? "Something went wrong. Please try again.",
}) })
); );
}; };