mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore : dropdown loading state added and project card avatar fix (#2643)
* chore: project card avatar rendering fix * chore: state, assignee and label dropdown loading state added
This commit is contained in:
parent
ad558833af
commit
52395d0563
@ -47,11 +47,17 @@ export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer(
|
|||||||
|
|
||||||
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);
|
||||||
|
const [isLoading, setIsLoading] = useState<Boolean>(false);
|
||||||
|
|
||||||
const projectMembers = projectId ? projectStore?.members?.[projectId] : undefined;
|
const projectMembers = projectId ? projectStore?.members?.[projectId] : undefined;
|
||||||
|
|
||||||
const fetchProjectMembers = () =>
|
const fetchProjectMembers = () => {
|
||||||
workspaceSlug && projectId && projectStore.fetchProjectMembers(workspaceSlug, projectId);
|
setIsLoading(true);
|
||||||
|
if (workspaceSlug && projectId)
|
||||||
|
workspaceSlug &&
|
||||||
|
projectId &&
|
||||||
|
projectStore.fetchProjectMembers(workspaceSlug, projectId).then(() => setIsLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
const options = (projectMembers ?? [])?.map((member) => ({
|
const options = (projectMembers ?? [])?.map((member) => ({
|
||||||
value: member.member.id,
|
value: member.member.id,
|
||||||
@ -128,7 +134,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={() => fetchProjectMembers()}
|
onClick={() => !projectMembers && fetchProjectMembers()}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
{!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}
|
{!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}
|
||||||
@ -152,33 +158,31 @@ export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer(
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={`mt-2 space-y-1 max-h-48 overflow-y-scroll`}>
|
<div className={`mt-2 space-y-1 max-h-48 overflow-y-scroll`}>
|
||||||
{filteredOptions ? (
|
{isLoading ? (
|
||||||
filteredOptions.length > 0 ? (
|
|
||||||
filteredOptions.map((option) => (
|
|
||||||
<Combobox.Option
|
|
||||||
key={option.value}
|
|
||||||
value={option.value}
|
|
||||||
className={({ active, selected }) =>
|
|
||||||
`flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${
|
|
||||||
active && !selected ? "bg-custom-background-80" : ""
|
|
||||||
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{({ selected }) => (
|
|
||||||
<>
|
|
||||||
{option.content}
|
|
||||||
{selected && <Check className={`h-3.5 w-3.5`} />}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Combobox.Option>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<span className="flex items-center gap-2 p-1">
|
|
||||||
<p className="text-left text-custom-text-200 ">No matching results</p>
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<p className="text-center text-custom-text-200">Loading...</p>
|
<p className="text-center text-custom-text-200">Loading...</p>
|
||||||
|
) : filteredOptions.length > 0 ? (
|
||||||
|
filteredOptions.map((option) => (
|
||||||
|
<Combobox.Option
|
||||||
|
key={option.value}
|
||||||
|
value={option.value}
|
||||||
|
className={({ active, selected }) =>
|
||||||
|
`flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${
|
||||||
|
active && !selected ? "bg-custom-background-80" : ""
|
||||||
|
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{({ selected }) => (
|
||||||
|
<>
|
||||||
|
{option.content}
|
||||||
|
{selected && <Check className={`h-3.5 w-3.5`} />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Combobox.Option>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<span className="flex items-center gap-2 p-1">
|
||||||
|
<p className="text-left text-custom-text-200 ">No matching results</p>
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,11 +51,15 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
|||||||
|
|
||||||
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);
|
||||||
|
const [isLoading, setIsLoading] = useState<Boolean>(false);
|
||||||
|
|
||||||
const projectLabels = projectId && projectStore?.labels?.[projectId];
|
const projectLabels = projectId && projectStore?.labels?.[projectId];
|
||||||
|
|
||||||
const fetchProjectLabels = () =>
|
const fetchProjectLabels = () => {
|
||||||
workspaceSlug && projectId && projectStore.fetchProjectLabels(workspaceSlug, projectId);
|
setIsLoading(true);
|
||||||
|
if (workspaceSlug && projectId)
|
||||||
|
projectStore.fetchProjectLabels(workspaceSlug, projectId).then(() => setIsLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
const options = (projectLabels ? projectLabels : []).map((label) => ({
|
const options = (projectLabels ? projectLabels : []).map((label) => ({
|
||||||
value: label.id,
|
value: label.id,
|
||||||
@ -131,10 +135,10 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
|||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={`h-full flex items-center justify-center text-xs rounded px-2.5 py-1 hover:bg-custom-background-80 ${
|
className={`h-full flex items-center justify-center text-xs rounded px-2.5 py-1 hover:bg-custom-background-80 ${
|
||||||
noLabelBorder ? "" : "border-[0.5px] border-custom-border-300"
|
noLabelBorder ? "" : "border-[0.5px] border-custom-border-300"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Select labels
|
Select labels
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -161,7 +165,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={() => fetchProjectLabels()}
|
onClick={() => !projectLabels && fetchProjectLabels()}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
{!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}
|
{!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}
|
||||||
@ -186,33 +190,31 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={`mt-2 space-y-1 max-h-48 overflow-y-scroll`}>
|
<div className={`mt-2 space-y-1 max-h-48 overflow-y-scroll`}>
|
||||||
{filteredOptions ? (
|
{isLoading ? (
|
||||||
filteredOptions.length > 0 ? (
|
|
||||||
filteredOptions.map((option) => (
|
|
||||||
<Combobox.Option
|
|
||||||
key={option.value}
|
|
||||||
value={option.value}
|
|
||||||
className={({ active, selected }) =>
|
|
||||||
`flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${
|
|
||||||
active ? "bg-custom-background-80" : ""
|
|
||||||
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{({ selected }) => (
|
|
||||||
<>
|
|
||||||
{option.content}
|
|
||||||
{selected && <Check className={`h-3.5 w-3.5`} />}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Combobox.Option>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<span className="flex items-center gap-2 p-1">
|
|
||||||
<p className="text-left text-custom-text-200 ">No matching results</p>
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<p className="text-center text-custom-text-200">Loading...</p>
|
<p className="text-center text-custom-text-200">Loading...</p>
|
||||||
|
) : filteredOptions.length > 0 ? (
|
||||||
|
filteredOptions.map((option) => (
|
||||||
|
<Combobox.Option
|
||||||
|
key={option.value}
|
||||||
|
value={option.value}
|
||||||
|
className={({ active, selected }) =>
|
||||||
|
`flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${
|
||||||
|
active ? "bg-custom-background-80" : ""
|
||||||
|
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{({ selected }) => (
|
||||||
|
<>
|
||||||
|
{option.content}
|
||||||
|
{selected && <Check className={`h-3.5 w-3.5`} />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Combobox.Option>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<span className="flex items-center gap-2 p-1">
|
||||||
|
<p className="text-left text-custom-text-200 ">No matching results</p>
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -47,14 +47,20 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
|
|||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
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);
|
||||||
|
const [isLoading, setIsLoading] = useState<Boolean>(false);
|
||||||
|
|
||||||
const projectStates: IState[] = [];
|
const projectStates: IState[] = [];
|
||||||
const projectStatesByGroup = projectId && projectStore?.states?.[projectId];
|
const projectStatesByGroup = projectId && projectStore?.states?.[projectId];
|
||||||
if (projectStatesByGroup)
|
if (projectStatesByGroup)
|
||||||
for (const group in projectStatesByGroup) projectStates.push(...projectStatesByGroup[group]);
|
for (const group in projectStatesByGroup) projectStates.push(...projectStatesByGroup[group]);
|
||||||
|
|
||||||
const fetchProjectStates = () =>
|
const fetchProjectStates = () => {
|
||||||
workspaceSlug && projectId && projectStore.fetchProjectStates(workspaceSlug, projectId);
|
setIsLoading(true);
|
||||||
|
if (workspaceSlug && projectId)
|
||||||
|
workspaceSlug &&
|
||||||
|
projectId &&
|
||||||
|
projectStore.fetchProjectStates(workspaceSlug, projectId).then(() => setIsLoading(false));
|
||||||
|
};
|
||||||
|
|
||||||
const dropdownOptions = projectStates?.map((state) => ({
|
const dropdownOptions = projectStates?.map((state) => ({
|
||||||
value: state.id,
|
value: state.id,
|
||||||
@ -113,7 +119,7 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
|
|||||||
className={`flex items-center justify-between 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 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={() => fetchProjectStates()}
|
onClick={() => !projectStatesByGroup && 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" />}
|
||||||
@ -137,33 +143,31 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={`mt-2 space-y-1 max-h-48 overflow-y-scroll`}>
|
<div className={`mt-2 space-y-1 max-h-48 overflow-y-scroll`}>
|
||||||
{filteredOptions ? (
|
{isLoading ? (
|
||||||
filteredOptions.length > 0 ? (
|
|
||||||
filteredOptions.map((option) => (
|
|
||||||
<Combobox.Option
|
|
||||||
key={option.value}
|
|
||||||
value={option.value}
|
|
||||||
className={({ active, selected }) =>
|
|
||||||
`flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${
|
|
||||||
active ? "bg-custom-background-80" : ""
|
|
||||||
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{({ selected }) => (
|
|
||||||
<>
|
|
||||||
{option.content}
|
|
||||||
{selected && <Check className="h-3.5 w-3.5" />}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Combobox.Option>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<span className="flex items-center gap-2 p-1">
|
|
||||||
<p className="text-left text-custom-text-200 ">No matching results</p>
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<p className="text-center text-custom-text-200">Loading...</p>
|
<p className="text-center text-custom-text-200">Loading...</p>
|
||||||
|
) : filteredOptions.length > 0 ? (
|
||||||
|
filteredOptions.map((option) => (
|
||||||
|
<Combobox.Option
|
||||||
|
key={option.value}
|
||||||
|
value={option.value}
|
||||||
|
className={({ active, selected }) =>
|
||||||
|
`flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${
|
||||||
|
active ? "bg-custom-background-80" : ""
|
||||||
|
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{({ selected }) => (
|
||||||
|
<>
|
||||||
|
{option.content}
|
||||||
|
{selected && <Check className="h-3.5 w-3.5" />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Combobox.Option>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<span className="flex items-center gap-2 p-1">
|
||||||
|
<p className="text-left text-custom-text-200 ">No matching results</p>
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -181,7 +181,7 @@ export const ProjectCard: React.FC<ProjectCardProps> = observer((props) => {
|
|||||||
<div className="flex items-center cursor-pointer gap-2 text-custom-text-200">
|
<div className="flex items-center cursor-pointer gap-2 text-custom-text-200">
|
||||||
<AvatarGroup showTooltip={false}>
|
<AvatarGroup showTooltip={false}>
|
||||||
{projectMembersIds.map((memberId) => {
|
{projectMembersIds.map((memberId) => {
|
||||||
const member = project.members?.find((m) => m.id === memberId);
|
const member = project.members?.find((m) => m.member_id === memberId);
|
||||||
|
|
||||||
if (!member) return null;
|
if (!member) return null;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user