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:
Anmol Singh Bhatia 2023-06-24 18:09:06 +05:30 committed by GitHub
parent 7acd4ef1af
commit fd30ea9a20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 390 additions and 231 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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));
}) })

View File

@ -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(
{ {

View File

@ -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(
{ {

View File

@ -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,

View File

@ -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";

View 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]"
/>
</>
);
};

View File

@ -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,

View File

@ -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(
{ {