forked from github/plane
feat: editable label option added in all view , fix: view page list and kanban view mutation fix, chore: code refactor (#1390)
* feat: editable label select component added in spreadsheet view * feat: editable label select option added in all view, chore: code refactor * fix: view page list and kanban view mutation fix and sub issue mutation, chore: refactor partial update issue function * fix: build fix
This commit is contained in:
parent
7acd4ef1af
commit
fd30ea9a20
@ -23,6 +23,7 @@ import {
|
|||||||
ViewAssigneeSelect,
|
ViewAssigneeSelect,
|
||||||
ViewDueDateSelect,
|
ViewDueDateSelect,
|
||||||
ViewEstimateSelect,
|
ViewEstimateSelect,
|
||||||
|
ViewLabelSelect,
|
||||||
ViewPrioritySelect,
|
ViewPrioritySelect,
|
||||||
ViewStateSelect,
|
ViewStateSelect,
|
||||||
} from "components/issues";
|
} from "components/issues";
|
||||||
@ -44,7 +45,14 @@ import { LayerDiagonalIcon } from "components/icons";
|
|||||||
import { handleIssuesMutation } from "constants/issue";
|
import { handleIssuesMutation } from "constants/issue";
|
||||||
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, IIssue, Properties, TIssueGroupByOptions, UserAuth } from "types";
|
import {
|
||||||
|
ICurrentUserResponse,
|
||||||
|
IIssue,
|
||||||
|
ISubIssueResponse,
|
||||||
|
Properties,
|
||||||
|
TIssueGroupByOptions,
|
||||||
|
UserAuth,
|
||||||
|
} from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import {
|
||||||
CYCLE_DETAILS,
|
CYCLE_DETAILS,
|
||||||
@ -52,6 +60,8 @@ import {
|
|||||||
MODULE_DETAILS,
|
MODULE_DETAILS,
|
||||||
MODULE_ISSUES_WITH_PARAMS,
|
MODULE_ISSUES_WITH_PARAMS,
|
||||||
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
||||||
|
SUB_ISSUES,
|
||||||
|
VIEW_ISSUES,
|
||||||
} from "constants/fetch-keys";
|
} from "constants/fetch-keys";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -101,86 +111,68 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
|||||||
const { orderBy, params } = useIssuesView();
|
const { orderBy, params } = useIssuesView();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const partialUpdateIssue = useCallback(
|
const partialUpdateIssue = useCallback(
|
||||||
(formData: Partial<IIssue>, issueId: string) => {
|
(formData: Partial<IIssue>, issue: IIssue) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
if (cycleId)
|
const fetchKey = cycleId
|
||||||
mutate<
|
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params)
|
||||||
| {
|
: moduleId
|
||||||
[key: string]: IIssue[];
|
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params)
|
||||||
}
|
: viewId
|
||||||
| IIssue[]
|
? VIEW_ISSUES(viewId.toString(), params)
|
||||||
>(
|
: PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params);
|
||||||
CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params),
|
|
||||||
(prevData) =>
|
if (issue.parent) {
|
||||||
handleIssuesMutation(
|
mutate<ISubIssueResponse>(
|
||||||
formData,
|
SUB_ISSUES(issue.parent.toString()),
|
||||||
groupTitle ?? "",
|
|
||||||
selectedGroup,
|
|
||||||
index,
|
|
||||||
orderBy,
|
|
||||||
prevData
|
|
||||||
),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
else if (moduleId)
|
|
||||||
mutate<
|
|
||||||
| {
|
|
||||||
[key: string]: IIssue[];
|
|
||||||
}
|
|
||||||
| IIssue[]
|
|
||||||
>(
|
|
||||||
MODULE_ISSUES_WITH_PARAMS(moduleId as string),
|
|
||||||
(prevData) =>
|
|
||||||
handleIssuesMutation(
|
|
||||||
formData,
|
|
||||||
groupTitle ?? "",
|
|
||||||
selectedGroup,
|
|
||||||
index,
|
|
||||||
orderBy,
|
|
||||||
prevData
|
|
||||||
),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
else {
|
|
||||||
mutate<
|
|
||||||
| {
|
|
||||||
[key: string]: IIssue[];
|
|
||||||
}
|
|
||||||
| IIssue[]
|
|
||||||
>(
|
|
||||||
PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params),
|
|
||||||
(prevData) => {
|
(prevData) => {
|
||||||
if (!prevData) return prevData;
|
if (!prevData) return prevData;
|
||||||
|
|
||||||
return handleIssuesMutation(
|
return {
|
||||||
|
...prevData,
|
||||||
|
sub_issues: (prevData.sub_issues ?? []).map((i) => {
|
||||||
|
if (i.id === issue.id) {
|
||||||
|
return {
|
||||||
|
...i,
|
||||||
|
...formData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
mutate<
|
||||||
|
| {
|
||||||
|
[key: string]: IIssue[];
|
||||||
|
}
|
||||||
|
| IIssue[]
|
||||||
|
>(
|
||||||
|
fetchKey,
|
||||||
|
(prevData) =>
|
||||||
|
handleIssuesMutation(
|
||||||
formData,
|
formData,
|
||||||
groupTitle ?? "",
|
groupTitle ?? "",
|
||||||
selectedGroup,
|
selectedGroup,
|
||||||
index,
|
index,
|
||||||
orderBy,
|
orderBy,
|
||||||
prevData
|
prevData
|
||||||
);
|
),
|
||||||
},
|
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
issuesService
|
issuesService
|
||||||
.patchIssue(workspaceSlug as string, projectId as string, issueId, formData, user)
|
.patchIssue(workspaceSlug as string, projectId as string, issue.id, formData, user)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (cycleId) {
|
mutate(fetchKey);
|
||||||
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params));
|
|
||||||
mutate(CYCLE_DETAILS(cycleId as string));
|
|
||||||
} else if (moduleId) {
|
|
||||||
mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
|
|
||||||
mutate(MODULE_DETAILS(moduleId as string));
|
|
||||||
} else mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params));
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
@ -188,6 +180,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
|||||||
projectId,
|
projectId,
|
||||||
cycleId,
|
cycleId,
|
||||||
moduleId,
|
moduleId,
|
||||||
|
viewId,
|
||||||
groupTitle,
|
groupTitle,
|
||||||
index,
|
index,
|
||||||
selectedGroup,
|
selectedGroup,
|
||||||
@ -370,23 +363,15 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
|||||||
isNotAllowed={isNotAllowed}
|
isNotAllowed={isNotAllowed}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{properties.labels && issue.label_details.length > 0 && (
|
{properties.labels && (
|
||||||
<div className="flex flex-wrap gap-1">
|
<ViewLabelSelect
|
||||||
{issue.label_details.map((label) => (
|
issue={issue}
|
||||||
<div
|
partialUpdateIssue={partialUpdateIssue}
|
||||||
key={label.id}
|
isNotAllowed={isNotAllowed}
|
||||||
className="group flex items-center gap-1 rounded-2xl border border-brand-base px-2 py-0.5 text-xs text-brand-secondary"
|
tooltipPosition="left"
|
||||||
>
|
user={user}
|
||||||
<span
|
selfPositioned
|
||||||
className="h-1.5 w-1.5 flex-shrink-0 rounded-full"
|
/>
|
||||||
style={{
|
|
||||||
backgroundColor: label?.color && label.color !== "" ? label.color : "#000",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{label.name}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{properties.assignee && (
|
{properties.assignee && (
|
||||||
<ViewAssigneeSelect
|
<ViewAssigneeSelect
|
||||||
|
@ -19,6 +19,7 @@ import {
|
|||||||
ViewAssigneeSelect,
|
ViewAssigneeSelect,
|
||||||
ViewDueDateSelect,
|
ViewDueDateSelect,
|
||||||
ViewEstimateSelect,
|
ViewEstimateSelect,
|
||||||
|
ViewLabelSelect,
|
||||||
ViewPrioritySelect,
|
ViewPrioritySelect,
|
||||||
ViewStateSelect,
|
ViewStateSelect,
|
||||||
} from "components/issues";
|
} from "components/issues";
|
||||||
@ -28,12 +29,13 @@ import { LayerDiagonalIcon } from "components/icons";
|
|||||||
// helper
|
// helper
|
||||||
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
||||||
// type
|
// type
|
||||||
import { ICurrentUserResponse, IIssue } from "types";
|
import { ICurrentUserResponse, IIssue, ISubIssueResponse } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import {
|
||||||
CYCLE_ISSUES_WITH_PARAMS,
|
CYCLE_ISSUES_WITH_PARAMS,
|
||||||
MODULE_ISSUES_WITH_PARAMS,
|
MODULE_ISSUES_WITH_PARAMS,
|
||||||
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
||||||
|
SUB_ISSUES,
|
||||||
VIEW_ISSUES,
|
VIEW_ISSUES,
|
||||||
} from "constants/fetch-keys";
|
} from "constants/fetch-keys";
|
||||||
|
|
||||||
@ -68,7 +70,7 @@ export const SingleCalendarIssue: React.FC<Props> = ({
|
|||||||
const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string);
|
const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string);
|
||||||
|
|
||||||
const partialUpdateIssue = useCallback(
|
const partialUpdateIssue = useCallback(
|
||||||
(formData: Partial<IIssue>, issueId: string) => {
|
(formData: Partial<IIssue>, issue: IIssue) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
const fetchKey = cycleId
|
const fetchKey = cycleId
|
||||||
@ -79,25 +81,54 @@ export const SingleCalendarIssue: React.FC<Props> = ({
|
|||||||
? VIEW_ISSUES(viewId.toString(), params)
|
? VIEW_ISSUES(viewId.toString(), params)
|
||||||
: PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params);
|
: PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params);
|
||||||
|
|
||||||
mutate<IIssue[]>(
|
if (issue.parent) {
|
||||||
fetchKey,
|
mutate<ISubIssueResponse>(
|
||||||
(prevData) =>
|
SUB_ISSUES(issue.parent.toString()),
|
||||||
(prevData ?? []).map((p) => {
|
(prevData) => {
|
||||||
if (p.id === issueId) {
|
if (!prevData) return prevData;
|
||||||
return {
|
|
||||||
...p,
|
|
||||||
...formData,
|
|
||||||
assignees: formData?.assignees_list ?? p.assignees,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return p;
|
return {
|
||||||
}),
|
...prevData,
|
||||||
false
|
sub_issues: (prevData.sub_issues ?? []).map((i) => {
|
||||||
);
|
if (i.id === issue.id) {
|
||||||
|
return {
|
||||||
|
...i,
|
||||||
|
...formData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
mutate<IIssue[]>(
|
||||||
|
fetchKey,
|
||||||
|
(prevData) =>
|
||||||
|
(prevData ?? []).map((p) => {
|
||||||
|
if (p.id === issue.id) {
|
||||||
|
return {
|
||||||
|
...p,
|
||||||
|
...formData,
|
||||||
|
assignees: formData?.assignees_list ?? p.assignees,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
issuesService
|
issuesService
|
||||||
.patchIssue(workspaceSlug as string, projectId as string, issueId as string, formData, user)
|
.patchIssue(
|
||||||
|
workspaceSlug as string,
|
||||||
|
projectId as string,
|
||||||
|
issue.id as string,
|
||||||
|
formData,
|
||||||
|
user
|
||||||
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
mutate(fetchKey);
|
mutate(fetchKey);
|
||||||
})
|
})
|
||||||
@ -207,25 +238,14 @@ export const SingleCalendarIssue: React.FC<Props> = ({
|
|||||||
isNotAllowed={isNotAllowed}
|
isNotAllowed={isNotAllowed}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{properties.labels && issue.label_details.length > 0 ? (
|
{properties.labels && (
|
||||||
<div className="flex flex-wrap gap-1">
|
<ViewLabelSelect
|
||||||
{issue.label_details.map((label) => (
|
issue={issue}
|
||||||
<span
|
partialUpdateIssue={partialUpdateIssue}
|
||||||
key={label.id}
|
position="left"
|
||||||
className="group flex items-center gap-1 rounded-2xl border border-brand-base px-2 py-0.5 text-xs text-brand-secondary"
|
user={user}
|
||||||
>
|
isNotAllowed={isNotAllowed}
|
||||||
<span
|
/>
|
||||||
className="h-1.5 w-1.5 rounded-full"
|
|
||||||
style={{
|
|
||||||
backgroundColor: label?.color && label.color !== "" ? label.color : "#000",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{label.name}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
)}
|
||||||
{properties.assignee && (
|
{properties.assignee && (
|
||||||
<ViewAssigneeSelect
|
<ViewAssigneeSelect
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
ViewAssigneeSelect,
|
ViewAssigneeSelect,
|
||||||
ViewDueDateSelect,
|
ViewDueDateSelect,
|
||||||
ViewEstimateSelect,
|
ViewEstimateSelect,
|
||||||
|
ViewLabelSelect,
|
||||||
ViewPrioritySelect,
|
ViewPrioritySelect,
|
||||||
ViewStateSelect,
|
ViewStateSelect,
|
||||||
} from "components/issues/view-select";
|
} from "components/issues/view-select";
|
||||||
@ -36,7 +37,7 @@ import { LayerDiagonalIcon } from "components/icons";
|
|||||||
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
||||||
import { handleIssuesMutation } from "constants/issue";
|
import { handleIssuesMutation } from "constants/issue";
|
||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, IIssue, Properties, UserAuth } from "types";
|
import { ICurrentUserResponse, IIssue, ISubIssueResponse, Properties, UserAuth } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import {
|
||||||
CYCLE_DETAILS,
|
CYCLE_DETAILS,
|
||||||
@ -44,6 +45,8 @@ import {
|
|||||||
MODULE_DETAILS,
|
MODULE_DETAILS,
|
||||||
MODULE_ISSUES_WITH_PARAMS,
|
MODULE_ISSUES_WITH_PARAMS,
|
||||||
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
||||||
|
SUB_ISSUES,
|
||||||
|
VIEW_ISSUES,
|
||||||
} from "constants/fetch-keys";
|
} from "constants/fetch-keys";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -80,24 +83,53 @@ export const SingleListIssue: React.FC<Props> = ({
|
|||||||
const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 });
|
const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 });
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { groupByProperty: selectedGroup, orderBy, params } = useIssueView();
|
const { groupByProperty: selectedGroup, orderBy, params } = useIssueView();
|
||||||
|
|
||||||
const partialUpdateIssue = useCallback(
|
const partialUpdateIssue = useCallback(
|
||||||
(formData: Partial<IIssue>, issueId: string) => {
|
(formData: Partial<IIssue>, issue: IIssue) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
if (cycleId)
|
const fetchKey = cycleId
|
||||||
|
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params)
|
||||||
|
: moduleId
|
||||||
|
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params)
|
||||||
|
: viewId
|
||||||
|
? VIEW_ISSUES(viewId.toString(), params)
|
||||||
|
: PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params);
|
||||||
|
|
||||||
|
if (issue.parent) {
|
||||||
|
mutate<ISubIssueResponse>(
|
||||||
|
SUB_ISSUES(issue.parent.toString()),
|
||||||
|
(prevData) => {
|
||||||
|
if (!prevData) return prevData;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prevData,
|
||||||
|
sub_issues: (prevData.sub_issues ?? []).map((i) => {
|
||||||
|
if (i.id === issue.id) {
|
||||||
|
return {
|
||||||
|
...i,
|
||||||
|
...formData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
mutate<
|
mutate<
|
||||||
| {
|
| {
|
||||||
[key: string]: IIssue[];
|
[key: string]: IIssue[];
|
||||||
}
|
}
|
||||||
| IIssue[]
|
| IIssue[]
|
||||||
>(
|
>(
|
||||||
CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params),
|
fetchKey,
|
||||||
(prevData) =>
|
(prevData) =>
|
||||||
handleIssuesMutation(
|
handleIssuesMutation(
|
||||||
formData,
|
formData,
|
||||||
@ -109,49 +141,12 @@ export const SingleListIssue: React.FC<Props> = ({
|
|||||||
),
|
),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
}
|
||||||
if (moduleId)
|
|
||||||
mutate<
|
|
||||||
| {
|
|
||||||
[key: string]: IIssue[];
|
|
||||||
}
|
|
||||||
| IIssue[]
|
|
||||||
>(
|
|
||||||
MODULE_ISSUES_WITH_PARAMS(moduleId as string, params),
|
|
||||||
(prevData) =>
|
|
||||||
handleIssuesMutation(
|
|
||||||
formData,
|
|
||||||
groupTitle ?? "",
|
|
||||||
selectedGroup,
|
|
||||||
index,
|
|
||||||
orderBy,
|
|
||||||
prevData
|
|
||||||
),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
mutate<
|
|
||||||
| {
|
|
||||||
[key: string]: IIssue[];
|
|
||||||
}
|
|
||||||
| IIssue[]
|
|
||||||
>(
|
|
||||||
PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params),
|
|
||||||
(prevData) =>
|
|
||||||
handleIssuesMutation(formData, groupTitle ?? "", selectedGroup, index, orderBy, prevData),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
issuesService
|
issuesService
|
||||||
.patchIssue(workspaceSlug as string, projectId as string, issueId, formData, user)
|
.patchIssue(workspaceSlug as string, projectId as string, issue.id, formData, user)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (cycleId) {
|
mutate(fetchKey);
|
||||||
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params));
|
|
||||||
mutate(CYCLE_DETAILS(cycleId as string));
|
|
||||||
} else if (moduleId) {
|
|
||||||
mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
|
|
||||||
mutate(MODULE_DETAILS(moduleId as string));
|
|
||||||
} else mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params));
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
@ -159,6 +154,7 @@ export const SingleListIssue: React.FC<Props> = ({
|
|||||||
projectId,
|
projectId,
|
||||||
cycleId,
|
cycleId,
|
||||||
moduleId,
|
moduleId,
|
||||||
|
viewId,
|
||||||
groupTitle,
|
groupTitle,
|
||||||
index,
|
index,
|
||||||
selectedGroup,
|
selectedGroup,
|
||||||
@ -275,25 +271,14 @@ export const SingleListIssue: React.FC<Props> = ({
|
|||||||
isNotAllowed={isNotAllowed}
|
isNotAllowed={isNotAllowed}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{properties.labels && issue.label_details.length > 0 ? (
|
{properties.labels && (
|
||||||
<div className="flex flex-wrap gap-1">
|
<ViewLabelSelect
|
||||||
{issue.label_details.map((label) => (
|
issue={issue}
|
||||||
<span
|
partialUpdateIssue={partialUpdateIssue}
|
||||||
key={label.id}
|
position="right"
|
||||||
className="group flex items-center gap-1 rounded-2xl border border-brand-base px-2 py-0.5 text-xs text-brand-secondary"
|
user={user}
|
||||||
>
|
isNotAllowed={isNotAllowed}
|
||||||
<span
|
/>
|
||||||
className="h-1.5 w-1.5 rounded-full"
|
|
||||||
style={{
|
|
||||||
backgroundColor: label?.color && label.color !== "" ? label.color : "#000",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{label.name}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
)}
|
||||||
{properties.assignee && (
|
{properties.assignee && (
|
||||||
<ViewAssigneeSelect
|
<ViewAssigneeSelect
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
ViewAssigneeSelect,
|
ViewAssigneeSelect,
|
||||||
ViewDueDateSelect,
|
ViewDueDateSelect,
|
||||||
ViewEstimateSelect,
|
ViewEstimateSelect,
|
||||||
|
ViewLabelSelect,
|
||||||
ViewPrioritySelect,
|
ViewPrioritySelect,
|
||||||
ViewStateSelect,
|
ViewStateSelect,
|
||||||
} from "components/issues";
|
} from "components/issues";
|
||||||
@ -26,10 +27,11 @@ import {
|
|||||||
CYCLE_ISSUES_WITH_PARAMS,
|
CYCLE_ISSUES_WITH_PARAMS,
|
||||||
MODULE_ISSUES_WITH_PARAMS,
|
MODULE_ISSUES_WITH_PARAMS,
|
||||||
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
||||||
|
SUB_ISSUES,
|
||||||
VIEW_ISSUES,
|
VIEW_ISSUES,
|
||||||
} from "constants/fetch-keys";
|
} from "constants/fetch-keys";
|
||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, IIssue, Properties, UserAuth } from "types";
|
import { ICurrentUserResponse, IIssue, ISubIssueResponse, Properties, UserAuth } from "types";
|
||||||
// helper
|
// helper
|
||||||
import { copyTextToClipboard } from "helpers/string.helper";
|
import { copyTextToClipboard } from "helpers/string.helper";
|
||||||
|
|
||||||
@ -67,7 +69,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
|
|||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const partialUpdateIssue = useCallback(
|
const partialUpdateIssue = useCallback(
|
||||||
(formData: Partial<IIssue>, issueId: string) => {
|
(formData: Partial<IIssue>, issue: IIssue) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
const fetchKey = cycleId
|
const fetchKey = cycleId
|
||||||
@ -78,23 +80,52 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
|
|||||||
? VIEW_ISSUES(viewId.toString(), params)
|
? VIEW_ISSUES(viewId.toString(), params)
|
||||||
: PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params);
|
: PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params);
|
||||||
|
|
||||||
mutate<IIssue[]>(
|
if (issue.parent) {
|
||||||
fetchKey,
|
mutate<ISubIssueResponse>(
|
||||||
(prevData) =>
|
SUB_ISSUES(issue.parent.toString()),
|
||||||
(prevData ?? []).map((p) => {
|
(prevData) => {
|
||||||
if (p.id === issueId) {
|
if (!prevData) return prevData;
|
||||||
return {
|
|
||||||
...p,
|
return {
|
||||||
...formData,
|
...prevData,
|
||||||
};
|
sub_issues: (prevData.sub_issues ?? []).map((i) => {
|
||||||
}
|
if (i.id === issue.id) {
|
||||||
return p;
|
return {
|
||||||
}),
|
...i,
|
||||||
false
|
...formData,
|
||||||
);
|
};
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
mutate<IIssue[]>(
|
||||||
|
fetchKey,
|
||||||
|
(prevData) =>
|
||||||
|
(prevData ?? []).map((p) => {
|
||||||
|
if (p.id === issue.id) {
|
||||||
|
return {
|
||||||
|
...p,
|
||||||
|
...formData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
issuesService
|
issuesService
|
||||||
.patchIssue(workspaceSlug as string, projectId as string, issueId as string, formData, user)
|
.patchIssue(
|
||||||
|
workspaceSlug as string,
|
||||||
|
projectId as string,
|
||||||
|
issue.id as string,
|
||||||
|
formData,
|
||||||
|
user
|
||||||
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
mutate(fetchKey);
|
mutate(fetchKey);
|
||||||
})
|
})
|
||||||
@ -191,30 +222,19 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{properties.labels ? (
|
{properties.labels && (
|
||||||
issue.label_details.length > 0 ? (
|
<div className="flex items-center text-xs text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
|
||||||
<div className="flex items-center gap-2 text-xs text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
|
<ViewLabelSelect
|
||||||
{issue.label_details.slice(0, 4).map((label, index) => (
|
issue={issue}
|
||||||
<div className={`flex h-4 w-4 rounded-full ${index ? "-ml-3.5" : ""}`}>
|
partialUpdateIssue={partialUpdateIssue}
|
||||||
<span
|
position="left"
|
||||||
className={`h-4 w-4 flex-shrink-0 rounded-full border group-hover:bg-brand-surface-2 border-brand-base
|
customButton
|
||||||
`}
|
user={user}
|
||||||
style={{
|
isNotAllowed={isNotAllowed}
|
||||||
backgroundColor: label?.color && label.color !== "" ? label.color : "#000000",
|
/>
|
||||||
}}
|
</div>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{issue.label_details.length > 4 ? <span>+{issue.label_details.length - 4}</span> : null}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex items-center text-xs text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
|
|
||||||
No Labels
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{properties.due_date && (
|
{properties.due_date && (
|
||||||
<div className="flex items-center text-xs text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
|
<div className="flex items-center text-xs text-brand-secondary text-center p-2 group-hover:bg-brand-surface-2 border-brand-base">
|
||||||
<ViewDueDateSelect
|
<ViewDueDateSelect
|
||||||
|
@ -44,14 +44,14 @@ export const MyIssuesListItem: React.FC<Props> = ({ issue, properties, projectId
|
|||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const partialUpdateIssue = useCallback(
|
const partialUpdateIssue = useCallback(
|
||||||
(formData: Partial<IIssue>, issueId: string) => {
|
(formData: Partial<IIssue>, issue: IIssue) => {
|
||||||
if (!workspaceSlug) return;
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
mutate<IIssue[]>(
|
mutate<IIssue[]>(
|
||||||
USER_ISSUE(workspaceSlug as string),
|
USER_ISSUE(workspaceSlug as string),
|
||||||
(prevData) =>
|
(prevData) =>
|
||||||
prevData?.map((p) => {
|
prevData?.map((p) => {
|
||||||
if (p.id === issueId) return { ...p, ...formData };
|
if (p.id === issue.id) return { ...p, ...formData };
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
}),
|
}),
|
||||||
@ -59,7 +59,7 @@ export const MyIssuesListItem: React.FC<Props> = ({ issue, properties, projectId
|
|||||||
);
|
);
|
||||||
|
|
||||||
issuesService
|
issuesService
|
||||||
.patchIssue(workspaceSlug as string, projectId as string, issueId, formData, user)
|
.patchIssue(workspaceSlug as string, projectId as string, issue.id, formData, user)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
mutate(USER_ISSUE(workspaceSlug as string));
|
mutate(USER_ISSUE(workspaceSlug as string));
|
||||||
})
|
})
|
||||||
|
@ -18,7 +18,7 @@ import { PROJECT_MEMBERS } from "constants/fetch-keys";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void;
|
partialUpdateIssue: (formData: Partial<IIssue>, issue: IIssue) => void;
|
||||||
position?: "left" | "right";
|
position?: "left" | "right";
|
||||||
selfPositioned?: boolean;
|
selfPositioned?: boolean;
|
||||||
tooltipPosition?: "left" | "right";
|
tooltipPosition?: "left" | "right";
|
||||||
@ -108,7 +108,7 @@ export const ViewAssigneeSelect: React.FC<Props> = ({
|
|||||||
if (newData.includes(data)) newData.splice(newData.indexOf(data), 1);
|
if (newData.includes(data)) newData.splice(newData.indexOf(data), 1);
|
||||||
else newData.push(data);
|
else newData.push(data);
|
||||||
|
|
||||||
partialUpdateIssue({ assignees_list: data }, issue.id);
|
partialUpdateIssue({ assignees_list: data }, issue);
|
||||||
|
|
||||||
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
||||||
{
|
{
|
||||||
|
@ -11,7 +11,7 @@ import { ICurrentUserResponse, IIssue } from "types";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void;
|
partialUpdateIssue: (formData: Partial<IIssue>, issue: IIssue) => void;
|
||||||
noBorder?: boolean;
|
noBorder?: boolean;
|
||||||
user: ICurrentUserResponse | undefined;
|
user: ICurrentUserResponse | undefined;
|
||||||
isNotAllowed: boolean;
|
isNotAllowed: boolean;
|
||||||
@ -48,7 +48,7 @@ export const ViewDueDateSelect: React.FC<Props> = ({
|
|||||||
priority: issue.priority,
|
priority: issue.priority,
|
||||||
state: issue.state,
|
state: issue.state,
|
||||||
},
|
},
|
||||||
issue.id
|
issue
|
||||||
);
|
);
|
||||||
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
||||||
{
|
{
|
||||||
|
@ -15,7 +15,7 @@ import { ICurrentUserResponse, IIssue } from "types";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void;
|
partialUpdateIssue: (formData: Partial<IIssue>, issue: IIssue) => void;
|
||||||
position?: "left" | "right";
|
position?: "left" | "right";
|
||||||
selfPositioned?: boolean;
|
selfPositioned?: boolean;
|
||||||
customButton?: boolean;
|
customButton?: boolean;
|
||||||
@ -54,7 +54,7 @@ export const ViewEstimateSelect: React.FC<Props> = ({
|
|||||||
<CustomSelect
|
<CustomSelect
|
||||||
value={issue.estimate_point}
|
value={issue.estimate_point}
|
||||||
onChange={(val: number) => {
|
onChange={(val: number) => {
|
||||||
partialUpdateIssue({ estimate_point: val }, issue.id);
|
partialUpdateIssue({ estimate_point: val }, issue);
|
||||||
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
||||||
{
|
{
|
||||||
workspaceSlug,
|
workspaceSlug,
|
||||||
|
@ -3,3 +3,4 @@ export * from "./due-date";
|
|||||||
export * from "./estimate";
|
export * from "./estimate";
|
||||||
export * from "./priority";
|
export * from "./priority";
|
||||||
export * from "./state";
|
export * from "./state";
|
||||||
|
export * from "./label";
|
||||||
|
148
apps/app/components/issues/view-select/label.tsx
Normal file
148
apps/app/components/issues/view-select/label.tsx
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
// services
|
||||||
|
import issuesService from "services/issues.service";
|
||||||
|
// component
|
||||||
|
import { CreateLabelModal } from "components/labels";
|
||||||
|
// ui
|
||||||
|
import { CustomSearchSelect, Tooltip } from "components/ui";
|
||||||
|
// icons
|
||||||
|
import { PlusIcon, TagIcon } from "@heroicons/react/24/outline";
|
||||||
|
// types
|
||||||
|
import { ICurrentUserResponse, IIssue, IIssueLabels } from "types";
|
||||||
|
// fetch-keys
|
||||||
|
import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
issue: IIssue;
|
||||||
|
partialUpdateIssue: (formData: Partial<IIssue>, issue: IIssue) => void;
|
||||||
|
position?: "left" | "right";
|
||||||
|
selfPositioned?: boolean;
|
||||||
|
tooltipPosition?: "left" | "right";
|
||||||
|
customButton?: boolean;
|
||||||
|
user: ICurrentUserResponse | undefined;
|
||||||
|
isNotAllowed: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ViewLabelSelect: React.FC<Props> = ({
|
||||||
|
issue,
|
||||||
|
partialUpdateIssue,
|
||||||
|
position = "left",
|
||||||
|
selfPositioned = false,
|
||||||
|
tooltipPosition = "right",
|
||||||
|
user,
|
||||||
|
isNotAllowed,
|
||||||
|
customButton = false,
|
||||||
|
}) => {
|
||||||
|
const [labelModal, setLabelModal] = useState(false);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
|
const { data: issueLabels } = useSWR<IIssueLabels[]>(
|
||||||
|
projectId ? PROJECT_ISSUE_LABELS(projectId.toString()) : null,
|
||||||
|
workspaceSlug && projectId
|
||||||
|
? () => issuesService.getIssueLabels(workspaceSlug as string, projectId as string)
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
|
||||||
|
const options = issueLabels?.map((label) => ({
|
||||||
|
value: label.id,
|
||||||
|
query: label.name,
|
||||||
|
content: (
|
||||||
|
<div className="flex items-center justify-start gap-2">
|
||||||
|
<span
|
||||||
|
className="h-2.5 w-2.5 flex-shrink-0 rounded-full"
|
||||||
|
style={{
|
||||||
|
backgroundColor: label.color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span>{label.name}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const labelsLabel = (
|
||||||
|
<Tooltip
|
||||||
|
position={`top-${tooltipPosition}`}
|
||||||
|
tooltipHeading="Labels"
|
||||||
|
tooltipContent={
|
||||||
|
issue.label_details.length > 0
|
||||||
|
? issue.label_details.map((label) => label.name ?? "").join(", ")
|
||||||
|
: "No Label"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`flex ${
|
||||||
|
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
|
||||||
|
} items-center gap-2 text-brand-secondary`}
|
||||||
|
>
|
||||||
|
{issue.label_details.length > 0 ? (
|
||||||
|
<>
|
||||||
|
{issue.label_details.slice(0, 4).map((label, index) => (
|
||||||
|
<div className={`flex h-4 w-4 rounded-full ${index ? "-ml-3.5" : ""}`}>
|
||||||
|
<span
|
||||||
|
className={`h-4 w-4 flex-shrink-0 rounded-full border group-hover:bg-brand-surface-2 border-brand-base
|
||||||
|
`}
|
||||||
|
style={{
|
||||||
|
backgroundColor: label?.color && label.color !== "" ? label.color : "#000000",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{issue.label_details.length > 4 ? <span>+{issue.label_details.length - 4}</span> : null}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<TagIcon className="h-3.5 w-3.5 text-brand-secondary" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
|
||||||
|
const footerOption = (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex w-full select-none items-center rounded py-2 px-1 hover:bg-brand-surface-2"
|
||||||
|
onClick={() => setLabelModal(true)}
|
||||||
|
>
|
||||||
|
<span className="flex items-center justify-start gap-1 text-brand-secondary">
|
||||||
|
<PlusIcon className="h-4 w-4" aria-hidden="true" />
|
||||||
|
<span>Create New Label</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{projectId && (
|
||||||
|
<CreateLabelModal
|
||||||
|
isOpen={labelModal}
|
||||||
|
handleClose={() => setLabelModal(false)}
|
||||||
|
projectId={projectId.toString()}
|
||||||
|
user={user}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<CustomSearchSelect
|
||||||
|
value={issue.labels}
|
||||||
|
onChange={(data: string[]) => {
|
||||||
|
partialUpdateIssue({ labels_list: data }, issue);
|
||||||
|
}}
|
||||||
|
options={options}
|
||||||
|
{...(customButton ? { customButton: labelsLabel } : { label: labelsLabel })}
|
||||||
|
multiple
|
||||||
|
noChevron
|
||||||
|
position={position}
|
||||||
|
disabled={isNotAllowed}
|
||||||
|
selfPositioned={selfPositioned}
|
||||||
|
footerOption={footerOption}
|
||||||
|
dropdownWidth="w-full min-w-[12rem]"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -17,7 +17,7 @@ import { capitalizeFirstLetter } from "helpers/string.helper";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void;
|
partialUpdateIssue: (formData: Partial<IIssue>, issue: IIssue) => void;
|
||||||
position?: "left" | "right";
|
position?: "left" | "right";
|
||||||
selfPositioned?: boolean;
|
selfPositioned?: boolean;
|
||||||
noBorder?: boolean;
|
noBorder?: boolean;
|
||||||
@ -41,7 +41,7 @@ export const ViewPrioritySelect: React.FC<Props> = ({
|
|||||||
<CustomSelect
|
<CustomSelect
|
||||||
value={issue.priority}
|
value={issue.priority}
|
||||||
onChange={(data: string) => {
|
onChange={(data: string) => {
|
||||||
partialUpdateIssue({ priority: data }, issue.id);
|
partialUpdateIssue({ priority: data }, issue);
|
||||||
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
||||||
{
|
{
|
||||||
workspaceSlug,
|
workspaceSlug,
|
||||||
|
@ -19,7 +19,7 @@ import { STATES_LIST } from "constants/fetch-keys";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
partialUpdateIssue: (formData: Partial<IIssue>, issueId: string) => void;
|
partialUpdateIssue: (formData: Partial<IIssue>, issue: IIssue) => void;
|
||||||
position?: "left" | "right";
|
position?: "left" | "right";
|
||||||
selfPositioned?: boolean;
|
selfPositioned?: boolean;
|
||||||
customButton?: boolean;
|
customButton?: boolean;
|
||||||
@ -83,7 +83,7 @@ export const ViewStateSelect: React.FC<Props> = ({
|
|||||||
priority: issue.priority,
|
priority: issue.priority,
|
||||||
target_date: issue.target_date,
|
target_date: issue.target_date,
|
||||||
},
|
},
|
||||||
issue.id
|
issue
|
||||||
);
|
);
|
||||||
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user