forked from github/plane
fix: corrected rendering of workspace-level labels, members, and states in project view (#2966)
* fix: dynamic issue properties filters in project, workspace and profile level * clean-up: removed logs from the project store
This commit is contained in:
parent
0ff5f9ef62
commit
ef14f00777
@ -89,6 +89,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
|
|||||||
<IssuePropertyState
|
<IssuePropertyState
|
||||||
projectId={issue?.project_detail?.id || null}
|
projectId={issue?.project_detail?.id || null}
|
||||||
value={issue?.state || null}
|
value={issue?.state || null}
|
||||||
|
defaultOptions={issue?.state_detail ? [issue.state_detail] : []}
|
||||||
onChange={handleState}
|
onChange={handleState}
|
||||||
disabled={isReadOnly}
|
disabled={isReadOnly}
|
||||||
hideDropdownArrow
|
hideDropdownArrow
|
||||||
@ -110,6 +111,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
|
|||||||
<IssuePropertyLabels
|
<IssuePropertyLabels
|
||||||
projectId={issue?.project_detail?.id || null}
|
projectId={issue?.project_detail?.id || null}
|
||||||
value={issue?.labels || null}
|
value={issue?.labels || null}
|
||||||
|
defaultOptions={issue?.label_details ? issue.label_details : []}
|
||||||
onChange={handleLabel}
|
onChange={handleLabel}
|
||||||
disabled={isReadOnly}
|
disabled={isReadOnly}
|
||||||
hideDropdownArrow
|
hideDropdownArrow
|
||||||
@ -141,6 +143,7 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
|
|||||||
<IssuePropertyAssignee
|
<IssuePropertyAssignee
|
||||||
projectId={issue?.project_detail?.id || null}
|
projectId={issue?.project_detail?.id || null}
|
||||||
value={issue?.assignees || null}
|
value={issue?.assignees || null}
|
||||||
|
defaultOptions={issue?.assignee_details ? issue.assignee_details : []}
|
||||||
hideDropdownArrow
|
hideDropdownArrow
|
||||||
onChange={handleAssignee}
|
onChange={handleAssignee}
|
||||||
disabled={isReadOnly}
|
disabled={isReadOnly}
|
||||||
|
@ -60,6 +60,7 @@ export const ListProperties: FC<IListProperties> = observer((props) => {
|
|||||||
<IssuePropertyState
|
<IssuePropertyState
|
||||||
projectId={issue?.project_detail?.id || null}
|
projectId={issue?.project_detail?.id || null}
|
||||||
value={issue?.state || null}
|
value={issue?.state || null}
|
||||||
|
defaultOptions={issue?.state_detail ? [issue.state_detail] : []}
|
||||||
hideDropdownArrow
|
hideDropdownArrow
|
||||||
onChange={handleState}
|
onChange={handleState}
|
||||||
disabled={isReadonly}
|
disabled={isReadonly}
|
||||||
@ -81,6 +82,7 @@ export const ListProperties: FC<IListProperties> = observer((props) => {
|
|||||||
<IssuePropertyLabels
|
<IssuePropertyLabels
|
||||||
projectId={issue?.project_detail?.id || null}
|
projectId={issue?.project_detail?.id || null}
|
||||||
value={issue?.labels || null}
|
value={issue?.labels || null}
|
||||||
|
defaultOptions={issue?.label_details ? issue.label_details : []}
|
||||||
onChange={handleLabel}
|
onChange={handleLabel}
|
||||||
disabled={isReadonly}
|
disabled={isReadonly}
|
||||||
hideDropdownArrow
|
hideDropdownArrow
|
||||||
@ -92,6 +94,7 @@ export const ListProperties: FC<IListProperties> = observer((props) => {
|
|||||||
<IssuePropertyAssignee
|
<IssuePropertyAssignee
|
||||||
projectId={issue?.project_detail?.id || null}
|
projectId={issue?.project_detail?.id || null}
|
||||||
value={issue?.assignees || null}
|
value={issue?.assignees || null}
|
||||||
|
defaultOptions={issue?.assignee_details ? issue.assignee_details : []}
|
||||||
hideDropdownArrow
|
hideDropdownArrow
|
||||||
onChange={handleAssignee}
|
onChange={handleAssignee}
|
||||||
disabled={isReadonly}
|
disabled={isReadonly}
|
||||||
|
@ -8,10 +8,12 @@ import { Check, ChevronDown, Search, User2 } from "lucide-react";
|
|||||||
import { Avatar, AvatarGroup, Tooltip } from "@plane/ui";
|
import { Avatar, AvatarGroup, Tooltip } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { Placement } from "@popperjs/core";
|
import { Placement } from "@popperjs/core";
|
||||||
|
import { IProjectMember } from "types";
|
||||||
|
|
||||||
export interface IIssuePropertyAssignee {
|
export interface IIssuePropertyAssignee {
|
||||||
projectId: string | null;
|
projectId: string | null;
|
||||||
value: string[] | string;
|
value: string[] | string;
|
||||||
|
defaultOptions?: any;
|
||||||
onChange: (data: string[]) => void;
|
onChange: (data: string[]) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
hideDropdownArrow?: boolean;
|
hideDropdownArrow?: boolean;
|
||||||
@ -27,6 +29,7 @@ export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer(
|
|||||||
const {
|
const {
|
||||||
projectId,
|
projectId,
|
||||||
value,
|
value,
|
||||||
|
defaultOptions = [],
|
||||||
onChange,
|
onChange,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
hideDropdownArrow = false,
|
hideDropdownArrow = false,
|
||||||
@ -40,8 +43,7 @@ export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer(
|
|||||||
// store
|
// store
|
||||||
const {
|
const {
|
||||||
workspace: workspaceStore,
|
workspace: workspaceStore,
|
||||||
project: projectStore,
|
projectMember: { projectMembers: _projectMembers, fetchProjectMembers },
|
||||||
workspaceMember: { workspaceMembers, fetchWorkspaceMembers },
|
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
const workspaceSlug = workspaceStore?.workspaceSlug;
|
const workspaceSlug = workspaceStore?.workspaceSlug;
|
||||||
// states
|
// states
|
||||||
@ -50,20 +52,16 @@ export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer(
|
|||||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState<Boolean>(false);
|
const [isLoading, setIsLoading] = useState<Boolean>(false);
|
||||||
|
|
||||||
// const fetchProjectMembers = () => {
|
|
||||||
// setIsLoading(true);
|
|
||||||
// if (workspaceSlug && projectId)
|
|
||||||
// workspaceSlug &&
|
|
||||||
// projectId &&
|
|
||||||
// projectStore.fetchProjectMembers(workspaceSlug, projectId).then(() => setIsLoading(false));
|
|
||||||
// };
|
|
||||||
|
|
||||||
const getWorkspaceMembers = () => {
|
const getWorkspaceMembers = () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
if (workspaceSlug) workspaceSlug && fetchWorkspaceMembers(workspaceSlug).then(() => setIsLoading(false));
|
if (workspaceSlug && projectId) fetchProjectMembers(workspaceSlug, projectId).then(() => setIsLoading(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = (workspaceMembers ?? [])?.map((member) => ({
|
const updatedDefaultOptions: IProjectMember[] =
|
||||||
|
defaultOptions.map((member: any) => ({ member: { ...member } })) ?? [];
|
||||||
|
const projectMembers = _projectMembers ?? updatedDefaultOptions;
|
||||||
|
|
||||||
|
const options = projectMembers?.map((member) => ({
|
||||||
value: member.member.id,
|
value: member.member.id,
|
||||||
query: member.member.display_name,
|
query: member.member.display_name,
|
||||||
content: (
|
content: (
|
||||||
@ -82,7 +80,7 @@ export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer(
|
|||||||
|
|
||||||
// if multiple assignees
|
// if multiple assignees
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
const assignees = workspaceMembers?.filter((m) => value.includes(m.member.id));
|
const assignees = projectMembers?.filter((m) => value.includes(m.member.id));
|
||||||
|
|
||||||
if (!assignees || assignees.length === 0) return "No Assignee";
|
if (!assignees || assignees.length === 0) return "No Assignee";
|
||||||
|
|
||||||
@ -93,7 +91,7 @@ export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if single assignee
|
// if single assignee
|
||||||
const assignee = workspaceMembers?.find((m) => m.member.id === value)?.member;
|
const assignee = projectMembers?.find((m) => m.member.id === value)?.member;
|
||||||
|
|
||||||
if (!assignee) return "No Assignee";
|
if (!assignee) return "No Assignee";
|
||||||
|
|
||||||
@ -107,7 +105,7 @@ export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer(
|
|||||||
{value && value.length > 0 && Array.isArray(value) ? (
|
{value && value.length > 0 && Array.isArray(value) ? (
|
||||||
<AvatarGroup showTooltip={false}>
|
<AvatarGroup showTooltip={false}>
|
||||||
{value.map((assigneeId) => {
|
{value.map((assigneeId) => {
|
||||||
const member = workspaceMembers?.find((m) => m.member.id === assigneeId)?.member;
|
const member = projectMembers?.find((m) => m.member.id === assigneeId)?.member;
|
||||||
if (!member) return null;
|
if (!member) return null;
|
||||||
return <Avatar key={member.id} name={member.display_name} src={member.avatar} />;
|
return <Avatar key={member.id} name={member.display_name} src={member.avatar} />;
|
||||||
})}
|
})}
|
||||||
@ -149,7 +147,7 @@ export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer(
|
|||||||
className={`flex items-center justify-between gap-1 w-full text-xs ${
|
className={`flex items-center justify-between gap-1 w-full text-xs ${
|
||||||
disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer hover:bg-custom-background-80"
|
disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer hover:bg-custom-background-80"
|
||||||
} ${buttonClassName}`}
|
} ${buttonClassName}`}
|
||||||
onClick={() => !workspaceMembers && getWorkspaceMembers()}
|
onClick={() => !projectMembers && getWorkspaceMembers()}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
{!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}
|
{!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}
|
||||||
|
@ -10,10 +10,12 @@ import { Check, ChevronDown, Search } from "lucide-react";
|
|||||||
// types
|
// types
|
||||||
import { Placement } from "@popperjs/core";
|
import { Placement } from "@popperjs/core";
|
||||||
import { RootStore } from "store/root";
|
import { RootStore } from "store/root";
|
||||||
|
import { IIssueLabel } from "types";
|
||||||
|
|
||||||
export interface IIssuePropertyLabels {
|
export interface IIssuePropertyLabels {
|
||||||
projectId: string | null;
|
projectId: string | null;
|
||||||
value: string[];
|
value: string[];
|
||||||
|
defaultOptions?: any;
|
||||||
onChange: (data: string[]) => void;
|
onChange: (data: string[]) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
hideDropdownArrow?: boolean;
|
hideDropdownArrow?: boolean;
|
||||||
@ -29,6 +31,7 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
|||||||
const {
|
const {
|
||||||
projectId,
|
projectId,
|
||||||
value,
|
value,
|
||||||
|
defaultOptions = [],
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled,
|
||||||
hideDropdownArrow = false,
|
hideDropdownArrow = false,
|
||||||
@ -42,7 +45,7 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
workspace: workspaceStore,
|
workspace: workspaceStore,
|
||||||
projectLabel: { fetchProjectLabels, projectLabels },
|
projectLabel: { fetchProjectLabels, labels },
|
||||||
}: RootStore = useMobxStore();
|
}: RootStore = useMobxStore();
|
||||||
const workspaceSlug = workspaceStore?.workspaceSlug;
|
const workspaceSlug = workspaceStore?.workspaceSlug;
|
||||||
|
|
||||||
@ -59,7 +62,11 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
|||||||
|
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
|
|
||||||
const options = (projectLabels ? projectLabels : []).map((label) => ({
|
let projectLabels: IIssueLabel[] = defaultOptions;
|
||||||
|
const storeLabels = projectId && labels ? labels[projectId] : [];
|
||||||
|
if (storeLabels && storeLabels.length > 0) projectLabels = storeLabels;
|
||||||
|
|
||||||
|
const options = projectLabels.map((label) => ({
|
||||||
value: label.id,
|
value: label.id,
|
||||||
query: label.name,
|
query: label.name,
|
||||||
content: (
|
content: (
|
||||||
@ -95,7 +102,7 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
|||||||
{value.length > 0 ? (
|
{value.length > 0 ? (
|
||||||
value.length <= maxRender ? (
|
value.length <= maxRender ? (
|
||||||
<>
|
<>
|
||||||
{(projectLabels ? projectLabels : [])
|
{projectLabels
|
||||||
?.filter((l) => value.includes(l.id))
|
?.filter((l) => value.includes(l.id))
|
||||||
.map((label) => (
|
.map((label) => (
|
||||||
<Tooltip position="top" tooltipHeading="Labels" tooltipContent={label.name ?? ""}>
|
<Tooltip position="top" tooltipHeading="Labels" tooltipContent={label.name ?? ""}>
|
||||||
@ -123,7 +130,7 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
position="top"
|
position="top"
|
||||||
tooltipHeading="Labels"
|
tooltipHeading="Labels"
|
||||||
tooltipContent={(projectLabels ? projectLabels : [])
|
tooltipContent={projectLabels
|
||||||
?.filter((l) => value.includes(l.id))
|
?.filter((l) => value.includes(l.id))
|
||||||
.map((l) => l.name)
|
.map((l) => l.name)
|
||||||
.join(", ")}
|
.join(", ")}
|
||||||
@ -167,7 +174,7 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
|||||||
? "cursor-pointer"
|
? "cursor-pointer"
|
||||||
: "cursor-pointer hover:bg-custom-background-80"
|
: "cursor-pointer hover:bg-custom-background-80"
|
||||||
} ${buttonClassName}`}
|
} ${buttonClassName}`}
|
||||||
onClick={() => !projectLabels && fetchLabels()}
|
onClick={() => !storeLabels && fetchLabels()}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
{!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}
|
{!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}
|
||||||
@ -200,8 +207,9 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
|||||||
key={option.value}
|
key={option.value}
|
||||||
value={option.value}
|
value={option.value}
|
||||||
className={({ selected }) =>
|
className={({ selected }) =>
|
||||||
`flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 hover:bg-custom-background-80
|
`flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 hover:bg-custom-background-80 ${
|
||||||
${selected ? "text-custom-text-100" : "text-custom-text-200"}`
|
selected ? "text-custom-text-100" : "text-custom-text-200"
|
||||||
|
}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{({ selected }) => (
|
{({ selected }) => (
|
||||||
|
@ -17,6 +17,7 @@ import { RootStore } from "store/root";
|
|||||||
export interface IIssuePropertyState {
|
export interface IIssuePropertyState {
|
||||||
projectId: string | null;
|
projectId: string | null;
|
||||||
value: any | string | null;
|
value: any | string | null;
|
||||||
|
defaultOptions?: any;
|
||||||
onChange: (state: IState) => void;
|
onChange: (state: IState) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
hideDropdownArrow?: boolean;
|
hideDropdownArrow?: boolean;
|
||||||
@ -30,6 +31,7 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
|
|||||||
const {
|
const {
|
||||||
projectId,
|
projectId,
|
||||||
value,
|
value,
|
||||||
|
defaultOptions = [],
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled,
|
||||||
hideDropdownArrow = false,
|
hideDropdownArrow = false,
|
||||||
@ -47,10 +49,9 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
|
|||||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState<Boolean>(false);
|
const [isLoading, setIsLoading] = useState<Boolean>(false);
|
||||||
|
|
||||||
const projectStates: IState[] = [];
|
let projectStates: IState[] = defaultOptions;
|
||||||
const projectStatesByGroup = projectStateStore.groupedProjectStates;
|
const storeStates = projectId ? projectStateStore.states[projectId] : [];
|
||||||
if (projectStatesByGroup)
|
if (storeStates && storeStates.length > 0) projectStates = storeStates;
|
||||||
for (const group in projectStatesByGroup) projectStates.push(...projectStatesByGroup[group]);
|
|
||||||
|
|
||||||
const fetchProjectStates = () => {
|
const fetchProjectStates = () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@ -120,7 +121,7 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
|
|||||||
className={`flex items-center justify-between h-5 gap-1 w-full text-xs px-2.5 py-1 rounded border-[0.5px] border-custom-border-300 ${
|
className={`flex items-center justify-between h-5 gap-1 w-full text-xs px-2.5 py-1 rounded border-[0.5px] border-custom-border-300 ${
|
||||||
disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer hover:bg-custom-background-80"
|
disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer hover:bg-custom-background-80"
|
||||||
} ${buttonClassName}`}
|
} ${buttonClassName}`}
|
||||||
onClick={() => !projectStatesByGroup && fetchProjectStates()}
|
onClick={() => !storeStates && fetchProjectStates()}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
{!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}
|
{!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}
|
||||||
|
@ -25,6 +25,7 @@ export const SpreadsheetAssigneeColumn: React.FC<Props> = ({ issue, members, onC
|
|||||||
<IssuePropertyAssignee
|
<IssuePropertyAssignee
|
||||||
projectId={issue.project_detail?.id ?? null}
|
projectId={issue.project_detail?.id ?? null}
|
||||||
value={issue.assignees}
|
value={issue.assignees}
|
||||||
|
defaultOptions={issue?.assignee_details ? issue.assignee_details : []}
|
||||||
onChange={(data) => onChange({ assignees: data })}
|
onChange={(data) => onChange({ assignees: data })}
|
||||||
className="h-full w-full"
|
className="h-full w-full"
|
||||||
buttonClassName="!shadow-none !border-0 h-full w-full px-2.5 py-1 "
|
buttonClassName="!shadow-none !border-0 h-full w-full px-2.5 py-1 "
|
||||||
|
@ -27,6 +27,7 @@ export const SpreadsheetLabelColumn: React.FC<Props> = (props) => {
|
|||||||
<IssuePropertyLabels
|
<IssuePropertyLabels
|
||||||
projectId={issue.project_detail?.id ?? null}
|
projectId={issue.project_detail?.id ?? null}
|
||||||
value={issue.labels}
|
value={issue.labels}
|
||||||
|
defaultOptions={issue?.label_details ? issue.label_details : []}
|
||||||
onChange={(data) => onChange({ labels: data })}
|
onChange={(data) => onChange({ labels: data })}
|
||||||
className="h-full w-full"
|
className="h-full w-full"
|
||||||
buttonClassName="px-2.5 h-full"
|
buttonClassName="px-2.5 h-full"
|
||||||
|
@ -27,6 +27,7 @@ export const SpreadsheetStateColumn: React.FC<Props> = (props) => {
|
|||||||
<IssuePropertyState
|
<IssuePropertyState
|
||||||
projectId={issue.project_detail?.id ?? null}
|
projectId={issue.project_detail?.id ?? null}
|
||||||
value={issue.state_detail.id}
|
value={issue.state_detail.id}
|
||||||
|
defaultOptions={issue?.state_detail ? [issue.state_detail] : []}
|
||||||
onChange={(data) => onChange({ state: data.id, state_detail: data })}
|
onChange={(data) => onChange({ state: data.id, state_detail: data })}
|
||||||
className="h-full w-full"
|
className="h-full w-full"
|
||||||
buttonClassName="!shadow-none !border-0 h-full w-full"
|
buttonClassName="!shadow-none !border-0 h-full w-full"
|
||||||
|
Loading…
Reference in New Issue
Block a user