mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge pull request #1406 from makeplane/develop
promote: develop to stage-release
This commit is contained in:
commit
fd7274ba1c
@ -15,6 +15,7 @@ from django.db.models import (
|
|||||||
Value,
|
Value,
|
||||||
CharField,
|
CharField,
|
||||||
When,
|
When,
|
||||||
|
Max,
|
||||||
)
|
)
|
||||||
from django.core.serializers.json import DjangoJSONEncoder
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
@ -195,7 +196,7 @@ class IssueViewSet(BaseViewSet):
|
|||||||
output_field=CharField(),
|
output_field=CharField(),
|
||||||
)
|
)
|
||||||
).order_by("priority_order")
|
).order_by("priority_order")
|
||||||
|
|
||||||
# State Ordering
|
# State Ordering
|
||||||
elif order_by_param in [
|
elif order_by_param in [
|
||||||
"state__name",
|
"state__name",
|
||||||
@ -218,6 +219,22 @@ class IssueViewSet(BaseViewSet):
|
|||||||
output_field=CharField(),
|
output_field=CharField(),
|
||||||
)
|
)
|
||||||
).order_by("state_order")
|
).order_by("state_order")
|
||||||
|
# assignee and label ordering
|
||||||
|
elif order_by_param in [
|
||||||
|
"labels__name",
|
||||||
|
"-labels__name",
|
||||||
|
"assignees__first_name",
|
||||||
|
"-assignees__first_name",
|
||||||
|
]:
|
||||||
|
issue_queryset = issue_queryset.annotate(
|
||||||
|
max_values=Max(
|
||||||
|
order_by_param[1::]
|
||||||
|
if order_by_param.startswith("-")
|
||||||
|
else order_by_param
|
||||||
|
)
|
||||||
|
).order_by(
|
||||||
|
"-max_values" if order_by_param.startswith("-") else "max_values"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
issue_queryset = issue_queryset.order_by(order_by_param)
|
issue_queryset = issue_queryset.order_by(order_by_param)
|
||||||
|
|
||||||
@ -239,7 +256,7 @@ class IssueViewSet(BaseViewSet):
|
|||||||
return Response(issues, status=status.HTTP_200_OK)
|
return Response(issues, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
capture_exception(e)
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "Something went wrong please try again later"},
|
{"error": "Something went wrong please try again later"},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from django.utils.timezone import make_aware
|
from django.utils.timezone import make_aware
|
||||||
from django.utils.dateparse import parse_datetime
|
from django.utils.dateparse import parse_datetime
|
||||||
|
|
||||||
|
|
||||||
def filter_state(params, filter, method):
|
def filter_state(params, filter, method):
|
||||||
if method == "GET":
|
if method == "GET":
|
||||||
states = params.get("state").split(",")
|
states = params.get("state").split(",")
|
||||||
@ -26,12 +25,27 @@ def filter_estimate_point(params, filter, method):
|
|||||||
|
|
||||||
def filter_priority(params, filter, method):
|
def filter_priority(params, filter, method):
|
||||||
if method == "GET":
|
if method == "GET":
|
||||||
priorties = params.get("priority").split(",")
|
priorities = params.get("priority").split(",")
|
||||||
if len(priorties) and "" not in priorties:
|
if len(priorities) and "" not in priorities:
|
||||||
filter["priority__in"] = priorties
|
if len(priorities) == 1 and "null" in priorities:
|
||||||
|
filter["priority__isnull"] = True
|
||||||
|
elif len(priorities) > 1 and "null" in priorities:
|
||||||
|
filter["priority__isnull"] = True
|
||||||
|
filter["priority__in"] = [p for p in priorities if p != "null"]
|
||||||
|
else:
|
||||||
|
filter["priority__in"] = [p for p in priorities if p != "null"]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if params.get("priority", None) and len(params.get("priority")):
|
if params.get("priority", None) and len(params.get("priority")):
|
||||||
filter["priority__in"] = params.get("priority")
|
priorities = params.get("priority")
|
||||||
|
if len(priorities) == 1 and "null" in priorities:
|
||||||
|
filter["priority__isnull"] = True
|
||||||
|
elif len(priorities) > 1 and "null" in priorities:
|
||||||
|
filter["priority__isnull"] = True
|
||||||
|
filter["priority__in"] = [p for p in priorities if p != "null"]
|
||||||
|
else:
|
||||||
|
filter["priority__in"] = [p for p in priorities if p != "null"]
|
||||||
|
|
||||||
return filter
|
return filter
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,6 +41,14 @@ export const AnalyticsYearWiseIssues: React.FC<Props> = ({ defaultAnalytics }) =
|
|||||||
colors={(datum) => datum.color}
|
colors={(datum) => datum.color}
|
||||||
curve="monotoneX"
|
curve="monotoneX"
|
||||||
margin={{ top: 20 }}
|
margin={{ top: 20 }}
|
||||||
|
enableSlices="x"
|
||||||
|
sliceTooltip={(datum) => (
|
||||||
|
<div className="rounded-md border border-brand-base bg-brand-surface-2 p-2 text-xs">
|
||||||
|
{datum.slice.points[0].data.yFormatted}
|
||||||
|
<span className="text-brand-secondary"> issues closed in </span>
|
||||||
|
{datum.slice.points[0].data.xFormatted}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
theme={{
|
theme={{
|
||||||
background: "rgb(var(--color-bg-base))",
|
background: "rgb(var(--color-bg-base))",
|
||||||
}}
|
}}
|
||||||
|
@ -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
|
||||||
|
@ -572,8 +572,11 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
) : issueView === "spreadsheet" ? (
|
) : issueView === "spreadsheet" ? (
|
||||||
<SpreadsheetView
|
<SpreadsheetView
|
||||||
|
type={type}
|
||||||
handleEditIssue={handleEditIssue}
|
handleEditIssue={handleEditIssue}
|
||||||
handleDeleteIssue={handleDeleteIssue}
|
handleDeleteIssue={handleDeleteIssue}
|
||||||
|
openIssuesListModal={type !== "issue" ? openIssuesListModal : null}
|
||||||
|
isCompleted={isCompleted}
|
||||||
user={user}
|
user={user}
|
||||||
userAuth={memberRole}
|
userAuth={memberRole}
|
||||||
/>
|
/>
|
||||||
|
@ -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
|
||||||
|
@ -117,6 +117,14 @@ const ProgressChart: React.FC<Props> = ({ distribution, startDate, endDate, tota
|
|||||||
colors={(datum) => datum.color ?? "#3F76FF"}
|
colors={(datum) => datum.color ?? "#3F76FF"}
|
||||||
customYAxisTickValues={[0, totalIssues]}
|
customYAxisTickValues={[0, totalIssues]}
|
||||||
gridXValues={chartData.map((item, index) => (index % 2 === 0 ? item.currentDate : ""))}
|
gridXValues={chartData.map((item, index) => (index % 2 === 0 ? item.currentDate : ""))}
|
||||||
|
enableSlices="x"
|
||||||
|
sliceTooltip={(datum) => (
|
||||||
|
<div className="rounded-md border border-brand-base bg-brand-surface-2 p-2 text-xs">
|
||||||
|
{datum.slice.points[0].data.yFormatted}
|
||||||
|
<span className="text-brand-secondary"> issues pending on </span>
|
||||||
|
{datum.slice.points[0].data.xFormatted}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
theme={{
|
theme={{
|
||||||
background: "transparent",
|
background: "transparent",
|
||||||
axis: {
|
axis: {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback } from "react";
|
import React, { useCallback, useState } from "react";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
@ -10,12 +10,19 @@ import {
|
|||||||
ViewAssigneeSelect,
|
ViewAssigneeSelect,
|
||||||
ViewDueDateSelect,
|
ViewDueDateSelect,
|
||||||
ViewEstimateSelect,
|
ViewEstimateSelect,
|
||||||
|
ViewLabelSelect,
|
||||||
ViewPrioritySelect,
|
ViewPrioritySelect,
|
||||||
ViewStateSelect,
|
ViewStateSelect,
|
||||||
} from "components/issues";
|
} from "components/issues";
|
||||||
|
import { Popover2 } from "@blueprintjs/popover2";
|
||||||
// icons
|
// icons
|
||||||
import { CustomMenu, Icon } from "components/ui";
|
import { Icon } from "components/ui";
|
||||||
import { LinkIcon, PencilIcon, TrashIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
import {
|
||||||
|
EllipsisHorizontalIcon,
|
||||||
|
LinkIcon,
|
||||||
|
PencilIcon,
|
||||||
|
TrashIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
// hooks
|
// hooks
|
||||||
import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
|
import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
@ -26,10 +33,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";
|
||||||
|
|
||||||
@ -58,6 +66,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
|
|||||||
userAuth,
|
userAuth,
|
||||||
nestingLevel,
|
nestingLevel,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
||||||
@ -67,7 +76,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,25 +87,58 @@ 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);
|
if (issue.parent) {
|
||||||
|
mutate(SUB_ISSUES(issue.parent as string));
|
||||||
|
} else {
|
||||||
|
mutate(fetchKey);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@ -128,27 +170,87 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
|
|||||||
className="relative group grid auto-rows-[minmax(44px,1fr)] hover:rounded-sm hover:bg-brand-surface-2 border-b border-brand-base w-full min-w-max"
|
className="relative group grid auto-rows-[minmax(44px,1fr)] hover:rounded-sm hover:bg-brand-surface-2 border-b border-brand-base w-full min-w-max"
|
||||||
style={{ gridTemplateColumns }}
|
style={{ gridTemplateColumns }}
|
||||||
>
|
>
|
||||||
<div className="flex gap-1.5 items-center px-4 sticky left-0 z-[1] text-brand-secondary bg-brand-base group-hover:text-brand-base group-hover:bg-brand-surface-2 border-brand-base w-full">
|
<div className="flex gap-1.5 items-center px-4 sticky z-10 left-0 text-brand-secondary bg-brand-base group-hover:text-brand-base group-hover:bg-brand-surface-2 border-brand-base w-full">
|
||||||
<span className="flex gap-1 items-center" style={issue.parent ? { paddingLeft } : {}}>
|
<div className="flex gap-1.5 items-center" style={issue.parent ? { paddingLeft } : {}}>
|
||||||
<div className="flex items-center cursor-pointer text-xs text-center hover:text-brand-base w-14 opacity-100 group-hover:opacity-0">
|
<div className="relative flex items-center cursor-pointer text-xs text-center hover:text-brand-base w-14">
|
||||||
{properties.key && (
|
{properties.key && (
|
||||||
<span>
|
<span className="flex items-center justify-center opacity-100 group-hover:opacity-0">
|
||||||
{issue.project_detail?.identifier}-{issue.sequence_id}
|
{issue.project_detail?.identifier}-{issue.sequence_id}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{!isNotAllowed && (
|
||||||
|
<div className="absolute top-0 left-2.5 opacity-0 group-hover:opacity-100">
|
||||||
|
<Popover2
|
||||||
|
isOpen={isOpen}
|
||||||
|
canEscapeKeyClose
|
||||||
|
onInteraction={(nextOpenState) => setIsOpen(nextOpenState)}
|
||||||
|
content={
|
||||||
|
<div
|
||||||
|
className={`flex flex-col gap-1.5 overflow-y-scroll whitespace-nowrap rounded-md border p-1 text-xs shadow-lg focus:outline-none max-h-44 min-w-full border-brand-base bg-brand-surface-1`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="hover:text-brand-muted-1 w-full select-none gap-2 truncate rounded px-1 py-1.5 text-left text-brand-secondary hover:bg-brand-surface-2"
|
||||||
|
onClick={() => {
|
||||||
|
handleEditIssue(issue);
|
||||||
|
setIsOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-start gap-2">
|
||||||
|
<PencilIcon className="h-4 w-4" />
|
||||||
|
<span>Edit issue</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="hover:text-brand-muted-1 w-full select-none gap-2 truncate rounded px-1 py-1.5 text-left text-brand-secondary hover:bg-brand-surface-2"
|
||||||
|
onClick={() => {
|
||||||
|
handleDeleteIssue(issue);
|
||||||
|
setIsOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-start gap-2">
|
||||||
|
<TrashIcon className="h-4 w-4" />
|
||||||
|
<span>Delete issue</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="hover:text-brand-muted-1 w-full select-none gap-2 truncate rounded px-1 py-1.5 text-left text-brand-secondary hover:bg-brand-surface-2"
|
||||||
|
onClick={() => {
|
||||||
|
handleCopyText();
|
||||||
|
setIsOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-start gap-2">
|
||||||
|
<LinkIcon className="h-4 w-4" />
|
||||||
|
<span>Copy issue link</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
placement="bottom-start"
|
||||||
|
>
|
||||||
|
<EllipsisHorizontalIcon className="h-5 w-5 text-brand-secondary" />
|
||||||
|
</Popover2>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="h-5 w-5">
|
<div className="h-6 w-6 flex justify-center items-center">
|
||||||
{issue.sub_issues_count > 0 && (
|
{issue.sub_issues_count > 0 && (
|
||||||
<button
|
<button
|
||||||
className="h-5 w-5 hover:bg-brand-surface-1 hover:text-brand-base rounded-sm"
|
className="h-5 w-5 hover:bg-brand-surface-1 hover:text-brand-base rounded-sm cursor-pointer"
|
||||||
onClick={() => handleToggleExpand(issue.id)}
|
onClick={() => handleToggleExpand(issue.id)}
|
||||||
>
|
>
|
||||||
<Icon iconName="chevron_right" className={`${expanded ? "rotate-90" : ""}`} />
|
<Icon iconName="chevron_right" className={`${expanded ? "rotate-90" : ""}`} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</div>
|
||||||
|
|
||||||
<Link href={`/${workspaceSlug}/projects/${issue?.project_detail?.id}/issues/${issue.id}`}>
|
<Link href={`/${workspaceSlug}/projects/${issue?.project_detail?.id}/issues/${issue.id}`}>
|
||||||
<a className="truncate text-brand-base cursor-pointer w-full text-[0.825rem]">
|
<a className="truncate text-brand-base cursor-pointer w-full text-[0.825rem]">
|
||||||
{issue.name}
|
{issue.name}
|
||||||
@ -191,30 +293,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
|
||||||
@ -237,35 +328,6 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div
|
|
||||||
className="absolute top-2.5 z-10 cursor-pointer opacity-0 group-hover:opacity-100"
|
|
||||||
style={{
|
|
||||||
left: `${nestingLevel * 68 + 24}px`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{!isNotAllowed && (
|
|
||||||
<CustomMenu width="auto" position="left" ellipsis>
|
|
||||||
<CustomMenu.MenuItem onClick={() => handleEditIssue(issue)}>
|
|
||||||
<div className="flex items-center justify-start gap-2">
|
|
||||||
<PencilIcon className="h-4 w-4" />
|
|
||||||
<span>Edit issue</span>
|
|
||||||
</div>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
<CustomMenu.MenuItem onClick={() => handleDeleteIssue(issue)}>
|
|
||||||
<div className="flex items-center justify-start gap-2">
|
|
||||||
<TrashIcon className="h-4 w-4" />
|
|
||||||
<span>Delete issue</span>
|
|
||||||
</div>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
<CustomMenu.MenuItem onClick={handleCopyText}>
|
|
||||||
<div className="flex items-center justify-start gap-2">
|
|
||||||
<LinkIcon className="h-4 w-4" />
|
|
||||||
<span>Copy issue link</span>
|
|
||||||
</div>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
</CustomMenu>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -40,7 +40,7 @@ export const SpreadsheetColumns: React.FC<Props> = ({ columnData, gridTemplateCo
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`bg-brand-surface-1 w-full ${
|
className={`bg-brand-surface-1 w-full ${
|
||||||
col.propertyName === "title" ? "sticky left-0 z-[2] bg-brand-surface-1 pl-24" : ""
|
col.propertyName === "title" ? "sticky left-0 z-20 bg-brand-surface-1 pl-24" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{col.propertyName === "title" ? (
|
{col.propertyName === "title" ? (
|
||||||
|
@ -5,7 +5,7 @@ import { useRouter } from "next/router";
|
|||||||
|
|
||||||
// components
|
// components
|
||||||
import { SpreadsheetColumns, SpreadsheetIssues } from "components/core";
|
import { SpreadsheetColumns, SpreadsheetIssues } from "components/core";
|
||||||
import { Icon, Spinner } from "components/ui";
|
import { CustomMenu, Icon, Spinner } from "components/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import useIssuesProperties from "hooks/use-issue-properties";
|
import useIssuesProperties from "hooks/use-issue-properties";
|
||||||
import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
|
import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
|
||||||
@ -17,15 +17,21 @@ import { SPREADSHEET_COLUMN } from "constants/spreadsheet";
|
|||||||
import { PlusIcon } from "@heroicons/react/24/outline";
|
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
type: "issue" | "cycle" | "module";
|
||||||
handleEditIssue: (issue: IIssue) => void;
|
handleEditIssue: (issue: IIssue) => void;
|
||||||
handleDeleteIssue: (issue: IIssue) => void;
|
handleDeleteIssue: (issue: IIssue) => void;
|
||||||
|
openIssuesListModal?: (() => void) | null;
|
||||||
|
isCompleted?: boolean;
|
||||||
user: ICurrentUserResponse | undefined;
|
user: ICurrentUserResponse | undefined;
|
||||||
userAuth: UserAuth;
|
userAuth: UserAuth;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SpreadsheetView: React.FC<Props> = ({
|
export const SpreadsheetView: React.FC<Props> = ({
|
||||||
|
type,
|
||||||
handleEditIssue,
|
handleEditIssue,
|
||||||
handleDeleteIssue,
|
handleDeleteIssue,
|
||||||
|
openIssuesListModal,
|
||||||
|
isCompleted = false,
|
||||||
user,
|
user,
|
||||||
userAuth,
|
userAuth,
|
||||||
}) => {
|
}) => {
|
||||||
@ -56,7 +62,7 @@ export const SpreadsheetView: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full rounded-lg text-brand-secondary overflow-x-auto whitespace-nowrap bg-brand-base">
|
<div className="h-full rounded-lg text-brand-secondary overflow-x-auto whitespace-nowrap bg-brand-base">
|
||||||
<div className="sticky z-[2] top-0 border-b border-brand-base bg-brand-surface-1 w-full min-w-max">
|
<div className="sticky z-20 top-0 border-b border-brand-base bg-brand-surface-1 w-full min-w-max">
|
||||||
<SpreadsheetColumns columnData={columnData} gridTemplateColumns={gridTemplateColumns} />
|
<SpreadsheetColumns columnData={columnData} gridTemplateColumns={gridTemplateColumns} />
|
||||||
</div>
|
</div>
|
||||||
{spreadsheetIssues ? (
|
{spreadsheetIssues ? (
|
||||||
@ -75,16 +81,55 @@ export const SpreadsheetView: React.FC<Props> = ({
|
|||||||
userAuth={userAuth}
|
userAuth={userAuth}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<button
|
<div
|
||||||
className="flex items-center gap-1.5 pl-7 py-2.5 text-sm text-brand-secondary hover:text-brand-base hover:bg-brand-surface-2 border-b border-brand-base w-full min-w-max"
|
className="relative group grid auto-rows-[minmax(44px,1fr)] hover:rounded-sm hover:bg-brand-surface-2 border-b border-brand-base w-full min-w-max"
|
||||||
onClick={() => {
|
style={{ gridTemplateColumns }}
|
||||||
const e = new KeyboardEvent("keydown", { key: "c" });
|
|
||||||
document.dispatchEvent(e);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<PlusIcon className="h-4 w-4" />
|
{type === "issue" ? (
|
||||||
Add Issue
|
<button
|
||||||
</button>
|
className="flex gap-1.5 items-center pl-7 py-2.5 text-sm sticky left-0 z-[1] text-brand-secondary bg-brand-base group-hover:text-brand-base group-hover:bg-brand-surface-2 border-brand-base w-full"
|
||||||
|
onClick={() => {
|
||||||
|
const e = new KeyboardEvent("keydown", { key: "c" });
|
||||||
|
document.dispatchEvent(e);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusIcon className="h-4 w-4" />
|
||||||
|
Add Issue
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
!isCompleted && (
|
||||||
|
<CustomMenu
|
||||||
|
className="sticky left-0 z-[1]"
|
||||||
|
customButton={
|
||||||
|
<button
|
||||||
|
className="flex gap-1.5 items-center pl-7 py-2.5 text-sm sticky left-0 z-[1] text-brand-secondary bg-brand-base group-hover:text-brand-base group-hover:bg-brand-surface-2 border-brand-base w-full"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<PlusIcon className="h-4 w-4" />
|
||||||
|
Add Issue
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
position="left"
|
||||||
|
menuItemsClassName="left-5 !w-36"
|
||||||
|
noBorder
|
||||||
|
>
|
||||||
|
<CustomMenu.MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
const e = new KeyboardEvent("keydown", { key: "c" });
|
||||||
|
document.dispatchEvent(e);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create new
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
{openIssuesListModal && (
|
||||||
|
<CustomMenu.MenuItem onClick={openIssuesListModal}>
|
||||||
|
Add an existing issue
|
||||||
|
</CustomMenu.MenuItem>
|
||||||
|
)}
|
||||||
|
</CustomMenu>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
|
@ -23,7 +23,6 @@ import {
|
|||||||
IssueStateSelect,
|
IssueStateSelect,
|
||||||
} from "components/issues/select";
|
} from "components/issues/select";
|
||||||
import { CreateStateModal } from "components/states";
|
import { CreateStateModal } from "components/states";
|
||||||
import { CreateUpdateCycleModal } from "components/cycles";
|
|
||||||
import { CreateLabelModal } from "components/labels";
|
import { CreateLabelModal } from "components/labels";
|
||||||
// ui
|
// ui
|
||||||
import {
|
import {
|
||||||
@ -73,7 +72,6 @@ const defaultValues: Partial<IIssue> = {
|
|||||||
description_html: "<p></p>",
|
description_html: "<p></p>",
|
||||||
estimate_point: null,
|
estimate_point: null,
|
||||||
state: "",
|
state: "",
|
||||||
cycle: null,
|
|
||||||
priority: null,
|
priority: null,
|
||||||
assignees: [],
|
assignees: [],
|
||||||
assignees_list: [],
|
assignees_list: [],
|
||||||
@ -122,7 +120,6 @@ export const IssueForm: FC<IssueFormProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
// states
|
// states
|
||||||
const [mostSimilarIssue, setMostSimilarIssue] = useState<IIssue | undefined>();
|
const [mostSimilarIssue, setMostSimilarIssue] = useState<IIssue | undefined>();
|
||||||
const [cycleModal, setCycleModal] = useState(false);
|
|
||||||
const [stateModal, setStateModal] = useState(false);
|
const [stateModal, setStateModal] = useState(false);
|
||||||
const [labelModal, setLabelModal] = useState(false);
|
const [labelModal, setLabelModal] = useState(false);
|
||||||
const [parentIssueListModalOpen, setParentIssueListModalOpen] = useState(false);
|
const [parentIssueListModalOpen, setParentIssueListModalOpen] = useState(false);
|
||||||
@ -252,11 +249,6 @@ export const IssueForm: FC<IssueFormProps> = ({
|
|||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
user={user}
|
user={user}
|
||||||
/>
|
/>
|
||||||
<CreateUpdateCycleModal
|
|
||||||
isOpen={cycleModal}
|
|
||||||
handleClose={() => setCycleModal(false)}
|
|
||||||
user={user}
|
|
||||||
/>
|
|
||||||
<CreateLabelModal
|
<CreateLabelModal
|
||||||
isOpen={labelModal}
|
isOpen={labelModal}
|
||||||
handleClose={() => setLabelModal(false)}
|
handleClose={() => setLabelModal(false)}
|
||||||
|
@ -82,12 +82,17 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
const { params: inboxParams } = useInboxView();
|
const { params: inboxParams } = useInboxView();
|
||||||
const { params: spreadsheetParams } = useSpreadsheetIssuesView();
|
const { params: spreadsheetParams } = useSpreadsheetIssuesView();
|
||||||
|
|
||||||
if (cycleId) prePopulateData = { ...prePopulateData, cycle: cycleId as string };
|
|
||||||
if (moduleId) prePopulateData = { ...prePopulateData, module: moduleId as string };
|
|
||||||
|
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
|
if (cycleId) prePopulateData = { ...prePopulateData, cycle: cycleId as string };
|
||||||
|
if (moduleId) prePopulateData = { ...prePopulateData, module: moduleId as string };
|
||||||
|
if (router.asPath.includes("my-issues"))
|
||||||
|
prePopulateData = {
|
||||||
|
...prePopulateData,
|
||||||
|
assignees: [...(prePopulateData?.assignees ?? []), user?.id ?? ""],
|
||||||
|
};
|
||||||
|
|
||||||
const { data: issues } = useSWR(
|
const { data: issues } = useSWR(
|
||||||
workspaceSlug && activeProject
|
workspaceSlug && activeProject
|
||||||
? PROJECT_ISSUES_LIST(workspaceSlug as string, activeProject ?? "")
|
? PROJECT_ISSUES_LIST(workspaceSlug as string, activeProject ?? "")
|
||||||
@ -121,7 +126,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
}, [handleClose]);
|
}, [handleClose]);
|
||||||
|
|
||||||
const addIssueToCycle = async (issueId: string, cycleId: string) => {
|
const addIssueToCycle = async (issueId: string, cycleId: string) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !activeProject) return;
|
||||||
|
|
||||||
await issuesService
|
await issuesService
|
||||||
.addIssueToCycle(
|
.addIssueToCycle(
|
||||||
@ -142,7 +147,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addIssueToModule = async (issueId: string, moduleId: string) => {
|
const addIssueToModule = async (issueId: string, moduleId: string) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !activeProject) return;
|
||||||
|
|
||||||
await modulesService
|
await modulesService
|
||||||
.addIssuesToModule(
|
.addIssuesToModule(
|
||||||
@ -163,7 +168,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addIssueToInbox = async (formData: Partial<IIssue>) => {
|
const addIssueToInbox = async (formData: Partial<IIssue>) => {
|
||||||
if (!workspaceSlug || !projectId || !inboxId) return;
|
if (!workspaceSlug || !activeProject || !inboxId) return;
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
issue: {
|
issue: {
|
||||||
@ -178,7 +183,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
await inboxServices
|
await inboxServices
|
||||||
.createInboxIssue(
|
.createInboxIssue(
|
||||||
workspaceSlug.toString(),
|
workspaceSlug.toString(),
|
||||||
projectId.toString(),
|
activeProject.toString(),
|
||||||
inboxId.toString(),
|
inboxId.toString(),
|
||||||
payload,
|
payload,
|
||||||
user
|
user
|
||||||
@ -191,7 +196,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.push(
|
router.push(
|
||||||
`/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}?inboxIssueId=${res.issue_inbox[0].id}`
|
`/${workspaceSlug}/projects/${activeProject}/inbox/${inboxId}?inboxIssueId=${res.issue_inbox[0].id}`
|
||||||
);
|
);
|
||||||
|
|
||||||
mutate(INBOX_ISSUES(inboxId.toString(), inboxParams));
|
mutate(INBOX_ISSUES(inboxId.toString(), inboxParams));
|
||||||
@ -211,7 +216,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), calendarParams)
|
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), calendarParams)
|
||||||
: viewId
|
: viewId
|
||||||
? VIEW_ISSUES(viewId.toString(), calendarParams)
|
? VIEW_ISSUES(viewId.toString(), calendarParams)
|
||||||
: PROJECT_ISSUES_LIST_WITH_PARAMS(projectId?.toString() ?? "", calendarParams);
|
: PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject?.toString() ?? "", calendarParams);
|
||||||
|
|
||||||
const spreadsheetFetchKey = cycleId
|
const spreadsheetFetchKey = cycleId
|
||||||
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), spreadsheetParams)
|
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), spreadsheetParams)
|
||||||
@ -219,7 +224,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), spreadsheetParams)
|
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), spreadsheetParams)
|
||||||
: viewId
|
: viewId
|
||||||
? VIEW_ISSUES(viewId.toString(), spreadsheetParams)
|
? VIEW_ISSUES(viewId.toString(), spreadsheetParams)
|
||||||
: PROJECT_ISSUES_LIST_WITH_PARAMS(projectId?.toString() ?? "", spreadsheetParams);
|
: PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject?.toString() ?? "", spreadsheetParams);
|
||||||
|
|
||||||
const ganttFetchKey = cycleId
|
const ganttFetchKey = cycleId
|
||||||
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString())
|
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString())
|
||||||
@ -227,10 +232,10 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString())
|
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString())
|
||||||
: viewId
|
: viewId
|
||||||
? VIEW_ISSUES(viewId.toString(), viewGanttParams)
|
? VIEW_ISSUES(viewId.toString(), viewGanttParams)
|
||||||
: PROJECT_ISSUES_LIST_WITH_PARAMS(projectId?.toString() ?? "");
|
: PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject?.toString() ?? "");
|
||||||
|
|
||||||
const createIssue = async (payload: Partial<IIssue>) => {
|
const createIssue = async (payload: Partial<IIssue>) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !activeProject) return;
|
||||||
|
|
||||||
if (inboxId) await addIssueToInbox(payload);
|
if (inboxId) await addIssueToInbox(payload);
|
||||||
else
|
else
|
||||||
@ -252,7 +257,8 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
message: "Issue created successfully.",
|
message: "Issue created successfully.",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (payload.assignees_list?.some((assignee) => assignee === user?.id)) mutate(USER_ISSUE);
|
if (payload.assignees_list?.some((assignee) => assignee === user?.id))
|
||||||
|
mutate(USER_ISSUE(workspaceSlug as string));
|
||||||
|
|
||||||
if (payload.parent && payload.parent !== "") mutate(SUB_ISSUES(payload.parent));
|
if (payload.parent && payload.parent !== "") mutate(SUB_ISSUES(payload.parent));
|
||||||
})
|
})
|
||||||
|
@ -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(
|
||||||
{
|
{
|
||||||
|
@ -15,7 +15,7 @@ import stateService from "services/state.service";
|
|||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
import { CustomSelect, Input, PrimaryButton, SecondaryButton } from "components/ui";
|
import { CustomSelect, Input, PrimaryButton, SecondaryButton, Tooltip } from "components/ui";
|
||||||
// types
|
// types
|
||||||
import type { ICurrentUserResponse, IState, IStateResponse } from "types";
|
import type { ICurrentUserResponse, IState, IStateResponse } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
@ -28,6 +28,7 @@ type Props = {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
selectedGroup: StateGroup | null;
|
selectedGroup: StateGroup | null;
|
||||||
user: ICurrentUserResponse | undefined;
|
user: ICurrentUserResponse | undefined;
|
||||||
|
groupLength: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StateGroup = "backlog" | "unstarted" | "started" | "completed" | "cancelled" | null;
|
export type StateGroup = "backlog" | "unstarted" | "started" | "completed" | "cancelled" | null;
|
||||||
@ -43,6 +44,7 @@ export const CreateUpdateStateInline: React.FC<Props> = ({
|
|||||||
onClose,
|
onClose,
|
||||||
selectedGroup,
|
selectedGroup,
|
||||||
user,
|
user,
|
||||||
|
groupLength,
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
@ -174,9 +176,8 @@ export const CreateUpdateStateInline: React.FC<Props> = ({
|
|||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<Popover.Button
|
<Popover.Button
|
||||||
className={`group inline-flex items-center text-base font-medium focus:outline-none ${
|
className={`group inline-flex items-center text-base font-medium focus:outline-none ${open ? "text-brand-base" : "text-brand-secondary"
|
||||||
open ? "text-brand-base" : "text-brand-secondary"
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{watch("color") && watch("color") !== "" && (
|
{watch("color") && watch("color") !== "" && (
|
||||||
<span
|
<span
|
||||||
@ -228,22 +229,27 @@ export const CreateUpdateStateInline: React.FC<Props> = ({
|
|||||||
name="group"
|
name="group"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<CustomSelect
|
<Tooltip tooltipContent={groupLength === 1 ? "Cannot have an empty group." : "Choose State"} >
|
||||||
value={value}
|
<div>
|
||||||
onChange={onChange}
|
<CustomSelect
|
||||||
label={
|
disabled={groupLength === 1}
|
||||||
Object.keys(GROUP_CHOICES).find((k) => k === value.toString())
|
value={value}
|
||||||
? GROUP_CHOICES[value.toString() as keyof typeof GROUP_CHOICES]
|
onChange={onChange}
|
||||||
: "Select group"
|
label={
|
||||||
}
|
Object.keys(GROUP_CHOICES).find((k) => k === value.toString())
|
||||||
input
|
? GROUP_CHOICES[value.toString() as keyof typeof GROUP_CHOICES]
|
||||||
>
|
: "Select group"
|
||||||
{Object.keys(GROUP_CHOICES).map((key) => (
|
}
|
||||||
<CustomSelect.Option key={key} value={key}>
|
input
|
||||||
{GROUP_CHOICES[key as keyof typeof GROUP_CHOICES]}
|
>
|
||||||
</CustomSelect.Option>
|
{Object.keys(GROUP_CHOICES).map((key) => (
|
||||||
))}
|
<CustomSelect.Option key={key} value={key}>
|
||||||
</CustomSelect>
|
{GROUP_CHOICES[key as keyof typeof GROUP_CHOICES]}
|
||||||
|
</CustomSelect.Option>
|
||||||
|
))}
|
||||||
|
</CustomSelect>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -19,6 +19,7 @@ type Props = {
|
|||||||
noChevron?: boolean;
|
noChevron?: boolean;
|
||||||
position?: "left" | "right";
|
position?: "left" | "right";
|
||||||
verticalPosition?: "top" | "bottom";
|
verticalPosition?: "top" | "bottom";
|
||||||
|
menuItemsClassName?: string;
|
||||||
customButton?: JSX.Element;
|
customButton?: JSX.Element;
|
||||||
menuItemsWhiteBg?: boolean;
|
menuItemsWhiteBg?: boolean;
|
||||||
};
|
};
|
||||||
@ -44,6 +45,7 @@ const CustomMenu = ({
|
|||||||
noChevron = false,
|
noChevron = false,
|
||||||
position = "right",
|
position = "right",
|
||||||
verticalPosition = "bottom",
|
verticalPosition = "bottom",
|
||||||
|
menuItemsClassName = "",
|
||||||
customButton,
|
customButton,
|
||||||
menuItemsWhiteBg = false,
|
menuItemsWhiteBg = false,
|
||||||
}: Props) => (
|
}: Props) => (
|
||||||
@ -133,7 +135,7 @@ const CustomMenu = ({
|
|||||||
menuItemsWhiteBg
|
menuItemsWhiteBg
|
||||||
? "border-brand-surface-1 bg-brand-base"
|
? "border-brand-surface-1 bg-brand-base"
|
||||||
: "border-brand-base bg-brand-surface-1"
|
: "border-brand-base bg-brand-surface-1"
|
||||||
}`}
|
} ${menuItemsClassName}`}
|
||||||
>
|
>
|
||||||
<div className="py-1">{children}</div>
|
<div className="py-1">{children}</div>
|
||||||
</Menu.Items>
|
</Menu.Items>
|
||||||
|
@ -54,7 +54,7 @@ const CustomSelect = ({
|
|||||||
) : (
|
) : (
|
||||||
<Listbox.Button
|
<Listbox.Button
|
||||||
className={`flex w-full ${
|
className={`flex w-full ${
|
||||||
disabled ? "cursor-not-allowed" : "cursor-pointer hover:bg-brand-surface-2"
|
disabled ? "cursor-not-allowed text-brand-secondary" : "cursor-pointer hover:bg-brand-surface-2"
|
||||||
} items-center justify-between gap-1 rounded-md border border-brand-base shadow-sm duration-300 focus:outline-none ${
|
} items-center justify-between gap-1 rounded-md border border-brand-base shadow-sm duration-300 focus:outline-none ${
|
||||||
input ? "border-brand-base px-3 py-2 text-sm" : "px-2.5 py-1 text-xs"
|
input ? "border-brand-base px-3 py-2 text-sm" : "px-2.5 py-1 text-xs"
|
||||||
} ${
|
} ${
|
||||||
|
@ -3,7 +3,7 @@ import { Fragment, useState } from "react";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Menu, Transition } from "@headlessui/react";
|
import { Menu, Transition } from "@headlessui/react";
|
||||||
// icons
|
// icons
|
||||||
import { ChevronDownIcon } from "@heroicons/react/24/outline";
|
import { CheckIcon, ChevronDownIcon } from "@heroicons/react/24/outline";
|
||||||
import { ChevronRightIcon, ChevronLeftIcon } from "@heroicons/react/20/solid";
|
import { ChevronRightIcon, ChevronLeftIcon } from "@heroicons/react/20/solid";
|
||||||
|
|
||||||
type MultiLevelDropdownProps = {
|
type MultiLevelDropdownProps = {
|
||||||
@ -127,9 +127,14 @@ export const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
|||||||
}}
|
}}
|
||||||
className={`${
|
className={`${
|
||||||
child.selected ? "bg-brand-surface-2" : ""
|
child.selected ? "bg-brand-surface-2" : ""
|
||||||
} flex w-full items-center whitespace-nowrap break-words rounded px-1 py-1.5 text-left capitalize text-brand-secondary hover:bg-brand-surface-2`}
|
} flex w-full items-center justify-between whitespace-nowrap break-words rounded px-1 py-1.5 text-left capitalize text-brand-secondary hover:bg-brand-surface-2`}
|
||||||
>
|
>
|
||||||
{child.label}
|
{child.label}
|
||||||
|
<CheckIcon
|
||||||
|
className={`h-3.5 w-3.5 opacity-0 ${
|
||||||
|
child.selected ? "opacity-100" : ""
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -70,7 +70,7 @@ export const SelectFilters: React.FC<Props> = ({
|
|||||||
value: PRIORITIES,
|
value: PRIORITIES,
|
||||||
children: [
|
children: [
|
||||||
...PRIORITIES.map((priority) => ({
|
...PRIORITIES.map((priority) => ({
|
||||||
id: priority ?? "none",
|
id: priority === null ? "null" : priority,
|
||||||
label: (
|
label: (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{getPriorityIcon(priority)} {priority ?? "None"}
|
{getPriorityIcon(priority)} {priority ?? "None"}
|
||||||
@ -78,9 +78,9 @@ export const SelectFilters: React.FC<Props> = ({
|
|||||||
),
|
),
|
||||||
value: {
|
value: {
|
||||||
key: "priority",
|
key: "priority",
|
||||||
value: priority,
|
value: priority === null ? "null" : priority,
|
||||||
},
|
},
|
||||||
selected: filters?.priority?.includes(priority ?? "none"),
|
selected: filters?.priority?.includes(priority === null ? "null" : priority),
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -60,6 +60,14 @@ export const CompletedIssuesGraph: React.FC<Props> = ({ month, issues, setMonth
|
|||||||
margin={{ top: 20, right: 20, bottom: 20, left: 20 }}
|
margin={{ top: 20, right: 20, bottom: 20, left: 20 }}
|
||||||
customYAxisTickValues={data.map((item) => item.completed_count)}
|
customYAxisTickValues={data.map((item) => item.completed_count)}
|
||||||
colors={(datum) => datum.color}
|
colors={(datum) => datum.color}
|
||||||
|
enableSlices="x"
|
||||||
|
sliceTooltip={(datum) => (
|
||||||
|
<div className="rounded-md border border-brand-base bg-brand-surface-2 p-2 text-xs">
|
||||||
|
{datum.slice.points[0].data.yFormatted}
|
||||||
|
<span className="text-brand-secondary"> issues closed in </span>
|
||||||
|
{datum.slice.points[0].data.xFormatted}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
theme={{
|
theme={{
|
||||||
background: "rgb(var(--color-bg-base))",
|
background: "rgb(var(--color-bg-base))",
|
||||||
}}
|
}}
|
||||||
|
@ -6,10 +6,15 @@ type Props = {
|
|||||||
left?: JSX.Element;
|
left?: JSX.Element;
|
||||||
right?: JSX.Element;
|
right?: JSX.Element;
|
||||||
setToggleSidebar: React.Dispatch<React.SetStateAction<boolean>>;
|
setToggleSidebar: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
noHeader: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Header: React.FC<Props> = ({ breadcrumbs, left, right, setToggleSidebar }) => (
|
const Header: React.FC<Props> = ({ breadcrumbs, left, right, setToggleSidebar, noHeader }) => (
|
||||||
<div className="relative flex w-full flex-shrink-0 flex-row items-center justify-between gap-y-4 border-b border-brand-base bg-brand-sidebar px-5 py-4">
|
<div
|
||||||
|
className={`relative flex w-full flex-shrink-0 flex-row items-center justify-between gap-y-4 border-b border-brand-base bg-brand-sidebar px-5 py-4 ${
|
||||||
|
noHeader ? "md:hidden" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="block md:hidden">
|
<div className="block md:hidden">
|
||||||
<button
|
<button
|
||||||
|
@ -100,14 +100,13 @@ const ProjectAuthorizationWrapped: React.FC<Props> = ({
|
|||||||
: "bg-brand-base"
|
: "bg-brand-base"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{!noHeader && (
|
<AppHeader
|
||||||
<AppHeader
|
breadcrumbs={breadcrumbs}
|
||||||
breadcrumbs={breadcrumbs}
|
left={left}
|
||||||
left={left}
|
right={right}
|
||||||
right={right}
|
setToggleSidebar={setToggleSidebar}
|
||||||
setToggleSidebar={setToggleSidebar}
|
noHeader={noHeader}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
<div className="h-full w-full overflow-hidden">
|
<div className="h-full w-full overflow-hidden">
|
||||||
<div className="h-full w-full overflow-x-hidden overflow-y-scroll">{children}</div>
|
<div className="h-full w-full overflow-x-hidden overflow-y-scroll">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -107,14 +107,13 @@ export const WorkspaceAuthorizationLayout: React.FC<Props> = ({
|
|||||||
: "bg-brand-base"
|
: "bg-brand-base"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{!noHeader && (
|
<AppHeader
|
||||||
<AppHeader
|
breadcrumbs={breadcrumbs}
|
||||||
breadcrumbs={breadcrumbs}
|
left={left}
|
||||||
left={left}
|
right={right}
|
||||||
right={right}
|
setToggleSidebar={setToggleSidebar}
|
||||||
setToggleSidebar={setToggleSidebar}
|
noHeader={noHeader}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
<div className="h-full w-full overflow-hidden">
|
<div className="h-full w-full overflow-hidden">
|
||||||
<div className="relative h-full w-full overflow-x-hidden overflow-y-scroll">
|
<div className="relative h-full w-full overflow-x-hidden overflow-y-scroll">
|
||||||
{children}
|
{children}
|
||||||
|
@ -98,6 +98,7 @@ const StatesSettings: NextPage = () => {
|
|||||||
<div className="divide-y divide-brand-base rounded-[10px] border border-brand-base">
|
<div className="divide-y divide-brand-base rounded-[10px] border border-brand-base">
|
||||||
{key === activeGroup && (
|
{key === activeGroup && (
|
||||||
<CreateUpdateStateInline
|
<CreateUpdateStateInline
|
||||||
|
groupLength={orderedStateGroups[key].length}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setActiveGroup(null);
|
setActiveGroup(null);
|
||||||
setSelectedState(null);
|
setSelectedState(null);
|
||||||
@ -128,6 +129,7 @@ const StatesSettings: NextPage = () => {
|
|||||||
setActiveGroup(null);
|
setActiveGroup(null);
|
||||||
setSelectedState(null);
|
setSelectedState(null);
|
||||||
}}
|
}}
|
||||||
|
groupLength={orderedStateGroups[key].length}
|
||||||
data={
|
data={
|
||||||
statesList?.find((state) => state.id === selectedState) ?? null
|
statesList?.find((state) => state.id === selectedState) ?? null
|
||||||
}
|
}
|
||||||
|
@ -225,3 +225,8 @@ body {
|
|||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
-webkit-line-clamp: 1;
|
-webkit-line-clamp: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* popover2 styling */
|
||||||
|
.bp4-popover2-transition-container {
|
||||||
|
z-index: 20 !important;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user