Merge branch 'develop' of github.com:makeplane/plane into feat/issue_relation

This commit is contained in:
NarayanBavisetti 2023-09-13 12:19:15 +05:30
commit 4bd5e56af2
68 changed files with 1193 additions and 1198 deletions

View File

@ -23,6 +23,8 @@ NEXT_PUBLIC_SLACK_CLIENT_ID=""
NEXT_PUBLIC_PLAUSIBLE_DOMAIN="" NEXT_PUBLIC_PLAUSIBLE_DOMAIN=""
# public boards deploy url # public boards deploy url
NEXT_PUBLIC_DEPLOY_URL="" NEXT_PUBLIC_DEPLOY_URL=""
# plane deploy using nginx
NEXT_PUBLIC_DEPLOY_WITH_NGINX=1
# Backend # Backend
# Debug value for api server use it as 0 for production use # Debug value for api server use it as 0 for production use

View File

@ -92,6 +92,7 @@ from plane.api.views import (
IssueReactionViewSet, IssueReactionViewSet,
IssueRelationViewSet, IssueRelationViewSet,
CommentReactionViewSet, CommentReactionViewSet,
IssueDraftViewSet,
## End Issues ## End Issues
# States # States
StateViewSet, StateViewSet,
@ -1031,6 +1032,27 @@ urlpatterns = [
name="issue-relation", name="issue-relation",
), ),
## End Issue Relation ## End Issue Relation
## Issue Drafts
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-drafts/",
IssueDraftViewSet.as_view(
{
"get": "list",
}
),
name="project-issue-draft",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-drafts/<uuid:pk>/",
IssueDraftViewSet.as_view(
{
"get": "retrieve",
"delete": "destroy",
}
),
name="project-issue-draft",
),
## End Issue Drafts
## File Assets ## File Assets
path( path(
"workspaces/<str:slug>/file-assets/", "workspaces/<str:slug>/file-assets/",

View File

@ -89,6 +89,7 @@ from .issue import (
IssueRelationViewSet, IssueRelationViewSet,
IssueRetrievePublicEndpoint, IssueRetrievePublicEndpoint,
ProjectIssuesPublicEndpoint, ProjectIssuesPublicEndpoint,
IssueDraftViewSet,
) )
from .auth_extended import ( from .auth_extended import (

View File

@ -181,7 +181,7 @@ class IssueViewSet(BaseViewSet):
filters = issue_filters(request.query_params, "GET") filters = issue_filters(request.query_params, "GET")
# Custom ordering for priority and state # Custom ordering for priority and state
priority_order = ["urgent", "high", "medium", "low", None] priority_order = ["urgent", "high", "medium", "low", "none"]
state_order = ["backlog", "unstarted", "started", "completed", "cancelled"] state_order = ["backlog", "unstarted", "started", "completed", "cancelled"]
order_by_param = request.GET.get("order_by", "-created_at") order_by_param = request.GET.get("order_by", "-created_at")
@ -334,7 +334,7 @@ class UserWorkSpaceIssues(BaseAPIView):
try: try:
filters = issue_filters(request.query_params, "GET") filters = issue_filters(request.query_params, "GET")
# Custom ordering for priority and state # Custom ordering for priority and state
priority_order = ["urgent", "high", "medium", "low", None] priority_order = ["urgent", "high", "medium", "low", "none"]
state_order = ["backlog", "unstarted", "started", "completed", "cancelled"] state_order = ["backlog", "unstarted", "started", "completed", "cancelled"]
order_by_param = request.GET.get("order_by", "-created_at") order_by_param = request.GET.get("order_by", "-created_at")
@ -1071,7 +1071,7 @@ class IssueArchiveViewSet(BaseViewSet):
show_sub_issues = request.GET.get("show_sub_issues", "true") show_sub_issues = request.GET.get("show_sub_issues", "true")
# Custom ordering for priority and state # Custom ordering for priority and state
priority_order = ["urgent", "high", "medium", "low", None] priority_order = ["urgent", "high", "medium", "low", "none"]
state_order = ["backlog", "unstarted", "started", "completed", "cancelled"] state_order = ["backlog", "unstarted", "started", "completed", "cancelled"]
order_by_param = request.GET.get("order_by", "-created_at") order_by_param = request.GET.get("order_by", "-created_at")
@ -2173,7 +2173,7 @@ class ProjectIssuesPublicEndpoint(BaseAPIView):
filters = issue_filters(request.query_params, "GET") filters = issue_filters(request.query_params, "GET")
# Custom ordering for priority and state # Custom ordering for priority and state
priority_order = ["urgent", "high", "medium", "low", None] priority_order = ["urgent", "high", "medium", "low", "none"]
state_order = ["backlog", "unstarted", "started", "completed", "cancelled"] state_order = ["backlog", "unstarted", "started", "completed", "cancelled"]
order_by_param = request.GET.get("order_by", "-created_at") order_by_param = request.GET.get("order_by", "-created_at")
@ -2335,3 +2335,157 @@ class ProjectIssuesPublicEndpoint(BaseAPIView):
{"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,
) )
class IssueDraftViewSet(BaseViewSet):
permission_classes = [
ProjectEntityPermission,
]
serializer_class = IssueFlatSerializer
model = Issue
def get_queryset(self):
return (
Issue.objects.annotate(
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.filter(project_id=self.kwargs.get("project_id"))
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(is_draft=True)
.select_related("project")
.select_related("workspace")
.select_related("state")
.select_related("parent")
.prefetch_related("assignees")
.prefetch_related("labels")
.prefetch_related(
Prefetch(
"issue_reactions",
queryset=IssueReaction.objects.select_related("actor"),
)
)
)
@method_decorator(gzip_page)
def list(self, request, slug, project_id):
try:
filters = issue_filters(request.query_params, "GET")
# Custom ordering for priority and state
priority_order = ["urgent", "high", "medium", "low", "none"]
state_order = ["backlog", "unstarted", "started", "completed", "cancelled"]
order_by_param = request.GET.get("order_by", "-created_at")
issue_queryset = (
self.get_queryset()
.filter(**filters)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(module_id=F("issue_module__module_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
attachment_count=IssueAttachment.objects.filter(
issue=OuterRef("id")
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
)
# Priority Ordering
if order_by_param == "priority" or order_by_param == "-priority":
priority_order = (
priority_order
if order_by_param == "priority"
else priority_order[::-1]
)
issue_queryset = issue_queryset.annotate(
priority_order=Case(
*[
When(priority=p, then=Value(i))
for i, p in enumerate(priority_order)
],
output_field=CharField(),
)
).order_by("priority_order")
# State Ordering
elif order_by_param in [
"state__name",
"state__group",
"-state__name",
"-state__group",
]:
state_order = (
state_order
if order_by_param in ["state__name", "state__group"]
else state_order[::-1]
)
issue_queryset = issue_queryset.annotate(
state_order=Case(
*[
When(state__group=state_group, then=Value(i))
for i, state_group in enumerate(state_order)
],
default=Value(len(state_order)),
output_field=CharField(),
)
).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:
issue_queryset = issue_queryset.order_by(order_by_param)
issues = IssueLiteSerializer(issue_queryset, many=True).data
## Grouping the results
group_by = request.GET.get("group_by", False)
if group_by:
return Response(
group_results(issues, group_by), status=status.HTTP_200_OK
)
return Response(issues, status=status.HTTP_200_OK)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
def retrieve(self, request, slug, project_id, pk=None):
try:
issue = Issue.objects.get(
workspace__slug=slug, project_id=project_id, pk=pk, is_draft=True
)
return Response(IssueSerializer(issue).data, status=status.HTTP_200_OK)
except Issue.DoesNotExist:
return Response(
{"error": "Issue Does not exist"}, status=status.HTTP_404_NOT_FOUND
)

View File

@ -1072,7 +1072,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
.order_by("state_group") .order_by("state_group")
) )
priority_order = ["urgent", "high", "medium", "low", None] priority_order = ["urgent", "high", "medium", "low", "none"]
priority_distribution = ( priority_distribution = (
Issue.issue_objects.filter( Issue.issue_objects.filter(

View File

@ -0,0 +1,137 @@
# Generated by Django 4.2.3 on 2023-09-11 16:55
from django.db import migrations
def workspace_member_props(old_props):
new_props = {
"filters": {
"priority": old_props.get("filters", {}).get("priority", None),
"state": old_props.get("filters", {}).get("state", None),
"state_group": old_props.get("filters", {}).get("state_group", None),
"assignees": old_props.get("filters", {}).get("assignees", None),
"created_by": old_props.get("filters", {}).get("created_by", None),
"labels": old_props.get("filters", {}).get("labels", None),
"start_date": old_props.get("filters", {}).get("start_date", None),
"target_date": old_props.get("filters", {}).get("target_date", None),
"subscriber": old_props.get("filters", {}).get("subscriber", None),
},
"display_filters": {
"group_by": old_props.get("groupByProperty", None),
"order_by": old_props.get("orderBy", "-created_at"),
"type": old_props.get("filters", {}).get("type", None),
"sub_issue": old_props.get("showSubIssues", True),
"show_empty_groups": old_props.get("showEmptyGroups", True),
"layout": old_props.get("issueView", "list"),
"calendar_date_range": old_props.get("calendarDateRange", ""),
},
"display_properties": {
"assignee": old_props.get("properties", {}).get("assignee",None),
"attachment_count": old_props.get("properties", {}).get("attachment_count", None),
"created_on": old_props.get("properties", {}).get("created_on", None),
"due_date": old_props.get("properties", {}).get("due_date", None),
"estimate": old_props.get("properties", {}).get("estimate", None),
"key": old_props.get("properties", {}).get("key", None),
"labels": old_props.get("properties", {}).get("labels", None),
"link": old_props.get("properties", {}).get("link", None),
"priority": old_props.get("properties", {}).get("priority", None),
"start_date": old_props.get("properties", {}).get("start_date", None),
"state": old_props.get("properties", {}).get("state", None),
"sub_issue_count": old_props.get("properties", {}).get("sub_issue_count", None),
"updated_on": old_props.get("properties", {}).get("updated_on", None),
},
}
return new_props
def project_member_props(old_props):
new_props = {
"filters": {
"priority": old_props.get("filters", {}).get("priority", None),
"state": old_props.get("filters", {}).get("state", None),
"state_group": old_props.get("filters", {}).get("state_group", None),
"assignees": old_props.get("filters", {}).get("assignees", None),
"created_by": old_props.get("filters", {}).get("created_by", None),
"labels": old_props.get("filters", {}).get("labels", None),
"start_date": old_props.get("filters", {}).get("start_date", None),
"target_date": old_props.get("filters", {}).get("target_date", None),
"subscriber": old_props.get("filters", {}).get("subscriber", None),
},
"display_filters": {
"group_by": old_props.get("groupByProperty", None),
"order_by": old_props.get("orderBy", "-created_at"),
"type": old_props.get("filters", {}).get("type", None),
"sub_issue": old_props.get("showSubIssues", True),
"show_empty_groups": old_props.get("showEmptyGroups", True),
"layout": old_props.get("issueView", "list"),
"calendar_date_range": old_props.get("calendarDateRange", ""),
},
}
return new_props
def cycle_module_props(old_props):
new_props = {
"filters": {
"priority": old_props.get("filters", {}).get("priority", None),
"state": old_props.get("filters", {}).get("state", None),
"state_group": old_props.get("filters", {}).get("state_group", None),
"assignees": old_props.get("filters", {}).get("assignees", None),
"created_by": old_props.get("filters", {}).get("created_by", None),
"labels": old_props.get("filters", {}).get("labels", None),
"start_date": old_props.get("filters", {}).get("start_date", None),
"target_date": old_props.get("filters", {}).get("target_date", None),
"subscriber": old_props.get("filters", {}).get("subscriber", None),
},
}
return new_props
def update_workspace_member_view_props(apps, schema_editor):
WorkspaceMemberModel = apps.get_model("db", "WorkspaceMember")
updated_workspace_member = []
for obj in WorkspaceMemberModel.objects.all():
obj.view_props = workspace_member_props(obj.view_props)
obj.default_props = workspace_member_props(obj.default_props)
updated_workspace_member.append(obj)
WorkspaceMemberModel.objects.bulk_update(updated_workspace_member, ["view_props", "default_props"], batch_size=100)
def update_project_member_view_props(apps, schema_editor):
ProjectMemberModel = apps.get_model("db", "ProjectMember")
updated_project_member = []
for obj in ProjectMemberModel.objects.all():
obj.view_props = project_member_props(obj.view_props)
obj.default_props = project_member_props(obj.default_props)
updated_project_member.append(obj)
ProjectMemberModel.objects.bulk_update(updated_project_member, ["view_props", "default_props"], batch_size=100)
def update_cycle_props(apps, schema_editor):
CycleModel = apps.get_model("db", "Cycle")
updated_cycle = []
for obj in CycleModel.objects.all():
if "filter" in obj.view_props:
obj.view_props = cycle_module_props(obj.view_props)
updated_cycle.append(obj)
CycleModel.objects.bulk_update(updated_cycle, ["view_props"], batch_size=100)
def update_module_props(apps, schema_editor):
ModuleModel = apps.get_model("db", "Module")
updated_module = []
for obj in ModuleModel.objects.all():
if "filter" in obj.view_props:
obj.view_props = cycle_module_props(obj.view_props)
updated_module.append(obj)
ModuleModel.objects.bulk_update(updated_module, ["view_props"], batch_size=100)
class Migration(migrations.Migration):
dependencies = [
('db', '0042_alter_analyticview_created_by_and_more'),
]
operations = [
migrations.RunPython(update_workspace_member_view_props),
migrations.RunPython(update_project_member_view_props),
migrations.RunPython(update_cycle_props),
migrations.RunPython(update_module_props),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 4.2.3 on 2023-09-12 17:33
from django.db import migrations
def update_issue_priority_choice(apps, schema_editor):
IssueModel = apps.get_model("db", "Issue")
updated_issues = []
for obj in IssueModel.objects.all():
if obj.priority is None:
obj.priority = "none"
updated_issues.append(obj)
IssueModel.objects.bulk_update(updated_issues, ["priority"], batch_size=100)
class Migration(migrations.Migration):
dependencies = [
('db', '0042_alter_analyticview_created_by_and_more'),
]
operations = [
migrations.RunPython(update_issue_priority_choice),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 4.2.3 on 2023-09-12 12:33
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('db', '0042_alter_analyticview_created_by_and_more'),
]
operations = [
migrations.AddField(
model_name='issue',
name='is_draft',
field=models.BooleanField(default=False),
),
]

View File

@ -29,6 +29,7 @@ class IssueManager(models.Manager):
| models.Q(issue_inbox__isnull=True) | models.Q(issue_inbox__isnull=True)
) )
.exclude(archived_at__isnull=False) .exclude(archived_at__isnull=False)
.exclude(is_draft=True)
) )
@ -38,6 +39,7 @@ class Issue(ProjectBaseModel):
("high", "High"), ("high", "High"),
("medium", "Medium"), ("medium", "Medium"),
("low", "Low"), ("low", "Low"),
("none", "None")
) )
parent = models.ForeignKey( parent = models.ForeignKey(
"self", "self",
@ -64,8 +66,7 @@ class Issue(ProjectBaseModel):
max_length=30, max_length=30,
choices=PRIORITY_CHOICES, choices=PRIORITY_CHOICES,
verbose_name="Issue Priority", verbose_name="Issue Priority",
null=True, default="none",
blank=True,
) )
start_date = models.DateField(null=True, blank=True) start_date = models.DateField(null=True, blank=True)
target_date = models.DateField(null=True, blank=True) target_date = models.DateField(null=True, blank=True)
@ -83,6 +84,7 @@ class Issue(ProjectBaseModel):
sort_order = models.FloatField(default=65535) sort_order = models.FloatField(default=65535)
completed_at = models.DateTimeField(null=True) completed_at = models.DateTimeField(null=True)
archived_at = models.DateField(null=True) archived_at = models.DateField(null=True)
is_draft = models.BooleanField(default=False)
objects = models.Manager() objects = models.Manager()
issue_objects = IssueManager() issue_objects = IssueManager()

View File

@ -1,6 +1,7 @@
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(",")
@ -23,7 +24,6 @@ def filter_state_group(params, filter, method):
return filter return filter
def filter_estimate_point(params, filter, method): def filter_estimate_point(params, filter, method):
if method == "GET": if method == "GET":
estimate_points = params.get("estimate_point").split(",") estimate_points = params.get("estimate_point").split(",")
@ -39,25 +39,10 @@ def filter_priority(params, filter, method):
if method == "GET": if method == "GET":
priorities = params.get("priority").split(",") priorities = params.get("priority").split(",")
if len(priorities) and "" not in priorities: if len(priorities) and "" not in priorities:
if len(priorities) == 1 and "null" in priorities: filter["priority__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")):
priorities = params.get("priority") filter["priority__in"] = 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
@ -229,7 +214,6 @@ def filter_issue_state_type(params, filter, method):
return filter return filter
def filter_project(params, filter, method): def filter_project(params, filter, method):
if method == "GET": if method == "GET":
projects = params.get("project").split(",") projects = params.get("project").split(",")

View File

@ -16,5 +16,7 @@ module.exports = {
"no-duplicate-imports": "error", "no-duplicate-imports": "error",
"arrow-body-style": ["error", "as-needed"], "arrow-body-style": ["error", "as-needed"],
"react/self-closing-comp": ["error", { component: true, html: true }], "react/self-closing-comp": ["error", { component: true, html: true }],
"@next/next/no-img-element": "off",
"@typescript-eslint/no-unused-vars": ["warn"],
}, },
}; };

View File

@ -12,4 +12,4 @@ fi
# Only perform action if $FROM and $TO are different. # Only perform action if $FROM and $TO are different.
echo "Replacing all statically built instances of $FROM with this string $TO ." echo "Replacing all statically built instances of $FROM with this string $TO ."
grep -R -la "${FROM}" apps/$DIRECTORY/.next | xargs -I{} sed -i "s|$FROM|$TO|g" "{}" grep -R -la "${FROM}" $DIRECTORY/.next | xargs -I{} sed -i "s|$FROM|$TO|g" "{}"

View File

@ -1,7 +1,4 @@
module.exports = { module.exports = {
root: true, root: true,
extends: ["custom"], extends: ["custom"],
rules: {
"@next/next/no-img-element": "off",
},
}; };

2
space/additional.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
// additional.d.ts
/// <reference types="next-images" />

View File

@ -1,9 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Link from "next/link"; import Link from "next/link";
// react hook form
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
// components // components
import { EmailResetPasswordForm } from "./email-reset-password-form"; import { EmailResetPasswordForm } from "./email-reset-password-form";

View File

@ -13,7 +13,7 @@ import useToast from "hooks/use-toast";
// components // components
import { EmailPasswordForm, GithubLoginButton, GoogleLoginButton, EmailCodeForm } from "components/accounts"; import { EmailPasswordForm, GithubLoginButton, GoogleLoginButton, EmailCodeForm } from "components/accounts";
// images // images
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; const imagePrefix = process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX ? "/spaces/" : "";
export const SignInView = observer(() => { export const SignInView = observer(() => {
const { user: userStore } = useMobxStore(); const { user: userStore } = useMobxStore();
@ -112,7 +112,7 @@ export const SignInView = observer(() => {
<div className="fixed grid place-items-center bg-custom-background-100 sm:py-5 top-11 sm:top-12 left-7 sm:left-16 lg:left-28"> <div className="fixed grid place-items-center bg-custom-background-100 sm:py-5 top-11 sm:top-12 left-7 sm:left-16 lg:left-28">
<div className="grid place-items-center bg-custom-background-100"> <div className="grid place-items-center bg-custom-background-100">
<div className="h-[30px] w-[30px]"> <div className="h-[30px] w-[30px]">
<Image src={BluePlaneLogoWithoutText} alt="Plane Logo" /> <img src={`${imagePrefix}/plane-logos/blue-without-text.png`} alt="Plane Logo" />
</div> </div>
</div> </div>
</div> </div>

View File

@ -12,7 +12,10 @@ const nextConfig = {
}; };
if (parseInt(process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX || "0")) { if (parseInt(process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX || "0")) {
const nextConfigWithNginx = withImages({ basePath: "/spaces", ...nextConfig }); const nextConfigWithNginx = withImages({
basePath: "/spaces",
...nextConfig,
});
module.exports = nextConfigWithNginx; module.exports = nextConfigWithNginx;
} else { } else {
module.exports = nextConfig; module.exports = nextConfig;

View File

@ -69,6 +69,7 @@
"@types/react": "18.0.28", "@types/react": "18.0.28",
"@types/react-dom": "18.0.11", "@types/react-dom": "18.0.11",
"@types/uuid": "^9.0.1", "@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"eslint": "8.34.0", "eslint": "8.34.0",
"eslint-config-custom": "*", "eslint-config-custom": "*",

View File

@ -1,24 +1,17 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import Image from "next/image";
// assets
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
// mobx // mobx
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// services
import authenticationService from "services/authentication.service";
// hooks
import useToast from "hooks/use-toast";
// components // components
import { OnBoardingForm } from "components/accounts/onboarding-form"; import { OnBoardingForm } from "components/accounts/onboarding-form";
const imagePrefix = process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX ? "/spaces/" : "";
const OnBoardingPage = () => { const OnBoardingPage = () => {
const { user: userStore } = useMobxStore(); const { user: userStore } = useMobxStore();
const user = userStore?.currentUser; const user = userStore?.currentUser;
const { setToastAlert } = useToast();
useEffect(() => { useEffect(() => {
const user = userStore?.currentUser; const user = userStore?.currentUser;
@ -34,7 +27,7 @@ const OnBoardingPage = () => {
<div className="absolute border-b-[0.5px] sm:border-r-[0.5px] border-custom-border-200 h-[0.5px] w-full top-1/2 left-0 -translate-y-1/2 sm:h-screen sm:w-[0.5px] sm:top-0 sm:left-1/2 md:left-1/3 sm:-translate-x-1/2 sm:translate-y-0 z-10" /> <div className="absolute border-b-[0.5px] sm:border-r-[0.5px] border-custom-border-200 h-[0.5px] w-full top-1/2 left-0 -translate-y-1/2 sm:h-screen sm:w-[0.5px] sm:top-0 sm:left-1/2 md:left-1/3 sm:-translate-x-1/2 sm:translate-y-0 z-10" />
<div className="absolute grid place-items-center bg-custom-background-100 px-3 sm:px-0 py-5 left-2 sm:left-1/2 md:left-1/3 sm:-translate-x-1/2 top-1/2 -translate-y-1/2 sm:translate-y-0 sm:top-12 z-10"> <div className="absolute grid place-items-center bg-custom-background-100 px-3 sm:px-0 py-5 left-2 sm:left-1/2 md:left-1/3 sm:-translate-x-1/2 top-1/2 -translate-y-1/2 sm:translate-y-0 sm:top-12 z-10">
<div className="h-[30px] w-[30px]"> <div className="h-[30px] w-[30px]">
<Image src={BluePlaneLogoWithoutText} alt="Plane logo" /> <img src={`${imagePrefix}/plane-logos/blue-without-text.png`} alt="Plane logo" />
</div> </div>
</div> </div>
<div className="absolute sm:fixed text-custom-text-100 text-sm font-medium right-4 top-1/4 sm:top-12 -translate-y-1/2 sm:translate-y-0 sm:right-16 sm:py-5"> <div className="absolute sm:fixed text-custom-text-100 text-sm font-medium right-4 top-1/4 sm:top-12 -translate-y-1/2 sm:translate-y-0 sm:right-16 sm:py-5">

View File

@ -1,6 +1,6 @@
{ {
"extends": "tsconfig/nextjs.json", "extends": "tsconfig/nextjs.json",
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "additional.d.ts"],
"exclude": ["node_modules"], "exclude": ["node_modules"],
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",

View File

@ -1,7 +1,4 @@
module.exports = { module.exports = {
root: true, root: true,
extends: ["custom"], extends: ["custom"],
rules: {
"@next/next/no-img-element": "off",
},
}; };

View File

@ -58,16 +58,8 @@ export const IssuesFilterView: React.FC = () => {
const isArchivedIssues = router.pathname.includes("archived-issues"); const isArchivedIssues = router.pathname.includes("archived-issues");
const { const {
issueView, displayFilters,
setIssueView, setDisplayFilters,
groupByProperty,
setGroupByProperty,
orderBy,
setOrderBy,
showEmptyGroups,
showSubIssues,
setShowSubIssues,
setShowEmptyGroups,
filters, filters,
setFilters, setFilters,
resetFilterToDefault, resetFilterToDefault,
@ -96,11 +88,11 @@ export const IssuesFilterView: React.FC = () => {
<button <button
type="button" type="button"
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80 duration-300 ${ className={`grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80 duration-300 ${
issueView === option.type displayFilters.layout === option.type
? "bg-custom-sidebar-background-80" ? "bg-custom-sidebar-background-80"
: "text-custom-sidebar-text-200" : "text-custom-sidebar-text-200"
}`} }`}
onClick={() => setIssueView(option.type)} onClick={() => setDisplayFilters({ layout: option.type })}
> >
<option.Icon <option.Icon
sx={{ sx={{
@ -174,28 +166,30 @@ export const IssuesFilterView: React.FC = () => {
<Popover.Panel className="absolute right-0 z-30 mt-1 w-screen max-w-xs transform rounded-lg border border-custom-border-200 bg-custom-background-90 p-3 shadow-lg"> <Popover.Panel className="absolute right-0 z-30 mt-1 w-screen max-w-xs transform rounded-lg border border-custom-border-200 bg-custom-background-90 p-3 shadow-lg">
<div className="relative divide-y-2 divide-custom-border-200"> <div className="relative divide-y-2 divide-custom-border-200">
<div className="space-y-4 pb-3 text-xs"> <div className="space-y-4 pb-3 text-xs">
{issueView !== "calendar" && {displayFilters.layout !== "calendar" &&
issueView !== "spreadsheet" && displayFilters.layout !== "spreadsheet" &&
issueView !== "gantt_chart" && ( displayFilters.layout !== "gantt_chart" && (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-custom-text-200">Group by</h4> <h4 className="text-custom-text-200">Group by</h4>
<div className="w-28"> <div className="w-28">
<CustomMenu <CustomMenu
label={ label={
GROUP_BY_OPTIONS.find((option) => option.key === groupByProperty) GROUP_BY_OPTIONS.find(
?.name ?? "Select" (option) => option.key === displayFilters.group_by
)?.name ?? "Select"
} }
className="!w-full" className="!w-full"
buttonClassName="w-full" buttonClassName="w-full"
> >
{GROUP_BY_OPTIONS.map((option) => { {GROUP_BY_OPTIONS.map((option) => {
if (issueView === "kanban" && option.key === null) return null; if (displayFilters.layout === "kanban" && option.key === null)
return null;
if (option.key === "project") return null; if (option.key === "project") return null;
return ( return (
<CustomMenu.MenuItem <CustomMenu.MenuItem
key={option.key} key={option.key}
onClick={() => setGroupByProperty(option.key)} onClick={() => setDisplayFilters({ group_by: option.key })}
> >
{option.name} {option.name}
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
@ -205,24 +199,27 @@ export const IssuesFilterView: React.FC = () => {
</div> </div>
</div> </div>
)} )}
{issueView !== "calendar" && issueView !== "spreadsheet" && ( {displayFilters.layout !== "calendar" &&
displayFilters.layout !== "spreadsheet" && (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-custom-text-200">Order by</h4> <h4 className="text-custom-text-200">Order by</h4>
<div className="w-28"> <div className="w-28">
<CustomMenu <CustomMenu
label={ label={
ORDER_BY_OPTIONS.find((option) => option.key === orderBy)?.name ?? ORDER_BY_OPTIONS.find(
"Select" (option) => option.key === displayFilters.order_by
)?.name ?? "Select"
} }
className="!w-full" className="!w-full"
buttonClassName="w-full" buttonClassName="w-full"
> >
{ORDER_BY_OPTIONS.map((option) => {ORDER_BY_OPTIONS.map((option) =>
groupByProperty === "priority" && option.key === "priority" ? null : ( displayFilters.group_by === "priority" &&
option.key === "priority" ? null : (
<CustomMenu.MenuItem <CustomMenu.MenuItem
key={option.key} key={option.key}
onClick={() => { onClick={() => {
setOrderBy(option.key); setDisplayFilters({ order_by: option.key });
}} }}
> >
{option.name} {option.name}
@ -238,8 +235,9 @@ export const IssuesFilterView: React.FC = () => {
<div className="w-28"> <div className="w-28">
<CustomMenu <CustomMenu
label={ label={
FILTER_ISSUE_OPTIONS.find((option) => option.key === filters.type) FILTER_ISSUE_OPTIONS.find(
?.name ?? "Select" (option) => option.key === displayFilters.type
)?.name ?? "Select"
} }
className="!w-full" className="!w-full"
buttonClassName="w-full" buttonClassName="w-full"
@ -248,7 +246,7 @@ export const IssuesFilterView: React.FC = () => {
<CustomMenu.MenuItem <CustomMenu.MenuItem
key={option.key} key={option.key}
onClick={() => onClick={() =>
setFilters({ setDisplayFilters({
type: option.key, type: option.key,
}) })
} }
@ -260,33 +258,40 @@ export const IssuesFilterView: React.FC = () => {
</div> </div>
</div> </div>
{issueView !== "calendar" && issueView !== "spreadsheet" && ( {displayFilters.layout !== "calendar" &&
displayFilters.layout !== "spreadsheet" && (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-custom-text-200">Show sub-issues</h4> <h4 className="text-custom-text-200">Show sub-issues</h4>
<div className="w-28"> <div className="w-28">
<ToggleSwitch <ToggleSwitch
value={showSubIssues} value={displayFilters.sub_issue ?? true}
onChange={() => setShowSubIssues(!showSubIssues)} onChange={() =>
setDisplayFilters({ sub_issue: !displayFilters.sub_issue })
}
/> />
</div> </div>
</div> </div>
)} )}
{issueView !== "calendar" && {displayFilters.layout !== "calendar" &&
issueView !== "spreadsheet" && displayFilters.layout !== "spreadsheet" &&
issueView !== "gantt_chart" && ( displayFilters.layout !== "gantt_chart" && (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-custom-text-200">Show empty states</h4> <h4 className="text-custom-text-200">Show empty states</h4>
<div className="w-28"> <div className="w-28">
<ToggleSwitch <ToggleSwitch
value={showEmptyGroups} value={displayFilters.show_empty_groups ?? true}
onChange={() => setShowEmptyGroups(!showEmptyGroups)} onChange={() =>
setDisplayFilters({
show_empty_groups: !displayFilters.show_empty_groups,
})
}
/> />
</div> </div>
</div> </div>
)} )}
{issueView !== "calendar" && {displayFilters.layout !== "calendar" &&
issueView !== "spreadsheet" && displayFilters.layout !== "spreadsheet" &&
issueView !== "gantt_chart" && ( displayFilters.layout !== "gantt_chart" && (
<div className="relative flex justify-end gap-x-3"> <div className="relative flex justify-end gap-x-3">
<button type="button" onClick={() => resetFilterToDefault()}> <button type="button" onClick={() => resetFilterToDefault()}>
Reset to default Reset to default
@ -302,7 +307,7 @@ export const IssuesFilterView: React.FC = () => {
)} )}
</div> </div>
{issueView !== "gantt_chart" && ( {displayFilters.layout !== "gantt_chart" && (
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">
<h4 className="text-sm text-custom-text-200">Display Properties</h4> <h4 className="text-sm text-custom-text-200">Display Properties</h4>
<div className="flex flex-wrap items-center gap-2 text-custom-text-200"> <div className="flex flex-wrap items-center gap-2 text-custom-text-200">
@ -310,7 +315,7 @@ export const IssuesFilterView: React.FC = () => {
if (key === "estimate" && !isEstimateActive) return null; if (key === "estimate" && !isEstimateActive) return null;
if ( if (
issueView === "spreadsheet" && displayFilters.layout === "spreadsheet" &&
(key === "attachment_count" || (key === "attachment_count" ||
key === "link" || key === "link" ||
key === "sub_issue_count") key === "sub_issue_count")
@ -318,7 +323,7 @@ export const IssuesFilterView: React.FC = () => {
return null; return null;
if ( if (
issueView !== "spreadsheet" && displayFilters.layout !== "spreadsheet" &&
(key === "created_on" || key === "updated_on") (key === "created_on" || key === "updated_on")
) )
return null; return null;

View File

@ -58,7 +58,7 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen, user
); );
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const { issueView, params } = useIssuesView(); const { displayFilters, params } = useIssuesView();
const { params: calendarParams } = useCalendarIssuesView(); const { params: calendarParams } = useCalendarIssuesView();
const { order_by, group_by, ...viewGanttParams } = params; const { order_by, group_by, ...viewGanttParams } = params;
@ -126,8 +126,8 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen, user
message: "Issues deleted successfully!", message: "Issues deleted successfully!",
}); });
if (issueView === "calendar") mutate(calendarFetchKey); if (displayFilters.layout === "calendar") mutate(calendarFetchKey);
else if (issueView === "gantt_chart") mutate(ganttFetchKey); else if (displayFilters.layout === "gantt_chart") mutate(ganttFetchKey);
else { else {
if (cycleId) { if (cycleId) {
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params)); mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params));

View File

@ -80,7 +80,7 @@ export const AllViews: React.FC<Props> = ({
const { user } = useUser(); const { user } = useUser();
const { memberRole } = useProjectMyMembership(); const { memberRole } = useProjectMyMembership();
const { groupedIssues, isEmpty, issueView } = viewProps; const { groupedIssues, isEmpty, displayFilters } = viewProps;
const { data: stateGroups } = useSWR( const { data: stateGroups } = useSWR(
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null, workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
@ -117,11 +117,11 @@ export const AllViews: React.FC<Props> = ({
</StrictModeDroppable> </StrictModeDroppable>
{groupedIssues ? ( {groupedIssues ? (
!isEmpty || !isEmpty ||
issueView === "kanban" || displayFilters?.layout === "kanban" ||
issueView === "calendar" || displayFilters?.layout === "calendar" ||
issueView === "gantt_chart" ? ( displayFilters?.layout === "gantt_chart" ? (
<> <>
{issueView === "list" ? ( {displayFilters?.layout === "list" ? (
<AllLists <AllLists
states={states} states={states}
addIssueToGroup={addIssueToGroup} addIssueToGroup={addIssueToGroup}
@ -134,7 +134,7 @@ export const AllViews: React.FC<Props> = ({
userAuth={memberRole} userAuth={memberRole}
viewProps={viewProps} viewProps={viewProps}
/> />
) : issueView === "kanban" ? ( ) : displayFilters?.layout === "kanban" ? (
<AllBoards <AllBoards
addIssueToGroup={addIssueToGroup} addIssueToGroup={addIssueToGroup}
disableUserActions={disableUserActions} disableUserActions={disableUserActions}
@ -149,7 +149,7 @@ export const AllViews: React.FC<Props> = ({
userAuth={memberRole} userAuth={memberRole}
viewProps={viewProps} viewProps={viewProps}
/> />
) : issueView === "calendar" ? ( ) : displayFilters?.layout === "calendar" ? (
<CalendarView <CalendarView
handleIssueAction={handleIssueAction} handleIssueAction={handleIssueAction}
addIssueToDate={addIssueToDate} addIssueToDate={addIssueToDate}
@ -157,7 +157,7 @@ export const AllViews: React.FC<Props> = ({
user={user} user={user}
userAuth={memberRole} userAuth={memberRole}
/> />
) : issueView === "spreadsheet" ? ( ) : displayFilters?.layout === "spreadsheet" ? (
<SpreadsheetView <SpreadsheetView
handleIssueAction={handleIssueAction} handleIssueAction={handleIssueAction}
openIssuesListModal={cycleId || moduleId ? openIssuesListModal : null} openIssuesListModal={cycleId || moduleId ? openIssuesListModal : null}
@ -166,7 +166,7 @@ export const AllViews: React.FC<Props> = ({
userAuth={memberRole} userAuth={memberRole}
/> />
) : ( ) : (
issueView === "gantt_chart" && <GanttChartView /> displayFilters?.layout === "gantt_chart" && <GanttChartView />
)} )}
</> </>
) : router.pathname.includes("archived-issues") ? ( ) : router.pathname.includes("archived-issues") ? (

View File

@ -36,7 +36,7 @@ export const AllBoards: React.FC<Props> = ({
userAuth, userAuth,
viewProps, viewProps,
}) => { }) => {
const { groupByProperty: selectedGroup, groupedIssues, showEmptyGroups } = viewProps; const { displayFilters, groupedIssues } = viewProps;
return ( return (
<> <>
@ -44,9 +44,12 @@ export const AllBoards: React.FC<Props> = ({
<div className="horizontal-scroll-enable flex h-full gap-x-4 p-8"> <div className="horizontal-scroll-enable flex h-full gap-x-4 p-8">
{Object.keys(groupedIssues).map((singleGroup, index) => { {Object.keys(groupedIssues).map((singleGroup, index) => {
const currentState = const currentState =
selectedGroup === "state" ? states?.find((s) => s.id === singleGroup) : null; displayFilters?.group_by === "state"
? states?.find((s) => s.id === singleGroup)
: null;
if (!showEmptyGroups && groupedIssues[singleGroup].length === 0) return null; if (!displayFilters?.show_empty_groups && groupedIssues[singleGroup].length === 0)
return null;
return ( return (
<SingleBoard <SingleBoard
@ -67,13 +70,15 @@ export const AllBoards: React.FC<Props> = ({
/> />
); );
})} })}
{!showEmptyGroups && ( {!displayFilters?.show_empty_groups && (
<div className="h-full w-96 flex-shrink-0 space-y-2 p-1"> <div className="h-full w-96 flex-shrink-0 space-y-2 p-1">
<h2 className="text-lg font-semibold">Hidden groups</h2> <h2 className="text-lg font-semibold">Hidden groups</h2>
<div className="space-y-3"> <div className="space-y-3">
{Object.keys(groupedIssues).map((singleGroup, index) => { {Object.keys(groupedIssues).map((singleGroup, index) => {
const currentState = const currentState =
selectedGroup === "state" ? states?.find((s) => s.id === singleGroup) : null; displayFilters?.group_by === "state"
? states?.find((s) => s.id === singleGroup)
: null;
if (groupedIssues[singleGroup].length === 0) if (groupedIssues[singleGroup].length === 0)
return ( return (
@ -91,7 +96,7 @@ export const AllBoards: React.FC<Props> = ({
/> />
)} )}
<h4 className="text-sm capitalize"> <h4 className="text-sm capitalize">
{selectedGroup === "state" {displayFilters?.group_by === "state"
? addSpaceIfCamelCase(currentState?.name ?? "") ? addSpaceIfCamelCase(currentState?.name ?? "")
: addSpaceIfCamelCase(singleGroup)} : addSpaceIfCamelCase(singleGroup)}
</h4> </h4>

View File

@ -48,22 +48,28 @@ export const BoardHeader: React.FC<Props> = ({
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
const { groupedIssues, groupByProperty: selectedGroup } = viewProps; const { displayFilters, groupedIssues } = viewProps;
console.log("dF", displayFilters);
const { data: issueLabels } = useSWR( const { data: issueLabels } = useSWR(
workspaceSlug && projectId && selectedGroup === "labels" workspaceSlug && projectId && displayFilters?.group_by === "labels"
? PROJECT_ISSUE_LABELS(projectId.toString()) ? PROJECT_ISSUE_LABELS(projectId.toString())
: null, : null,
workspaceSlug && projectId && selectedGroup === "labels" workspaceSlug && projectId && displayFilters?.group_by === "labels"
? () => issuesService.getIssueLabels(workspaceSlug.toString(), projectId.toString()) ? () => issuesService.getIssueLabels(workspaceSlug.toString(), projectId.toString())
: null : null
); );
const { data: members } = useSWR( const { data: members } = useSWR(
workspaceSlug && projectId && (selectedGroup === "created_by" || selectedGroup === "assignees") workspaceSlug &&
projectId &&
(displayFilters?.group_by === "created_by" || displayFilters?.group_by === "assignees")
? PROJECT_MEMBERS(projectId.toString()) ? PROJECT_MEMBERS(projectId.toString())
: null, : null,
workspaceSlug && projectId && (selectedGroup === "created_by" || selectedGroup === "assignees") workspaceSlug &&
projectId &&
(displayFilters?.group_by === "created_by" || displayFilters?.group_by === "assignees")
? () => projectService.projectMembers(workspaceSlug.toString(), projectId.toString()) ? () => projectService.projectMembers(workspaceSlug.toString(), projectId.toString())
: null : null
); );
@ -73,7 +79,7 @@ export const BoardHeader: React.FC<Props> = ({
const getGroupTitle = () => { const getGroupTitle = () => {
let title = addSpaceIfCamelCase(groupTitle); let title = addSpaceIfCamelCase(groupTitle);
switch (selectedGroup) { switch (displayFilters?.group_by) {
case "state": case "state":
title = addSpaceIfCamelCase(currentState?.name ?? ""); title = addSpaceIfCamelCase(currentState?.name ?? "");
break; break;
@ -97,7 +103,7 @@ export const BoardHeader: React.FC<Props> = ({
const getGroupIcon = () => { const getGroupIcon = () => {
let icon; let icon;
switch (selectedGroup) { switch (displayFilters?.group_by) {
case "state": case "state":
icon = currentState && ( icon = currentState && (
<StateGroupIcon <StateGroupIcon
@ -167,7 +173,7 @@ export const BoardHeader: React.FC<Props> = ({
<span className="flex items-center">{getGroupIcon()}</span> <span className="flex items-center">{getGroupIcon()}</span>
<h2 <h2
className={`text-lg font-semibold truncate ${ className={`text-lg font-semibold truncate ${
selectedGroup === "created_by" ? "" : "capitalize" displayFilters?.group_by === "created_by" ? "" : "capitalize"
}`} }`}
style={{ style={{
writingMode: isCollapsed ? "horizontal-tb" : "vertical-rl", writingMode: isCollapsed ? "horizontal-tb" : "vertical-rl",
@ -198,7 +204,7 @@ export const BoardHeader: React.FC<Props> = ({
<Icon iconName="open_in_full" className="text-base font-medium text-custom-text-900" /> <Icon iconName="open_in_full" className="text-base font-medium text-custom-text-900" />
)} )}
</button> </button>
{!disableAddIssue && !disableUserActions && selectedGroup !== "created_by" && ( {!disableAddIssue && !disableUserActions && displayFilters?.group_by !== "created_by" && (
<button <button
type="button" type="button"
className="grid h-7 w-7 place-items-center rounded p-1 text-custom-text-200 outline-none duration-300 hover:bg-custom-background-80" className="grid h-7 w-7 place-items-center rounded p-1 text-custom-text-200 outline-none duration-300 hover:bg-custom-background-80"

View File

@ -50,7 +50,7 @@ export const SingleBoard: React.FC<Props> = ({
// collapse/expand // collapse/expand
const [isCollapsed, setIsCollapsed] = useState(true); const [isCollapsed, setIsCollapsed] = useState(true);
const { groupedIssues, groupByProperty: selectedGroup, orderBy, properties } = viewProps; const { displayFilters, groupedIssues, properties } = viewProps;
const router = useRouter(); const router = useRouter();
const { cycleId, moduleId } = router.query; const { cycleId, moduleId } = router.query;
@ -80,14 +80,14 @@ export const SingleBoard: React.FC<Props> = ({
{(provided, snapshot) => ( {(provided, snapshot) => (
<div <div
className={`relative h-full ${ className={`relative h-full ${
orderBy !== "sort_order" && snapshot.isDraggingOver displayFilters?.order_by !== "sort_order" && snapshot.isDraggingOver
? "bg-custom-background-100/20" ? "bg-custom-background-100/20"
: "" : ""
} ${!isCollapsed ? "hidden" : "flex flex-col"}`} } ${!isCollapsed ? "hidden" : "flex flex-col"}`}
ref={provided.innerRef} ref={provided.innerRef}
{...provided.droppableProps} {...provided.droppableProps}
> >
{orderBy !== "sort_order" && ( {displayFilters?.order_by !== "sort_order" && (
<> <>
<div <div
className={`absolute ${ className={`absolute ${
@ -101,7 +101,11 @@ export const SingleBoard: React.FC<Props> = ({
> >
This board is ordered by{" "} This board is ordered by{" "}
{replaceUnderscoreIfSnakeCase( {replaceUnderscoreIfSnakeCase(
orderBy ? (orderBy[0] === "-" ? orderBy.slice(1) : orderBy) : "created_at" displayFilters?.order_by
? displayFilters?.order_by[0] === "-"
? displayFilters?.order_by.slice(1)
: displayFilters?.order_by
: "created_at"
)} )}
</div> </div>
</> </>
@ -145,13 +149,13 @@ export const SingleBoard: React.FC<Props> = ({
))} ))}
<span <span
style={{ style={{
display: orderBy === "sort_order" ? "inline" : "none", display: displayFilters?.order_by === "sort_order" ? "inline" : "none",
}} }}
> >
{provided.placeholder} {provided.placeholder}
</span> </span>
</div> </div>
{selectedGroup !== "created_by" && ( {displayFilters?.group_by !== "created_by" && (
<div> <div>
{type === "issue" {type === "issue"
? !disableAddIssueOption && ( ? !disableAddIssueOption && (

View File

@ -93,7 +93,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
const actionSectionRef = useRef<HTMLDivElement | null>(null); const actionSectionRef = useRef<HTMLDivElement | null>(null);
const { groupByProperty: selectedGroup, orderBy, properties, mutateIssues } = viewProps; const { displayFilters, properties, mutateIssues } = viewProps;
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId } = router.query; const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
@ -131,9 +131,9 @@ export const SingleBoardIssue: React.FC<Props> = ({
handleIssuesMutation( handleIssuesMutation(
formData, formData,
groupTitle ?? "", groupTitle ?? "",
selectedGroup, displayFilters?.group_by ?? null,
index, index,
orderBy, displayFilters?.order_by ?? "-created_at",
prevData prevData
), ),
false false
@ -149,24 +149,14 @@ export const SingleBoardIssue: React.FC<Props> = ({
if (moduleId) mutate(MODULE_DETAILS(moduleId as string)); if (moduleId) mutate(MODULE_DETAILS(moduleId as string));
}); });
}, },
[ [displayFilters, workspaceSlug, cycleId, moduleId, groupTitle, index, mutateIssues, user]
workspaceSlug,
cycleId,
moduleId,
groupTitle,
index,
selectedGroup,
mutateIssues,
orderBy,
user,
]
); );
const getStyle = ( const getStyle = (
style: DraggingStyle | NotDraggingStyle | undefined, style: DraggingStyle | NotDraggingStyle | undefined,
snapshot: DraggableStateSnapshot snapshot: DraggableStateSnapshot
) => { ) => {
if (orderBy === "sort_order") return style; if (displayFilters?.order_by === "sort_order") return style;
if (!snapshot.isDragging) return {}; if (!snapshot.isDragging) return {};
if (!snapshot.isDropAnimating) return style; if (!snapshot.isDropAnimating) return style;

View File

@ -60,7 +60,7 @@ export const CalendarView: React.FC<Props> = ({
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
const { calendarIssues, params, setCalendarDateRange } = useCalendarIssuesView(); const { calendarIssues, params, setDisplayFilters } = useCalendarIssuesView();
const totalDate = eachDayOfInterval({ const totalDate = eachDayOfInterval({
start: calendarDates.startDate, start: calendarDates.startDate,
@ -152,18 +152,20 @@ export const CalendarView: React.FC<Props> = ({
endDate, endDate,
}); });
setCalendarDateRange( setDisplayFilters({
`${renderDateFormat(startDate)};after,${renderDateFormat(endDate)};before` calendar_date_range: `${renderDateFormat(startDate)};after,${renderDateFormat(
); endDate
)};before`,
});
}; };
useEffect(() => { useEffect(() => {
setCalendarDateRange( setDisplayFilters({
`${renderDateFormat(startOfWeek(currentDate))};after,${renderDateFormat( calendar_date_range: `${renderDateFormat(startOfWeek(currentDate))};after,${renderDateFormat(
lastDayOfWeek(currentDate) lastDayOfWeek(currentDate)
)};before` )};before`,
); });
}, [currentDate]); }, [currentDate, setDisplayFilters]);
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disableUserActions; const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disableUserActions;

View File

@ -77,18 +77,8 @@ export const IssuesView: React.FC<Props> = ({
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const { const { groupedByIssues, mutateIssues, displayFilters, filters, isEmpty, setFilters, params } =
groupedByIssues, useIssuesView();
mutateIssues,
issueView,
groupByProperty: selectedGroup,
orderBy,
filters,
isEmpty,
setFilters,
params,
showEmptyGroups,
} = useIssuesView();
const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string); const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string);
const { data: stateGroups } = useSWR( const { data: stateGroups } = useSWR(
@ -129,7 +119,7 @@ export const IssuesView: React.FC<Props> = ({
if (destination.droppableId === "trashBox") { if (destination.droppableId === "trashBox") {
handleDeleteIssue(draggedItem); handleDeleteIssue(draggedItem);
} else { } else {
if (orderBy === "sort_order") { if (displayFilters.order_by === "sort_order") {
let newSortOrder = draggedItem.sort_order; let newSortOrder = draggedItem.sort_order;
const destinationGroupArray = groupedByIssues[destination.droppableId]; const destinationGroupArray = groupedByIssues[destination.droppableId];
@ -177,16 +167,19 @@ export const IssuesView: React.FC<Props> = ({
const destinationGroup = destination.droppableId; // destination group id const destinationGroup = destination.droppableId; // destination group id
if (orderBy === "sort_order" || source.droppableId !== destination.droppableId) { if (
displayFilters.order_by === "sort_order" ||
source.droppableId !== destination.droppableId
) {
// different group/column; // different group/column;
// source.droppableId !== destination.droppableId -> even if order by is not sort_order, // source.droppableId !== destination.droppableId -> even if order by is not sort_order,
// if the issue is moved to a different group, then we will change the group of the // if the issue is moved to a different group, then we will change the group of the
// dragged item(or issue) // dragged item(or issue)
if (selectedGroup === "priority") if (displayFilters.group_by === "priority")
draggedItem.priority = destinationGroup as TIssuePriorities; draggedItem.priority = destinationGroup as TIssuePriorities;
else if (selectedGroup === "state") { else if (displayFilters.group_by === "state") {
draggedItem.state = destinationGroup; draggedItem.state = destinationGroup;
draggedItem.state_detail = states?.find((s) => s.id === destinationGroup) as IState; draggedItem.state_detail = states?.find((s) => s.id === destinationGroup) as IState;
} }
@ -213,8 +206,14 @@ export const IssuesView: React.FC<Props> = ({
return { return {
...prevData, ...prevData,
[sourceGroup]: orderArrayBy(sourceGroupArray, orderBy), [sourceGroup]: orderArrayBy(
[destinationGroup]: orderArrayBy(destinationGroupArray, orderBy), sourceGroupArray,
displayFilters.order_by ?? "-created_at"
),
[destinationGroup]: orderArrayBy(
destinationGroupArray,
displayFilters.order_by ?? "-created_at"
),
}; };
}, },
false false
@ -267,13 +266,13 @@ export const IssuesView: React.FC<Props> = ({
} }
}, },
[ [
displayFilters.group_by,
displayFilters.order_by,
workspaceSlug, workspaceSlug,
cycleId, cycleId,
moduleId, moduleId,
groupedByIssues, groupedByIssues,
projectId, projectId,
selectedGroup,
orderBy,
handleDeleteIssue, handleDeleteIssue,
params, params,
states, states,
@ -287,19 +286,19 @@ export const IssuesView: React.FC<Props> = ({
let preloadedValue: string | string[] = groupTitle; let preloadedValue: string | string[] = groupTitle;
if (selectedGroup === "labels") { if (displayFilters.group_by === "labels") {
if (groupTitle === "None") preloadedValue = []; if (groupTitle === "None") preloadedValue = [];
else preloadedValue = [groupTitle]; else preloadedValue = [groupTitle];
} }
if (selectedGroup) if (displayFilters.group_by)
setPreloadedData({ setPreloadedData({
[selectedGroup]: preloadedValue, [displayFilters.group_by]: preloadedValue,
actionType: "createIssue", actionType: "createIssue",
}); });
else setPreloadedData({ actionType: "createIssue" }); else setPreloadedData({ actionType: "createIssue" });
}, },
[setCreateIssueModal, setPreloadedData, selectedGroup] [displayFilters.group_by, setCreateIssueModal, setPreloadedData]
); );
const addIssueToDate = useCallback( const addIssueToDate = useCallback(
@ -352,7 +351,7 @@ export const IssuesView: React.FC<Props> = ({
CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params), CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params),
(prevData: any) => { (prevData: any) => {
if (!prevData) return prevData; if (!prevData) return prevData;
if (selectedGroup) { if (displayFilters.group_by) {
const filteredData: any = {}; const filteredData: any = {};
for (const key in prevData) { for (const key in prevData) {
filteredData[key] = prevData[key].filter((item: any) => item.id !== issueId); filteredData[key] = prevData[key].filter((item: any) => item.id !== issueId);
@ -384,7 +383,7 @@ export const IssuesView: React.FC<Props> = ({
console.log(e); console.log(e);
}); });
}, },
[workspaceSlug, projectId, cycleId, params, selectedGroup, setToastAlert] [displayFilters.group_by, workspaceSlug, projectId, cycleId, params, setToastAlert]
); );
const removeIssueFromModule = useCallback( const removeIssueFromModule = useCallback(
@ -395,7 +394,7 @@ export const IssuesView: React.FC<Props> = ({
MODULE_ISSUES_WITH_PARAMS(moduleId as string, params), MODULE_ISSUES_WITH_PARAMS(moduleId as string, params),
(prevData: any) => { (prevData: any) => {
if (!prevData) return prevData; if (!prevData) return prevData;
if (selectedGroup) { if (displayFilters.group_by) {
const filteredData: any = {}; const filteredData: any = {};
for (const key in prevData) { for (const key in prevData) {
filteredData[key] = prevData[key].filter((item: any) => item.id !== issueId); filteredData[key] = prevData[key].filter((item: any) => item.id !== issueId);
@ -427,7 +426,7 @@ export const IssuesView: React.FC<Props> = ({
console.log(e); console.log(e);
}); });
}, },
[workspaceSlug, projectId, moduleId, params, selectedGroup, setToastAlert] [displayFilters.group_by, workspaceSlug, projectId, moduleId, params, setToastAlert]
); );
const nullFilters = Object.keys(filters).filter( const nullFilters = Object.keys(filters).filter(
@ -481,7 +480,6 @@ export const IssuesView: React.FC<Props> = ({
state: null, state: null,
start_date: null, start_date: null,
target_date: null, target_date: null,
type: null,
}) })
} }
/> />
@ -513,10 +511,10 @@ export const IssuesView: React.FC<Props> = ({
addIssueToGroup={addIssueToGroup} addIssueToGroup={addIssueToGroup}
disableUserActions={disableUserActions} disableUserActions={disableUserActions}
dragDisabled={ dragDisabled={
selectedGroup === "created_by" || displayFilters.group_by === "created_by" ||
selectedGroup === "labels" || displayFilters.group_by === "labels" ||
selectedGroup === "state_detail.group" || displayFilters.group_by === "state_detail.group" ||
selectedGroup === "assignees" displayFilters.group_by === "assignees"
} }
emptyState={{ emptyState={{
title: cycleId title: cycleId
@ -554,15 +552,12 @@ export const IssuesView: React.FC<Props> = ({
trashBox={trashBox} trashBox={trashBox}
setTrashBox={setTrashBox} setTrashBox={setTrashBox}
viewProps={{ viewProps={{
groupByProperty: selectedGroup,
groupedIssues: groupedByIssues, groupedIssues: groupedByIssues,
displayFilters,
isEmpty, isEmpty,
issueView,
mutateIssues, mutateIssues,
orderBy,
params, params,
properties, properties,
showEmptyGroups,
}} }}
/> />
</> </>

View File

@ -29,7 +29,7 @@ export const AllLists: React.FC<Props> = ({
userAuth, userAuth,
viewProps, viewProps,
}) => { }) => {
const { groupByProperty: selectedGroup, groupedIssues, showEmptyGroups } = viewProps; const { displayFilters, groupedIssues } = viewProps;
return ( return (
<> <>
@ -37,9 +37,12 @@ export const AllLists: React.FC<Props> = ({
<div className="h-full overflow-y-auto"> <div className="h-full overflow-y-auto">
{Object.keys(groupedIssues).map((singleGroup) => { {Object.keys(groupedIssues).map((singleGroup) => {
const currentState = const currentState =
selectedGroup === "state" ? states?.find((s) => s.id === singleGroup) : null; displayFilters?.group_by === "state"
? states?.find((s) => s.id === singleGroup)
: null;
if (!showEmptyGroups && groupedIssues[singleGroup].length === 0) return null; if (!displayFilters?.show_empty_groups && groupedIssues[singleGroup].length === 0)
return null;
return ( return (
<SingleList <SingleList

View File

@ -91,7 +91,7 @@ export const SingleListIssue: React.FC<Props> = ({
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const { groupByProperty: selectedGroup, orderBy, properties, mutateIssues } = viewProps; const { displayFilters, properties, mutateIssues } = viewProps;
const partialUpdateIssue = useCallback( const partialUpdateIssue = useCallback(
(formData: Partial<IIssue>, issue: IIssue) => { (formData: Partial<IIssue>, issue: IIssue) => {
@ -124,9 +124,9 @@ export const SingleListIssue: React.FC<Props> = ({
handleIssuesMutation( handleIssuesMutation(
formData, formData,
groupTitle ?? "", groupTitle ?? "",
selectedGroup, displayFilters?.group_by ?? null,
index, index,
orderBy, displayFilters?.order_by ?? "-created_at",
prevData prevData
), ),
false false
@ -148,15 +148,14 @@ export const SingleListIssue: React.FC<Props> = ({
}); });
}, },
[ [
displayFilters,
workspaceSlug, workspaceSlug,
cycleId, cycleId,
moduleId, moduleId,
userId, userId,
groupTitle, groupTitle,
index, index,
selectedGroup,
mutateIssues, mutateIssues,
orderBy,
user, user,
] ]
); );

View File

@ -69,7 +69,7 @@ export const SingleList: React.FC<Props> = ({
const type = cycleId ? "cycle" : moduleId ? "module" : "issue"; const type = cycleId ? "cycle" : moduleId ? "module" : "issue";
const { groupByProperty: selectedGroup, groupedIssues } = viewProps; const { displayFilters, groupedIssues } = viewProps;
const { data: issueLabels } = useSWR<IIssueLabels[]>( const { data: issueLabels } = useSWR<IIssueLabels[]>(
workspaceSlug && projectId ? PROJECT_ISSUE_LABELS(projectId as string) : null, workspaceSlug && projectId ? PROJECT_ISSUE_LABELS(projectId as string) : null,
@ -90,7 +90,7 @@ export const SingleList: React.FC<Props> = ({
const getGroupTitle = () => { const getGroupTitle = () => {
let title = addSpaceIfCamelCase(groupTitle); let title = addSpaceIfCamelCase(groupTitle);
switch (selectedGroup) { switch (displayFilters?.group_by) {
case "state": case "state":
title = addSpaceIfCamelCase(currentState?.name ?? ""); title = addSpaceIfCamelCase(currentState?.name ?? "");
break; break;
@ -113,7 +113,7 @@ export const SingleList: React.FC<Props> = ({
const getGroupIcon = () => { const getGroupIcon = () => {
let icon; let icon;
switch (selectedGroup) { switch (displayFilters?.group_by) {
case "state": case "state":
icon = currentState && ( icon = currentState && (
<StateGroupIcon <StateGroupIcon
@ -177,13 +177,13 @@ export const SingleList: React.FC<Props> = ({
<div className="flex items-center justify-between px-4 py-2.5 bg-custom-background-90"> <div className="flex items-center justify-between px-4 py-2.5 bg-custom-background-90">
<Disclosure.Button> <Disclosure.Button>
<div className="flex items-center gap-x-3"> <div className="flex items-center gap-x-3">
{selectedGroup !== null && ( {displayFilters?.group_by !== null && (
<div className="flex items-center">{getGroupIcon()}</div> <div className="flex items-center">{getGroupIcon()}</div>
)} )}
{selectedGroup !== null ? ( {displayFilters?.group_by !== null ? (
<h2 <h2
className={`text-sm font-semibold leading-6 text-custom-text-100 ${ className={`text-sm font-semibold leading-6 text-custom-text-100 ${
selectedGroup === "created_by" ? "" : "capitalize" displayFilters?.group_by === "created_by" ? "" : "capitalize"
}`} }`}
> >
{getGroupTitle()} {getGroupTitle()}

View File

@ -22,10 +22,10 @@ export const SpreadsheetColumns: React.FC<Props> = ({ columnData, gridTemplateCo
const { storedValue: activeSortingProperty, setValue: setActiveSortingProperty } = const { storedValue: activeSortingProperty, setValue: setActiveSortingProperty } =
useLocalStorage("spreadsheetViewActiveSortingProperty", ""); useLocalStorage("spreadsheetViewActiveSortingProperty", "");
const { orderBy, setOrderBy } = useSpreadsheetIssuesView(); const { displayFilters, setDisplayFilters } = useSpreadsheetIssuesView();
const handleOrderBy = (order: TIssueOrderByOptions, itemKey: string) => { const handleOrderBy = (order: TIssueOrderByOptions, itemKey: string) => {
setOrderBy(order); setDisplayFilters({ order_by: order });
setSelectedMenuItem(`${order}_${itemKey}`); setSelectedMenuItem(`${order}_${itemKey}`);
setActiveSortingProperty(order === "-created_at" ? "" : itemKey); setActiveSortingProperty(order === "-created_at" ? "" : itemKey);
}; };
@ -239,7 +239,7 @@ export const SpreadsheetColumns: React.FC<Props> = ({ columnData, gridTemplateCo
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
{selectedMenuItem && {selectedMenuItem &&
selectedMenuItem !== "" && selectedMenuItem !== "" &&
orderBy !== "-created_at" && displayFilters?.order_by !== "-created_at" &&
selectedMenuItem.includes(col.propertyName) && ( selectedMenuItem.includes(col.propertyName) && (
<CustomMenu.MenuItem <CustomMenu.MenuItem
className={`mt-0.5${ className={`mt-0.5${

View File

@ -16,7 +16,7 @@ export const CycleIssuesGanttChartView = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query; const { workspaceSlug, projectId, cycleId } = router.query;
const { orderBy } = useIssuesView(); const { displayFilters } = useIssuesView();
const { user } = useUser(); const { user } = useUser();
const { projectDetails } = useProjectDetails(); const { projectDetails } = useProjectDetails();
@ -44,7 +44,7 @@ export const CycleIssuesGanttChartView = () => {
enableBlockLeftResize={isAllowed} enableBlockLeftResize={isAllowed}
enableBlockRightResize={isAllowed} enableBlockRightResize={isAllowed}
enableBlockMove={isAllowed} enableBlockMove={isAllowed}
enableReorder={orderBy === "sort_order" && isAllowed} enableReorder={displayFilters.order_by === "sort_order" && isAllowed}
bottomSpacing bottomSpacing
/> />
</div> </div>

View File

@ -50,7 +50,7 @@ export const DeleteIssueModal: React.FC<Props> = ({
const { workspaceSlug, projectId, cycleId, moduleId, viewId, issueId } = router.query; const { workspaceSlug, projectId, cycleId, moduleId, viewId, issueId } = router.query;
const isArchivedIssues = router.pathname.includes("archived-issues"); const isArchivedIssues = router.pathname.includes("archived-issues");
const { issueView, params } = useIssuesView(); const { displayFilters, params } = useIssuesView();
const { params: calendarParams } = useCalendarIssuesView(); const { params: calendarParams } = useCalendarIssuesView();
const { params: spreadsheetParams } = useSpreadsheetIssuesView(); const { params: spreadsheetParams } = useSpreadsheetIssuesView();
@ -73,7 +73,7 @@ export const DeleteIssueModal: React.FC<Props> = ({
await issueServices await issueServices
.deleteIssue(workspaceSlug as string, data.project, data.id, user) .deleteIssue(workspaceSlug as string, data.project, data.id, user)
.then(() => { .then(() => {
if (issueView === "calendar") { if (displayFilters.layout === "calendar") {
const calendarFetchKey = cycleId const calendarFetchKey = cycleId
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), calendarParams) ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), calendarParams)
: moduleId : moduleId
@ -87,7 +87,7 @@ export const DeleteIssueModal: React.FC<Props> = ({
(prevData) => (prevData ?? []).filter((p) => p.id !== data.id), (prevData) => (prevData ?? []).filter((p) => p.id !== data.id),
false false
); );
} else if (issueView === "spreadsheet") { } else if (displayFilters.layout === "spreadsheet") {
const spreadsheetFetchKey = cycleId const spreadsheetFetchKey = cycleId
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), spreadsheetParams) ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), spreadsheetParams)
: moduleId : moduleId

View File

@ -16,7 +16,7 @@ export const IssueGanttChartView = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
const { orderBy } = useIssuesView(); const { displayFilters } = useIssuesView();
const { user } = useUser(); const { user } = useUser();
const { projectDetails } = useProjectDetails(); const { projectDetails } = useProjectDetails();
@ -43,7 +43,7 @@ export const IssueGanttChartView = () => {
enableBlockLeftResize={isAllowed} enableBlockLeftResize={isAllowed}
enableBlockRightResize={isAllowed} enableBlockRightResize={isAllowed}
enableBlockMove={isAllowed} enableBlockMove={isAllowed}
enableReorder={orderBy === "sort_order" && isAllowed} enableReorder={displayFilters.order_by === "sort_order" && isAllowed}
bottomSpacing bottomSpacing
/> />
</div> </div>

View File

@ -78,7 +78,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId, viewId, inboxId } = router.query; const { workspaceSlug, projectId, cycleId, moduleId, viewId, inboxId } = router.query;
const { issueView, params } = useIssuesView(); const { displayFilters, params } = useIssuesView();
const { params: calendarParams } = useCalendarIssuesView(); const { params: calendarParams } = useCalendarIssuesView();
const { order_by, group_by, ...viewGanttParams } = params; const { order_by, group_by, ...viewGanttParams } = params;
const { params: inboxParams } = useInboxView(); const { params: inboxParams } = useInboxView();
@ -247,13 +247,13 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
if (payload.module && payload.module !== "") if (payload.module && payload.module !== "")
await addIssueToModule(res.id, payload.module); await addIssueToModule(res.id, payload.module);
if (issueView === "calendar") mutate(calendarFetchKey); if (displayFilters.layout === "calendar") mutate(calendarFetchKey);
if (issueView === "gantt_chart") if (displayFilters.layout === "gantt_chart")
mutate(ganttFetchKey, { mutate(ganttFetchKey, {
start_target_date: true, start_target_date: true,
order_by: "sort_order", order_by: "sort_order",
}); });
if (issueView === "spreadsheet") mutate(spreadsheetFetchKey); if (displayFilters.layout === "spreadsheet") mutate(spreadsheetFetchKey);
if (groupedIssues) mutateMyIssues(); if (groupedIssues) mutateMyIssues();
setToastAlert({ setToastAlert({
@ -285,8 +285,8 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
if (isUpdatingSingleIssue) { if (isUpdatingSingleIssue) {
mutate<IIssue>(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false); mutate<IIssue>(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false);
} else { } else {
if (issueView === "calendar") mutate(calendarFetchKey); if (displayFilters.layout === "calendar") mutate(calendarFetchKey);
if (issueView === "spreadsheet") mutate(spreadsheetFetchKey); if (displayFilters.layout === "spreadsheet") mutate(spreadsheetFetchKey);
if (payload.parent) mutate(SUB_ISSUES(payload.parent.toString())); if (payload.parent) mutate(SUB_ISSUES(payload.parent.toString()));
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params)); mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
} }

View File

@ -37,20 +37,8 @@ export const MyIssuesViewOptions: React.FC = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
const { const { displayFilters, setDisplayFilters, properties, setProperty, filters, setFilters } =
issueView, useMyIssuesFilters(workspaceSlug?.toString());
setIssueView,
groupBy,
setGroupBy,
orderBy,
setOrderBy,
showEmptyGroups,
setShowEmptyGroups,
properties,
setProperty,
filters,
setFilters,
} = useMyIssuesFilters(workspaceSlug?.toString());
const { isEstimateActive } = useEstimateOption(); const { isEstimateActive } = useEstimateOption();
@ -68,11 +56,11 @@ export const MyIssuesViewOptions: React.FC = () => {
<button <button
type="button" type="button"
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80 duration-300 ${ className={`grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80 duration-300 ${
issueView === option.type displayFilters?.layout === option.type
? "bg-custom-sidebar-background-80" ? "bg-custom-sidebar-background-80"
: "text-custom-sidebar-text-200" : "text-custom-sidebar-text-200"
}`} }`}
onClick={() => setIssueView(option.type)} onClick={() => setDisplayFilters({ layout: option.type })}
> >
<option.Icon <option.Icon
sx={{ sx={{
@ -139,23 +127,26 @@ export const MyIssuesViewOptions: React.FC = () => {
<Popover.Panel className="absolute right-0 z-30 mt-1 w-screen max-w-xs transform rounded-lg border border-custom-border-200 bg-custom-background-90 p-3 shadow-lg"> <Popover.Panel className="absolute right-0 z-30 mt-1 w-screen max-w-xs transform rounded-lg border border-custom-border-200 bg-custom-background-90 p-3 shadow-lg">
<div className="relative divide-y-2 divide-custom-border-200"> <div className="relative divide-y-2 divide-custom-border-200">
<div className="space-y-4 pb-3 text-xs"> <div className="space-y-4 pb-3 text-xs">
{issueView !== "calendar" && issueView !== "spreadsheet" && ( {displayFilters?.layout !== "calendar" &&
displayFilters?.layout !== "spreadsheet" && (
<> <>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-custom-text-200">Group by</h4> <h4 className="text-custom-text-200">Group by</h4>
<div className="w-28"> <div className="w-28">
<CustomMenu <CustomMenu
label={ label={
groupBy === "project" displayFilters?.group_by === "project"
? "Project" ? "Project"
: GROUP_BY_OPTIONS.find((option) => option.key === groupBy) : GROUP_BY_OPTIONS.find(
?.name ?? "Select" (option) => option.key === displayFilters?.group_by
)?.name ?? "Select"
} }
className="!w-full" className="!w-full"
buttonClassName="w-full" buttonClassName="w-full"
> >
{GROUP_BY_OPTIONS.map((option) => { {GROUP_BY_OPTIONS.map((option) => {
if (issueView === "kanban" && option.key === null) return null; if (displayFilters?.layout === "kanban" && option.key === null)
return null;
if ( if (
option.key === "state" || option.key === "state" ||
option.key === "created_by" || option.key === "created_by" ||
@ -166,7 +157,7 @@ export const MyIssuesViewOptions: React.FC = () => {
return ( return (
<CustomMenu.MenuItem <CustomMenu.MenuItem
key={option.key} key={option.key}
onClick={() => setGroupBy(option.key)} onClick={() => setDisplayFilters({ group_by: option.key })}
> >
{option.name} {option.name}
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
@ -180,14 +171,18 @@ export const MyIssuesViewOptions: React.FC = () => {
<div className="w-28"> <div className="w-28">
<CustomMenu <CustomMenu
label={ label={
ORDER_BY_OPTIONS.find((option) => option.key === orderBy)?.name ?? ORDER_BY_OPTIONS.find(
"Select" (option) => option.key === displayFilters?.order_by
)?.name ?? "Select"
} }
className="!w-full" className="!w-full"
buttonClassName="w-full" buttonClassName="w-full"
> >
{ORDER_BY_OPTIONS.map((option) => { {ORDER_BY_OPTIONS.map((option) => {
if (groupBy === "priority" && option.key === "priority") if (
displayFilters?.group_by === "priority" &&
option.key === "priority"
)
return null; return null;
if (option.key === "sort_order") return null; if (option.key === "sort_order") return null;
@ -195,7 +190,7 @@ export const MyIssuesViewOptions: React.FC = () => {
<CustomMenu.MenuItem <CustomMenu.MenuItem
key={option.key} key={option.key}
onClick={() => { onClick={() => {
setOrderBy(option.key); setDisplayFilters({ order_by: option.key });
}} }}
> >
{option.name} {option.name}
@ -212,8 +207,9 @@ export const MyIssuesViewOptions: React.FC = () => {
<div className="w-28"> <div className="w-28">
<CustomMenu <CustomMenu
label={ label={
FILTER_ISSUE_OPTIONS.find((option) => option.key === filters?.type) FILTER_ISSUE_OPTIONS.find(
?.name ?? "Select" (option) => option.key === displayFilters?.type
)?.name ?? "Select"
} }
className="!w-full" className="!w-full"
buttonClassName="w-full" buttonClassName="w-full"
@ -222,7 +218,7 @@ export const MyIssuesViewOptions: React.FC = () => {
<CustomMenu.MenuItem <CustomMenu.MenuItem
key={option.key} key={option.key}
onClick={() => onClick={() =>
setFilters({ setDisplayFilters({
type: option.key, type: option.key,
}) })
} }
@ -234,12 +230,20 @@ export const MyIssuesViewOptions: React.FC = () => {
</div> </div>
</div> </div>
{issueView !== "calendar" && issueView !== "spreadsheet" && ( {displayFilters?.layout !== "calendar" &&
displayFilters?.layout !== "spreadsheet" && (
<> <>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-custom-text-200">Show empty states</h4> <h4 className="text-custom-text-200">Show empty states</h4>
<div className="w-28"> <div className="w-28">
<ToggleSwitch value={showEmptyGroups} onChange={setShowEmptyGroups} /> <ToggleSwitch
value={displayFilters?.show_empty_groups ?? true}
onChange={() =>
setDisplayFilters({
show_empty_groups: !displayFilters?.show_empty_groups,
})
}
/>
</div> </div>
</div> </div>
</> </>
@ -253,7 +257,7 @@ export const MyIssuesViewOptions: React.FC = () => {
if (key === "estimate" && !isEstimateActive) return null; if (key === "estimate" && !isEstimateActive) return null;
if ( if (
issueView === "spreadsheet" && displayFilters?.layout === "spreadsheet" &&
(key === "attachment_count" || (key === "attachment_count" ||
key === "link" || key === "link" ||
key === "sub_issue_count") key === "sub_issue_count")
@ -261,7 +265,7 @@ export const MyIssuesViewOptions: React.FC = () => {
return null; return null;
if ( if (
issueView !== "spreadsheet" && displayFilters?.layout !== "spreadsheet" &&
(key === "created_on" || key === "updated_on") (key === "created_on" || key === "updated_on")
) )
return null; return null;

View File

@ -57,8 +57,9 @@ export const MyIssuesView: React.FC<Props> = ({
const { user } = useUserAuth(); const { user } = useUserAuth();
const { groupedIssues, mutateMyIssues, isEmpty, params } = useMyIssues(workspaceSlug?.toString()); const { groupedIssues, mutateMyIssues, isEmpty, params } = useMyIssues(workspaceSlug?.toString());
const { filters, setFilters, issueView, groupBy, orderBy, properties, showEmptyGroups } = const { filters, setFilters, displayFilters, setDisplayFilters, properties } = useMyIssuesFilters(
useMyIssuesFilters(workspaceSlug?.toString()); workspaceSlug?.toString()
);
const { data: labels } = useSWR( const { data: labels } = useSWR(
workspaceSlug && (filters?.labels ?? []).length > 0 workspaceSlug && (filters?.labels ?? []).length > 0
@ -81,7 +82,13 @@ export const MyIssuesView: React.FC<Props> = ({
async (result: DropResult) => { async (result: DropResult) => {
setTrashBox(false); setTrashBox(false);
if (!result.destination || !workspaceSlug || !groupedIssues || groupBy !== "priority") return; if (
!result.destination ||
!workspaceSlug ||
!groupedIssues ||
displayFilters?.group_by !== "priority"
)
return;
const { source, destination } = result; const { source, destination } = result;
@ -96,7 +103,7 @@ export const MyIssuesView: React.FC<Props> = ({
const sourceGroup = source.droppableId; const sourceGroup = source.droppableId;
const destinationGroup = destination.droppableId; const destinationGroup = destination.droppableId;
draggedItem[groupBy] = destinationGroup as TIssuePriorities; draggedItem[displayFilters.group_by] = destinationGroup as TIssuePriorities;
mutate<{ mutate<{
[key: string]: IIssue[]; [key: string]: IIssue[];
@ -113,8 +120,14 @@ export const MyIssuesView: React.FC<Props> = ({
return { return {
...prevData, ...prevData,
[sourceGroup]: orderArrayBy(sourceGroupArray, orderBy), [sourceGroup]: orderArrayBy(
[destinationGroup]: orderArrayBy(destinationGroupArray, orderBy), sourceGroupArray,
displayFilters.order_by ?? "-created_at"
),
[destinationGroup]: orderArrayBy(
destinationGroupArray,
displayFilters.order_by ?? "-created_at"
),
}; };
}, },
false false
@ -134,7 +147,7 @@ export const MyIssuesView: React.FC<Props> = ({
.catch(() => mutate(USER_ISSUES(workspaceSlug.toString(), params))); .catch(() => mutate(USER_ISSUES(workspaceSlug.toString(), params)));
} }
}, },
[groupBy, groupedIssues, handleDeleteIssue, orderBy, params, user, workspaceSlug] [displayFilters, groupedIssues, handleDeleteIssue, params, user, workspaceSlug]
); );
const addIssueToGroup = useCallback( const addIssueToGroup = useCallback(
@ -143,19 +156,19 @@ export const MyIssuesView: React.FC<Props> = ({
let preloadedValue: string | string[] = groupTitle; let preloadedValue: string | string[] = groupTitle;
if (groupBy === "labels") { if (displayFilters?.group_by === "labels") {
if (groupTitle === "None") preloadedValue = []; if (groupTitle === "None") preloadedValue = [];
else preloadedValue = [groupTitle]; else preloadedValue = [groupTitle];
} }
if (groupBy) if (displayFilters?.group_by)
setPreloadedData({ setPreloadedData({
[groupBy]: preloadedValue, [displayFilters?.group_by]: preloadedValue,
actionType: "createIssue", actionType: "createIssue",
}); });
else setPreloadedData({ actionType: "createIssue" }); else setPreloadedData({ actionType: "createIssue" });
}, },
[setCreateIssueModal, setPreloadedData, groupBy] [setCreateIssueModal, setPreloadedData, displayFilters?.group_by]
); );
const addIssueToDate = useCallback( const addIssueToDate = useCallback(
@ -263,7 +276,6 @@ export const MyIssuesView: React.FC<Props> = ({
state_group: null, state_group: null,
start_date: null, start_date: null,
target_date: null, target_date: null,
type: null,
}) })
} }
/> />
@ -275,7 +287,7 @@ export const MyIssuesView: React.FC<Props> = ({
addIssueToDate={addIssueToDate} addIssueToDate={addIssueToDate}
addIssueToGroup={addIssueToGroup} addIssueToGroup={addIssueToGroup}
disableUserActions={disableUserActions} disableUserActions={disableUserActions}
dragDisabled={groupBy !== "priority"} dragDisabled={displayFilters?.group_by !== "priority"}
emptyState={{ emptyState={{
title: filters.assignees title: filters.assignees
? "You don't have any issue assigned to you yet" ? "You don't have any issue assigned to you yet"
@ -304,15 +316,12 @@ export const MyIssuesView: React.FC<Props> = ({
trashBox={trashBox} trashBox={trashBox}
setTrashBox={setTrashBox} setTrashBox={setTrashBox}
viewProps={{ viewProps={{
groupByProperty: groupBy, displayFilters,
groupedIssues, groupedIssues,
isEmpty, isEmpty,
issueView,
mutateIssues: mutateMyIssues, mutateIssues: mutateMyIssues,
orderBy,
params, params,
properties, properties,
showEmptyGroups,
}} }}
/> />
</> </>

View File

@ -34,7 +34,7 @@ export const ViewDueDateSelect: React.FC<Props> = ({
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
const { issueView } = useIssuesView(); const { displayFilters } = useIssuesView();
const minDate = issue.start_date ? new Date(issue.start_date) : null; const minDate = issue.start_date ? new Date(issue.start_date) : null;
minDate?.setDate(minDate.getDate()); minDate?.setDate(minDate.getDate());
@ -80,7 +80,9 @@ export const ViewDueDateSelect: React.FC<Props> = ({
); );
}} }}
className={`${issue?.target_date ? "w-[6.5rem]" : "w-[5rem] text-center"} ${ className={`${issue?.target_date ? "w-[6.5rem]" : "w-[5rem] text-center"} ${
issueView === "kanban" ? "bg-custom-background-90" : "bg-custom-background-100" displayFilters.layout === "kanban"
? "bg-custom-background-90"
: "bg-custom-background-100"
}`} }`}
minDate={minDate ?? undefined} minDate={minDate ?? undefined}
noBorder={noBorder} noBorder={noBorder}

View File

@ -34,7 +34,7 @@ export const ViewStartDateSelect: React.FC<Props> = ({
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
const { issueView } = useIssuesView(); const { displayFilters } = useIssuesView();
const maxDate = issue.target_date ? new Date(issue.target_date) : null; const maxDate = issue.target_date ? new Date(issue.target_date) : null;
maxDate?.setDate(maxDate.getDate()); maxDate?.setDate(maxDate.getDate());
@ -72,7 +72,9 @@ export const ViewStartDateSelect: React.FC<Props> = ({
); );
}} }}
className={`${issue?.start_date ? "w-[6.5rem]" : "w-[5rem] text-center"} ${ className={`${issue?.start_date ? "w-[6.5rem]" : "w-[5rem] text-center"} ${
issueView === "kanban" ? "bg-custom-background-90" : "bg-custom-background-100" displayFilters.layout === "kanban"
? "bg-custom-background-90"
: "bg-custom-background-100"
}`} }`}
maxDate={maxDate ?? undefined} maxDate={maxDate ?? undefined}
noBorder={noBorder} noBorder={noBorder}

View File

@ -20,7 +20,7 @@ export const ModuleIssuesGanttChartView: FC<Props> = ({}) => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query; const { workspaceSlug, projectId, moduleId } = router.query;
const { orderBy } = useIssuesView(); const { displayFilters } = useIssuesView();
const { user } = useUser(); const { user } = useUser();
const { projectDetails } = useProjectDetails(); const { projectDetails } = useProjectDetails();
@ -48,7 +48,7 @@ export const ModuleIssuesGanttChartView: FC<Props> = ({}) => {
enableBlockLeftResize={isAllowed} enableBlockLeftResize={isAllowed}
enableBlockRightResize={isAllowed} enableBlockRightResize={isAllowed}
enableBlockMove={isAllowed} enableBlockMove={isAllowed}
enableReorder={orderBy === "sort_order" && isAllowed} enableReorder={displayFilters.order_by === "sort_order" && isAllowed}
bottomSpacing bottomSpacing
/> />
</div> </div>

View File

@ -9,13 +9,16 @@ import useToast from "hooks/use-toast";
// services // services
import userService from "services/user.service"; import userService from "services/user.service";
// ui // ui
import { CustomSelect, Input, PrimaryButton } from "components/ui"; import { CustomSearchSelect, CustomSelect, Input, PrimaryButton } from "components/ui";
// types // types
import { ICurrentUserResponse, IUser } from "types"; import { ICurrentUserResponse, IUser } from "types";
// fetch-keys // fetch-keys
import { CURRENT_USER } from "constants/fetch-keys"; import { CURRENT_USER } from "constants/fetch-keys";
// helpers
import { getUserTimeZoneFromWindow } from "helpers/date-time.helper";
// constants // constants
import { USER_ROLES } from "constants/workspace"; import { USER_ROLES } from "constants/workspace";
import { TIME_ZONES } from "constants/timezones";
const defaultValues: Partial<IUser> = { const defaultValues: Partial<IUser> = {
first_name: "", first_name: "",
@ -27,6 +30,12 @@ type Props = {
user?: IUser; user?: IUser;
}; };
const timeZoneOptions = TIME_ZONES.map((timeZone) => ({
value: timeZone.value,
query: timeZone.label + " " + timeZone.value,
content: timeZone.label,
}));
export const UserDetails: React.FC<Props> = ({ user }) => { export const UserDetails: React.FC<Props> = ({ user }) => {
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
@ -84,6 +93,7 @@ export const UserDetails: React.FC<Props> = ({ user }) => {
first_name: user.first_name, first_name: user.first_name,
last_name: user.last_name, last_name: user.last_name,
role: user.role, role: user.role,
user_timezone: getUserTimeZoneFromWindow(),
}); });
} }
}, [user, reset]); }, [user, reset]);
@ -162,6 +172,34 @@ export const UserDetails: React.FC<Props> = ({ user }) => {
{errors.role && <span className="text-sm text-red-500">{errors.role.message}</span>} {errors.role && <span className="text-sm text-red-500">{errors.role.message}</span>}
</div> </div>
</div> </div>
<div className="space-y-1 text-sm">
<span>What time zone are you in? </span>
<div className="w-full">
<Controller
name="user_timezone"
control={control}
rules={{ required: "This field is required" }}
render={({ field: { value, onChange } }) => (
<CustomSearchSelect
value={value}
label={
value
? TIME_ZONES.find((t) => t.value === value)?.label ?? value
: "Select a timezone"
}
options={timeZoneOptions}
onChange={onChange}
verticalPosition="top"
optionsClassName="w-full"
input
/>
)}
/>
{errors?.user_timezone && (
<span className="text-sm text-red-500">{errors.user_timezone.message}</span>
)}
</div>
</div>
</div> </div>
<PrimaryButton type="submit" size="md" disabled={!isValid} loading={isSubmitting}> <PrimaryButton type="submit" size="md" disabled={!isValid} loading={isSubmitting}>

View File

@ -41,16 +41,10 @@ export const ProfileIssuesViewOptions: React.FC = () => {
const { projects } = useProjects(); const { projects } = useProjects();
const { const {
issueView, displayFilters,
setIssueView, setDisplayFilters,
groupByProperty,
setGroupByProperty,
orderBy,
setOrderBy,
showEmptyGroups,
setShowEmptyGroups,
filters, filters,
properties, displayProperties,
setProperties, setProperties,
setFilters, setFilters,
} = useProfileIssues(workspaceSlug?.toString(), userId?.toString()); } = useProfileIssues(workspaceSlug?.toString(), userId?.toString());
@ -94,11 +88,11 @@ export const ProfileIssuesViewOptions: React.FC = () => {
<button <button
type="button" type="button"
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80 duration-300 ${ className={`grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80 duration-300 ${
issueView === option.type displayFilters?.layout === option.type
? "bg-custom-sidebar-background-80" ? "bg-custom-sidebar-background-80"
: "text-custom-sidebar-text-200" : "text-custom-sidebar-text-200"
}`} }`}
onClick={() => setIssueView(option.type)} onClick={() => setDisplayFilters({ layout: option.type })}
> >
<option.Icon <option.Icon
sx={{ sx={{
@ -165,24 +159,26 @@ export const ProfileIssuesViewOptions: React.FC = () => {
<Popover.Panel className="absolute right-0 z-30 mt-1 w-screen max-w-xs transform rounded-lg border border-custom-border-200 bg-custom-background-90 p-3 shadow-lg"> <Popover.Panel className="absolute right-0 z-30 mt-1 w-screen max-w-xs transform rounded-lg border border-custom-border-200 bg-custom-background-90 p-3 shadow-lg">
<div className="relative divide-y-2 divide-custom-border-200"> <div className="relative divide-y-2 divide-custom-border-200">
<div className="space-y-4 pb-3 text-xs"> <div className="space-y-4 pb-3 text-xs">
{issueView !== "calendar" && issueView !== "spreadsheet" && ( {displayFilters?.layout !== "calendar" &&
displayFilters?.layout !== "spreadsheet" && (
<> <>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-custom-text-200">Group by</h4> <h4 className="text-custom-text-200">Group by</h4>
<div className="w-28"> <div className="w-28">
<CustomMenu <CustomMenu
label={ label={
groupByProperty === "project" displayFilters?.group_by === "project"
? "Project" ? "Project"
: GROUP_BY_OPTIONS.find( : GROUP_BY_OPTIONS.find(
(option) => option.key === groupByProperty (option) => option.key === displayFilters?.group_by
)?.name ?? "Select" )?.name ?? "Select"
} }
className="!w-full" className="!w-full"
buttonClassName="w-full" buttonClassName="w-full"
> >
{GROUP_BY_OPTIONS.map((option) => { {GROUP_BY_OPTIONS.map((option) => {
if (issueView === "kanban" && option.key === null) return null; if (displayFilters?.layout === "kanban" && option.key === null)
return null;
if ( if (
option.key === "state" || option.key === "state" ||
option.key === "created_by" || option.key === "created_by" ||
@ -193,7 +189,7 @@ export const ProfileIssuesViewOptions: React.FC = () => {
return ( return (
<CustomMenu.MenuItem <CustomMenu.MenuItem
key={option.key} key={option.key}
onClick={() => setGroupByProperty(option.key)} onClick={() => setDisplayFilters({ group_by: option.key })}
> >
{option.name} {option.name}
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
@ -207,14 +203,18 @@ export const ProfileIssuesViewOptions: React.FC = () => {
<div className="w-28"> <div className="w-28">
<CustomMenu <CustomMenu
label={ label={
ORDER_BY_OPTIONS.find((option) => option.key === orderBy)?.name ?? ORDER_BY_OPTIONS.find(
"Select" (option) => option.key === displayFilters?.order_by
)?.name ?? "Select"
} }
className="!w-full" className="!w-full"
buttonClassName="w-full" buttonClassName="w-full"
> >
{ORDER_BY_OPTIONS.map((option) => { {ORDER_BY_OPTIONS.map((option) => {
if (groupByProperty === "priority" && option.key === "priority") if (
displayFilters?.group_by === "priority" &&
option.key === "priority"
)
return null; return null;
if (option.key === "sort_order") return null; if (option.key === "sort_order") return null;
@ -222,7 +222,7 @@ export const ProfileIssuesViewOptions: React.FC = () => {
<CustomMenu.MenuItem <CustomMenu.MenuItem
key={option.key} key={option.key}
onClick={() => { onClick={() => {
setOrderBy(option.key); setDisplayFilters({ order_by: option.key });
}} }}
> >
{option.name} {option.name}
@ -239,8 +239,9 @@ export const ProfileIssuesViewOptions: React.FC = () => {
<div className="w-28"> <div className="w-28">
<CustomMenu <CustomMenu
label={ label={
FILTER_ISSUE_OPTIONS.find((option) => option.key === filters?.type) FILTER_ISSUE_OPTIONS.find(
?.name ?? "Select" (option) => option.key === displayFilters?.type
)?.name ?? "Select"
} }
className="!w-full" className="!w-full"
buttonClassName="w-full" buttonClassName="w-full"
@ -249,7 +250,7 @@ export const ProfileIssuesViewOptions: React.FC = () => {
<CustomMenu.MenuItem <CustomMenu.MenuItem
key={option.key} key={option.key}
onClick={() => onClick={() =>
setFilters({ setDisplayFilters({
type: option.key, type: option.key,
}) })
} }
@ -261,12 +262,20 @@ export const ProfileIssuesViewOptions: React.FC = () => {
</div> </div>
</div> </div>
{issueView !== "calendar" && issueView !== "spreadsheet" && ( {displayFilters?.layout !== "calendar" &&
displayFilters?.layout !== "spreadsheet" && (
<> <>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h4 className="text-custom-text-200">Show empty states</h4> <h4 className="text-custom-text-200">Show empty states</h4>
<div className="w-28"> <div className="w-28">
<ToggleSwitch value={showEmptyGroups} onChange={setShowEmptyGroups} /> <ToggleSwitch
value={displayFilters?.show_empty_groups ?? true}
onChange={() =>
setDisplayFilters({
show_empty_groups: !displayFilters?.show_empty_groups,
})
}
/>
</div> </div>
</div> </div>
</> </>
@ -276,11 +285,11 @@ export const ProfileIssuesViewOptions: React.FC = () => {
<div className="space-y-2 py-3"> <div className="space-y-2 py-3">
<h4 className="text-sm text-custom-text-200">Display Properties</h4> <h4 className="text-sm text-custom-text-200">Display Properties</h4>
<div className="flex flex-wrap items-center gap-2 text-custom-text-200"> <div className="flex flex-wrap items-center gap-2 text-custom-text-200">
{Object.keys(properties).map((key) => { {Object.keys(displayProperties).map((key) => {
if (key === "estimate" && !isEstimateActive) return null; if (key === "estimate" && !isEstimateActive) return null;
if ( if (
issueView === "spreadsheet" && displayFilters?.layout === "spreadsheet" &&
(key === "attachment_count" || (key === "attachment_count" ||
key === "link" || key === "link" ||
key === "sub_issue_count") key === "sub_issue_count")
@ -288,7 +297,7 @@ export const ProfileIssuesViewOptions: React.FC = () => {
return null; return null;
if ( if (
issueView !== "spreadsheet" && displayFilters?.layout !== "spreadsheet" &&
(key === "created_on" || key === "updated_on") (key === "created_on" || key === "updated_on")
) )
return null; return null;
@ -298,7 +307,7 @@ export const ProfileIssuesViewOptions: React.FC = () => {
key={key} key={key}
type="button" type="button"
className={`rounded border px-2 py-1 text-xs capitalize ${ className={`rounded border px-2 py-1 text-xs capitalize ${
properties[key as keyof Properties] displayProperties[key as keyof Properties]
? "border-custom-primary bg-custom-primary text-white" ? "border-custom-primary bg-custom-primary text-white"
: "border-custom-border-200" : "border-custom-border-200"
}`} }`}

View File

@ -50,14 +50,12 @@ export const ProfileIssuesView = () => {
const { const {
groupedIssues, groupedIssues,
mutateProfileIssues, mutateProfileIssues,
issueView, displayFilters,
groupByProperty, setDisplayFilters,
orderBy,
isEmpty, isEmpty,
showEmptyGroups,
filters, filters,
setFilters, setFilters,
properties, displayProperties,
params, params,
} = useProfileIssues(workspaceSlug?.toString(), userId?.toString()); } = useProfileIssues(workspaceSlug?.toString(), userId?.toString());
@ -92,7 +90,12 @@ export const ProfileIssuesView = () => {
async (result: DropResult) => { async (result: DropResult) => {
setTrashBox(false); setTrashBox(false);
if (!result.destination || !workspaceSlug || !groupedIssues || groupByProperty !== "priority") if (
!result.destination ||
!workspaceSlug ||
!groupedIssues ||
displayFilters?.group_by !== "priority"
)
return; return;
const { source, destination } = result; const { source, destination } = result;
@ -108,7 +111,7 @@ export const ProfileIssuesView = () => {
const sourceGroup = source.droppableId; const sourceGroup = source.droppableId;
const destinationGroup = destination.droppableId; const destinationGroup = destination.droppableId;
draggedItem[groupByProperty] = destinationGroup as TIssuePriorities; draggedItem[displayFilters.group_by] = destinationGroup as TIssuePriorities;
mutateProfileIssues((prevData: any) => { mutateProfileIssues((prevData: any) => {
if (!prevData) return prevData; if (!prevData) return prevData;
@ -121,8 +124,11 @@ export const ProfileIssuesView = () => {
return { return {
...prevData, ...prevData,
[sourceGroup]: orderArrayBy(sourceGroupArray, orderBy), [sourceGroup]: orderArrayBy(sourceGroupArray, displayFilters.order_by ?? "-created_at"),
[destinationGroup]: orderArrayBy(destinationGroupArray, orderBy), [destinationGroup]: orderArrayBy(
destinationGroupArray,
displayFilters.order_by ?? "-created_at"
),
}; };
}, false); }, false);
@ -140,15 +146,7 @@ export const ProfileIssuesView = () => {
.catch(() => mutateProfileIssues()); .catch(() => mutateProfileIssues());
} }
}, },
[ [displayFilters, groupedIssues, handleDeleteIssue, mutateProfileIssues, user, workspaceSlug]
groupByProperty,
groupedIssues,
handleDeleteIssue,
mutateProfileIssues,
orderBy,
user,
workspaceSlug,
]
); );
const addIssueToGroup = useCallback( const addIssueToGroup = useCallback(
@ -157,19 +155,19 @@ export const ProfileIssuesView = () => {
let preloadedValue: string | string[] = groupTitle; let preloadedValue: string | string[] = groupTitle;
if (groupByProperty === "labels") { if (displayFilters?.group_by === "labels") {
if (groupTitle === "None") preloadedValue = []; if (groupTitle === "None") preloadedValue = [];
else preloadedValue = [groupTitle]; else preloadedValue = [groupTitle];
} }
if (groupByProperty) if (displayFilters?.group_by)
setPreloadedData({ setPreloadedData({
[groupByProperty]: preloadedValue, [displayFilters?.group_by]: preloadedValue,
actionType: "createIssue", actionType: "createIssue",
}); });
else setPreloadedData({ actionType: "createIssue" }); else setPreloadedData({ actionType: "createIssue" });
}, },
[setCreateIssueModal, setPreloadedData, groupByProperty] [setCreateIssueModal, setPreloadedData, displayFilters?.group_by]
); );
const addIssueToDate = useCallback( const addIssueToDate = useCallback(
@ -277,7 +275,6 @@ export const ProfileIssuesView = () => {
state_group: null, state_group: null,
start_date: null, start_date: null,
target_date: null, target_date: null,
type: null,
}) })
} }
/> />
@ -289,7 +286,7 @@ export const ProfileIssuesView = () => {
addIssueToDate={addIssueToDate} addIssueToDate={addIssueToDate}
addIssueToGroup={addIssueToGroup} addIssueToGroup={addIssueToGroup}
disableUserActions={false} disableUserActions={false}
dragDisabled={groupByProperty !== "priority"} dragDisabled={displayFilters?.group_by !== "priority"}
emptyState={{ emptyState={{
title: router.pathname.includes("assigned") title: router.pathname.includes("assigned")
? `Issues assigned to ${profileData?.user_data.display_name} will appear here` ? `Issues assigned to ${profileData?.user_data.display_name} will appear here`
@ -305,15 +302,12 @@ export const ProfileIssuesView = () => {
trashBox={trashBox} trashBox={trashBox}
setTrashBox={setTrashBox} setTrashBox={setTrashBox}
viewProps={{ viewProps={{
groupByProperty,
groupedIssues, groupedIssues,
displayFilters,
isEmpty, isEmpty,
issueView,
mutateIssues: mutateProfileIssues, mutateIssues: mutateProfileIssues,
orderBy,
params, params,
properties, properties: displayProperties,
showEmptyGroups,
}} }}
/> />
</> </>

View File

@ -11,14 +11,15 @@ import projectService from "services/project.service";
import cyclesService from "services/cycles.service"; import cyclesService from "services/cycles.service";
import modulesService from "services/modules.service"; import modulesService from "services/modules.service";
import viewsService from "services/views.service"; import viewsService from "services/views.service";
// hooks
import useUserAuth from "hooks/use-user-auth";
// types // types
import { import {
IIssueFilterOptions, IIssueFilterOptions,
TIssueViewOptions,
IProjectMember, IProjectMember,
TIssueGroupByOptions,
TIssueOrderByOptions,
ICurrentUserResponse, ICurrentUserResponse,
IIssueDisplayFilterOptions,
IProjectViewProps,
} from "types"; } from "types";
// fetch-keys // fetch-keys
import { import {
@ -27,66 +28,34 @@ import {
USER_PROJECT_VIEW, USER_PROJECT_VIEW,
VIEW_DETAILS, VIEW_DETAILS,
} from "constants/fetch-keys"; } from "constants/fetch-keys";
import useUserAuth from "hooks/use-user-auth";
export const issueViewContext = createContext<ContextType>({} as ContextType); export const issueViewContext = createContext<ContextType>({} as ContextType);
type IssueViewProps = {
issueView: TIssueViewOptions;
groupByProperty: TIssueGroupByOptions;
orderBy: TIssueOrderByOptions;
showEmptyGroups: boolean;
showSubIssues: boolean;
calendarDateRange: string;
filters: IIssueFilterOptions;
};
type ReducerActionType = { type ReducerActionType = {
type: type: "REHYDRATE_THEME" | "SET_DISPLAY_FILTERS" | "SET_FILTERS" | "RESET_TO_DEFAULT";
| "REHYDRATE_THEME" payload?: Partial<IProjectViewProps>;
| "SET_ISSUE_VIEW"
| "SET_ORDER_BY_PROPERTY"
| "SET_SHOW_EMPTY_STATES"
| "SET_CALENDAR_DATE_RANGE"
| "SET_FILTERS"
| "SET_GROUP_BY_PROPERTY"
| "RESET_TO_DEFAULT"
| "SET_SHOW_SUB_ISSUES";
payload?: Partial<IssueViewProps>;
}; };
type ContextType = IssueViewProps & { type ContextType = IProjectViewProps & {
setGroupByProperty: (property: TIssueGroupByOptions) => void; setDisplayFilters: (displayFilter: Partial<IIssueDisplayFilterOptions>) => void;
setOrderBy: (property: TIssueOrderByOptions) => void;
setShowEmptyGroups: (property: boolean) => void;
setShowSubIssues: (value: boolean) => void;
setCalendarDateRange: (property: string) => void;
setFilters: (filters: Partial<IIssueFilterOptions>, saveToServer?: boolean) => void; setFilters: (filters: Partial<IIssueFilterOptions>, saveToServer?: boolean) => void;
resetFilterToDefault: () => void; resetFilterToDefault: () => void;
setNewFilterDefaultView: () => void; setNewFilterDefaultView: () => void;
setIssueView: (property: TIssueViewOptions) => void;
}; };
type StateType = { type StateType = IProjectViewProps;
issueView: TIssueViewOptions;
groupByProperty: TIssueGroupByOptions;
orderBy: TIssueOrderByOptions;
showEmptyGroups: boolean;
showSubIssues: boolean;
calendarDateRange: string;
filters: IIssueFilterOptions;
};
type ReducerFunctionType = (state: StateType, action: ReducerActionType) => StateType; type ReducerFunctionType = (state: StateType, action: ReducerActionType) => StateType;
export const initialState: StateType = { export const initialState: StateType = {
issueView: "list", display_filters: {
groupByProperty: null, calendar_date_range: "",
orderBy: "-created_at", group_by: null,
showEmptyGroups: true, layout: "list",
showSubIssues: true, order_by: "-created_at",
calendarDateRange: "", show_empty_groups: true,
sub_issue: true,
},
filters: { filters: {
type: null,
priority: null, priority: null,
assignees: null, assignees: null,
labels: null, labels: null,
@ -110,70 +79,14 @@ export const reducer: ReducerFunctionType = (state, action) => {
return { ...initialState, ...payload, collapsed }; return { ...initialState, ...payload, collapsed };
} }
case "SET_ISSUE_VIEW": { case "SET_DISPLAY_FILTERS": {
const newState = { const newState = {
...state, ...state,
issueView: payload?.issueView || "list", display_filters: {
}; ...state.display_filters,
...payload,
return { },
...state, issueView: payload?.display_filters?.layout || "list",
...newState,
};
}
case "SET_GROUP_BY_PROPERTY": {
const newState = {
...state,
groupByProperty: payload?.groupByProperty || null,
};
return {
...state,
...newState,
};
}
case "SET_ORDER_BY_PROPERTY": {
const newState = {
...state,
orderBy: payload?.orderBy || "-created_at",
};
return {
...state,
...newState,
};
}
case "SET_SHOW_EMPTY_STATES": {
const newState = {
...state,
showEmptyGroups: payload?.showEmptyGroups || true,
};
return {
...state,
...newState,
};
}
case "SET_SHOW_SUB_ISSUES": {
const newState = {
...state,
showSubIssues: payload?.showSubIssues || true,
};
return {
...state,
...newState,
};
}
case "SET_CALENDAR_DATE_RANGE": {
const newState = {
...state,
calendarDateRange: payload?.calendarDateRange || "",
}; };
return { return {
@ -210,9 +123,13 @@ export const reducer: ReducerFunctionType = (state, action) => {
} }
}; };
const saveDataToServer = async (workspaceSlug: string, projectID: string, state: any) => { const saveDataToServer = async (
workspaceSlug: string,
projectId: string,
state: IProjectViewProps
) => {
mutate<IProjectMember>( mutate<IProjectMember>(
workspaceSlug && projectID ? USER_PROJECT_VIEW(projectID as string) : null, workspaceSlug && projectId ? USER_PROJECT_VIEW(projectId as string) : null,
(prevData) => { (prevData) => {
if (!prevData) return prevData; if (!prevData) return prevData;
@ -224,7 +141,7 @@ const saveDataToServer = async (workspaceSlug: string, projectID: string, state:
false false
); );
await projectService.setProjectView(workspaceSlug, projectID, { await projectService.setProjectView(workspaceSlug, projectId, {
view_props: state, view_props: state,
}); });
}; };
@ -354,44 +271,57 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> =
: null : null
); );
const setIssueView = useCallback( const setDisplayFilters = useCallback(
(property: TIssueViewOptions) => { (displayFilter: Partial<IIssueDisplayFilterOptions>) => {
dispatch({ dispatch({
type: "SET_ISSUE_VIEW", type: "SET_DISPLAY_FILTERS",
payload: { payload: {
issueView: property, display_filters: {
...state.display_filters,
...displayFilter,
},
}, },
}); });
const additionalProperties = { const additionalProperties: Partial<IIssueDisplayFilterOptions> = {
groupByProperty: state.groupByProperty, group_by: displayFilter.group_by ?? state.display_filters?.group_by,
orderBy: state.orderBy, order_by: displayFilter.order_by ?? state.display_filters?.order_by,
}; };
if (property === "kanban" && state.groupByProperty === null) { if (
additionalProperties.groupByProperty = "state"; displayFilter.layout &&
displayFilter.layout === "kanban" &&
state.display_filters?.group_by === null
) {
additionalProperties.group_by = "state";
dispatch({ dispatch({
type: "SET_GROUP_BY_PROPERTY", type: "SET_DISPLAY_FILTERS",
payload: { payload: {
groupByProperty: "state", display_filters: {
group_by: "state",
},
}, },
}); });
} }
if (property === "calendar") { if (displayFilter.layout && displayFilter.layout === "calendar") {
additionalProperties.groupByProperty = null; additionalProperties.group_by = null;
dispatch({ dispatch({
type: "SET_GROUP_BY_PROPERTY", type: "SET_DISPLAY_FILTERS",
payload: { payload: {
groupByProperty: null, display_filters: {
group_by: null,
},
}, },
}); });
} }
if (property === "gantt_chart") { if (displayFilter.layout && displayFilter.layout === "gantt_chart") {
additionalProperties.orderBy = "sort_order"; additionalProperties.order_by = "sort_order";
dispatch({ dispatch({
type: "SET_ORDER_BY_PROPERTY", type: "SET_DISPLAY_FILTERS",
payload: { payload: {
orderBy: "sort_order", display_filters: {
order_by: "sort_order",
},
}, },
}); });
} }
@ -400,168 +330,16 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> =
saveDataToServer(workspaceSlug as string, projectId as string, { saveDataToServer(workspaceSlug as string, projectId as string, {
...state, ...state,
issueView: property, display_filters: {
...state.display_filters,
...displayFilter,
...additionalProperties, ...additionalProperties,
},
}); });
}, },
[workspaceSlug, projectId, state] [workspaceSlug, projectId, state]
); );
const setGroupByProperty = useCallback(
(property: TIssueGroupByOptions) => {
dispatch({
type: "SET_GROUP_BY_PROPERTY",
payload: {
groupByProperty: property,
},
});
if (!workspaceSlug || !projectId) return;
mutateMyViewProps((prevData: any) => {
if (!prevData) return prevData;
return {
...prevData,
view_props: {
...state,
groupByProperty: property,
},
};
}, false);
saveDataToServer(workspaceSlug as string, projectId as string, {
...state,
groupByProperty: property,
});
},
[projectId, workspaceSlug, state, mutateMyViewProps]
);
const setOrderBy = useCallback(
(property: TIssueOrderByOptions) => {
dispatch({
type: "SET_ORDER_BY_PROPERTY",
payload: {
orderBy: property,
},
});
if (!workspaceSlug || !projectId) return;
mutateMyViewProps((prevData: any) => {
if (!prevData) return prevData;
return {
...prevData,
view_props: {
...state,
orderBy: property,
},
};
}, false);
saveDataToServer(workspaceSlug as string, projectId as string, {
...state,
orderBy: property,
});
},
[projectId, workspaceSlug, state, mutateMyViewProps]
);
const setShowEmptyGroups = useCallback(
(property: boolean) => {
dispatch({
type: "SET_SHOW_EMPTY_STATES",
payload: {
showEmptyGroups: property,
},
});
if (!workspaceSlug || !projectId) return;
mutateMyViewProps((prevData: any) => {
if (!prevData) return prevData;
return {
...prevData,
view_props: {
...state,
showEmptyGroups: property,
},
};
}, false);
saveDataToServer(workspaceSlug as string, projectId as string, {
...state,
showEmptyGroups: property,
});
},
[projectId, workspaceSlug, state, mutateMyViewProps]
);
const setShowSubIssues = useCallback(
(property: boolean) => {
dispatch({
type: "SET_SHOW_SUB_ISSUES",
payload: {
showSubIssues: property,
},
});
if (!workspaceSlug || !projectId) return;
mutateMyViewProps((prevData: any) => {
if (!prevData) return prevData;
return {
...prevData,
view_props: {
...state,
showSubIssues: property,
},
};
}, false);
saveDataToServer(workspaceSlug as string, projectId as string, {
...state,
showSubIssues: property,
});
},
[projectId, workspaceSlug, state, mutateMyViewProps]
);
const setCalendarDateRange = useCallback(
(value: string) => {
dispatch({
type: "SET_CALENDAR_DATE_RANGE",
payload: {
calendarDateRange: value,
},
});
if (!workspaceSlug || !projectId) return;
mutateMyViewProps((prevData: any) => {
if (!prevData) return prevData;
return {
...prevData,
view_props: {
...state,
calendarDateRange: value,
},
};
}, false);
saveDataToServer(workspaceSlug as string, projectId as string, {
...state,
calendarDateRange: value,
});
},
[projectId, workspaceSlug, state, mutateMyViewProps]
);
const setFilters = useCallback( const setFilters = useCallback(
(property: Partial<IIssueFilterOptions>, saveToServer = true) => { (property: Partial<IIssueFilterOptions>, saveToServer = true) => {
Object.keys(property).forEach((key) => { Object.keys(property).forEach((key) => {
@ -717,9 +495,9 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> =
payload: myViewProps?.default_props, payload: myViewProps?.default_props,
}); });
if (!workspaceSlug || !projectId) return; if (!workspaceSlug || !projectId || !myViewProps) return;
saveDataToServer(workspaceSlug as string, projectId as string, myViewProps?.default_props); saveDataToServer(workspaceSlug as string, projectId as string, myViewProps.default_props);
}, [projectId, workspaceSlug, myViewProps]); }, [projectId, workspaceSlug, myViewProps]);
useEffect(() => { useEffect(() => {
@ -743,22 +521,12 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> =
return ( return (
<issueViewContext.Provider <issueViewContext.Provider
value={{ value={{
issueView: state.issueView, display_filters: state.display_filters,
groupByProperty: state.groupByProperty, setDisplayFilters,
setGroupByProperty,
orderBy: state.orderBy,
showEmptyGroups: state.showEmptyGroups,
showSubIssues: state.showSubIssues,
calendarDateRange: state.calendarDateRange,
setCalendarDateRange,
setOrderBy,
setShowEmptyGroups,
setShowSubIssues,
filters: state.filters, filters: state.filters,
setFilters, setFilters,
resetFilterToDefault: resetToDefault, resetFilterToDefault: resetToDefault,
setNewFilterDefaultView: setNewDefaultView, setNewFilterDefaultView: setNewDefaultView,
setIssueView,
}} }}
> >
<ToastAlert /> <ToastAlert />

View File

@ -5,66 +5,36 @@ import ToastAlert from "components/toast-alert";
// types // types
import { import {
IIssueFilterOptions, IIssueFilterOptions,
TIssueViewOptions,
TIssueGroupByOptions,
TIssueOrderByOptions,
Properties, Properties,
IWorkspaceViewProps,
IIssueDisplayFilterOptions,
} from "types"; } from "types";
export const profileIssuesContext = createContext<ContextType>({} as ContextType); export const profileIssuesContext = createContext<ContextType>({} as ContextType);
type IssueViewProps = {
issueView: TIssueViewOptions;
groupByProperty: TIssueGroupByOptions;
orderBy: TIssueOrderByOptions;
showEmptyGroups: boolean;
showSubIssues: boolean;
filters: IIssueFilterOptions;
properties: Properties;
};
type ReducerActionType = { type ReducerActionType = {
type: type: "SET_DISPLAY_FILTERS" | "SET_FILTERS" | "SET_PROPERTIES" | "RESET_TO_DEFAULT";
| "SET_ISSUE_VIEW" payload?: Partial<IWorkspaceViewProps>;
| "SET_ORDER_BY_PROPERTY"
| "SET_SHOW_EMPTY_STATES"
| "SET_FILTERS"
| "SET_PROPERTIES"
| "SET_GROUP_BY_PROPERTY"
| "RESET_TO_DEFAULT"
| "SET_SHOW_SUB_ISSUES";
payload?: Partial<IssueViewProps>;
}; };
type ContextType = IssueViewProps & { type ContextType = IWorkspaceViewProps & {
setGroupByProperty: (property: TIssueGroupByOptions) => void; setDisplayFilters: (displayFilter: Partial<IIssueDisplayFilterOptions>) => void;
setOrderBy: (property: TIssueOrderByOptions) => void;
setShowEmptyGroups: (property: boolean) => void;
setShowSubIssues: (value: boolean) => void;
setFilters: (filters: Partial<IIssueFilterOptions>) => void; setFilters: (filters: Partial<IIssueFilterOptions>) => void;
setProperties: (key: keyof Properties) => void; setProperties: (key: keyof Properties) => void;
setIssueView: (property: TIssueViewOptions) => void;
}; };
type StateType = { type StateType = IWorkspaceViewProps;
issueView: TIssueViewOptions;
groupByProperty: TIssueGroupByOptions;
orderBy: TIssueOrderByOptions;
showEmptyGroups: boolean;
showSubIssues: boolean;
filters: IIssueFilterOptions;
properties: Properties;
};
type ReducerFunctionType = (state: StateType, action: ReducerActionType) => StateType; type ReducerFunctionType = (state: StateType, action: ReducerActionType) => StateType;
export const initialState: StateType = { export const initialState: StateType = {
issueView: "list", display_filters: {
groupByProperty: null, group_by: null,
orderBy: "-created_at", layout: "list",
showEmptyGroups: true, order_by: "-created_at",
showSubIssues: true, show_empty_groups: true,
sub_issue: true,
},
filters: { filters: {
type: null,
priority: null, priority: null,
assignees: null, assignees: null,
labels: null, labels: null,
@ -75,7 +45,7 @@ export const initialState: StateType = {
start_date: null, start_date: null,
target_date: null, target_date: null,
}, },
properties: { display_properties: {
assignee: true, assignee: true,
start_date: true, start_date: true,
due_date: true, due_date: true,
@ -96,58 +66,14 @@ export const reducer: ReducerFunctionType = (state, action) => {
const { type, payload } = action; const { type, payload } = action;
switch (type) { switch (type) {
case "SET_ISSUE_VIEW": { case "SET_DISPLAY_FILTERS": {
const newState = { const newState = {
...state, ...state,
issueView: payload?.issueView || "list", display_filters: {
}; ...state.display_filters,
...payload,
return { },
...state, issueView: payload?.display_filters?.layout || "list",
...newState,
};
}
case "SET_GROUP_BY_PROPERTY": {
const newState = {
...state,
groupByProperty: payload?.groupByProperty || null,
};
return {
...state,
...newState,
};
}
case "SET_ORDER_BY_PROPERTY": {
const newState = {
...state,
orderBy: payload?.orderBy || "-created_at",
};
return {
...state,
...newState,
};
}
case "SET_SHOW_EMPTY_STATES": {
const newState = {
...state,
showEmptyGroups: payload?.showEmptyGroups || true,
};
return {
...state,
...newState,
};
}
case "SET_SHOW_SUB_ISSUES": {
const newState = {
...state,
showSubIssues: payload?.showSubIssues || true,
}; };
return { return {
@ -175,8 +101,8 @@ export const reducer: ReducerFunctionType = (state, action) => {
const newState = { const newState = {
...state, ...state,
properties: { properties: {
...state.properties, ...state.display_properties,
...payload?.properties, ...payload?.display_properties,
}, },
}; };
@ -197,20 +123,29 @@ export const ProfileIssuesContextProvider: React.FC<{ children: React.ReactNode
}) => { }) => {
const [state, dispatch] = useReducer(reducer, initialState); const [state, dispatch] = useReducer(reducer, initialState);
const setIssueView = useCallback( const setDisplayFilters = useCallback(
(property: TIssueViewOptions) => { (displayFilter: Partial<IIssueDisplayFilterOptions>) => {
dispatch({ dispatch({
type: "SET_ISSUE_VIEW", type: "SET_DISPLAY_FILTERS",
payload: { payload: {
issueView: property, display_filters: {
...state.display_filters,
...displayFilter,
},
}, },
}); });
if (property === "kanban" && state.groupByProperty === null) { if (
displayFilter.layout &&
displayFilter.layout === "kanban" &&
state.display_filters?.group_by === null
) {
dispatch({ dispatch({
type: "SET_GROUP_BY_PROPERTY", type: "SET_DISPLAY_FILTERS",
payload: { payload: {
groupByProperty: "state_detail.group", display_filters: {
group_by: "state_detail.group",
},
}, },
}); });
} }
@ -218,42 +153,6 @@ export const ProfileIssuesContextProvider: React.FC<{ children: React.ReactNode
[state] [state]
); );
const setGroupByProperty = useCallback((property: TIssueGroupByOptions) => {
dispatch({
type: "SET_GROUP_BY_PROPERTY",
payload: {
groupByProperty: property,
},
});
}, []);
const setOrderBy = useCallback((property: TIssueOrderByOptions) => {
dispatch({
type: "SET_ORDER_BY_PROPERTY",
payload: {
orderBy: property,
},
});
}, []);
const setShowEmptyGroups = useCallback((property: boolean) => {
dispatch({
type: "SET_SHOW_EMPTY_STATES",
payload: {
showEmptyGroups: property,
},
});
}, []);
const setShowSubIssues = useCallback((property: boolean) => {
dispatch({
type: "SET_SHOW_SUB_ISSUES",
payload: {
showSubIssues: property,
},
});
}, []);
const setFilters = useCallback( const setFilters = useCallback(
(property: Partial<IIssueFilterOptions>) => { (property: Partial<IIssueFilterOptions>) => {
Object.keys(property).forEach((key) => { Object.keys(property).forEach((key) => {
@ -279,9 +178,9 @@ export const ProfileIssuesContextProvider: React.FC<{ children: React.ReactNode
dispatch({ dispatch({
type: "SET_PROPERTIES", type: "SET_PROPERTIES",
payload: { payload: {
properties: { display_properties: {
...state.properties, ...state.display_properties,
[key]: !state.properties[key], [key]: !state.display_properties[key],
}, },
}, },
}); });
@ -292,19 +191,11 @@ export const ProfileIssuesContextProvider: React.FC<{ children: React.ReactNode
return ( return (
<profileIssuesContext.Provider <profileIssuesContext.Provider
value={{ value={{
issueView: state.issueView, display_filters: state.display_filters,
setIssueView, setDisplayFilters,
groupByProperty: state.groupByProperty,
setGroupByProperty,
orderBy: state.orderBy,
setOrderBy,
showEmptyGroups: state.showEmptyGroups,
setShowEmptyGroups,
showSubIssues: state.showSubIssues,
setShowSubIssues,
filters: state.filters, filters: state.filters,
setFilters, setFilters,
properties: state.properties, display_properties: state.display_properties,
setProperties, setProperties,
}} }}
> >

View File

@ -403,3 +403,5 @@ export const findTotalDaysInRange = (
return diffInDays; return diffInDays;
}; };
export const getUserTimeZoneFromWindow = () => Intl.DateTimeFormat().resolvedOptions().timeZone;

View File

@ -12,12 +12,12 @@ const useGanttChartCycleIssues = (
projectId: string | undefined, projectId: string | undefined,
cycleId: string | undefined cycleId: string | undefined
) => { ) => {
const { orderBy, filters, showSubIssues } = useIssuesView(); const { displayFilters, filters } = useIssuesView();
const params: any = { const params: any = {
order_by: orderBy, order_by: displayFilters.order_by,
type: filters?.type ? filters?.type : undefined, type: displayFilters?.type ? displayFilters?.type : undefined,
sub_issue: showSubIssues, sub_issue: displayFilters.sub_issue,
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined, assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
state: filters?.state ? filters?.state.join(",") : undefined, state: filters?.state ? filters?.state.join(",") : undefined,
priority: filters?.priority ? filters?.priority.join(",") : undefined, priority: filters?.priority ? filters?.priority.join(",") : undefined,

View File

@ -8,12 +8,12 @@ import useIssuesView from "hooks/use-issues-view";
import { PROJECT_ISSUES_LIST_WITH_PARAMS } from "constants/fetch-keys"; import { PROJECT_ISSUES_LIST_WITH_PARAMS } from "constants/fetch-keys";
const useGanttChartIssues = (workspaceSlug: string | undefined, projectId: string | undefined) => { const useGanttChartIssues = (workspaceSlug: string | undefined, projectId: string | undefined) => {
const { orderBy, filters, showSubIssues } = useIssuesView(); const { displayFilters, filters } = useIssuesView();
const params: any = { const params: any = {
order_by: orderBy, order_by: displayFilters.order_by,
type: filters?.type ? filters?.type : undefined, type: displayFilters?.type ? displayFilters?.type : undefined,
sub_issue: showSubIssues, sub_issue: displayFilters.sub_issue,
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined, assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
state: filters?.state ? filters?.state.join(",") : undefined, state: filters?.state ? filters?.state.join(",") : undefined,
priority: filters?.priority ? filters?.priority.join(",") : undefined, priority: filters?.priority ? filters?.priority.join(",") : undefined,

View File

@ -12,12 +12,12 @@ const useGanttChartModuleIssues = (
projectId: string | undefined, projectId: string | undefined,
moduleId: string | undefined moduleId: string | undefined
) => { ) => {
const { orderBy, filters, showSubIssues } = useIssuesView(); const { displayFilters, filters } = useIssuesView();
const params: any = { const params: any = {
order_by: orderBy, order_by: displayFilters.order_by,
type: filters?.type ? filters?.type : undefined, type: displayFilters?.type ? displayFilters?.type : undefined,
sub_issue: showSubIssues, sub_issue: displayFilters.sub_issue,
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined, assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
state: filters?.state ? filters?.state.join(",") : undefined, state: filters?.state ? filters?.state.join(",") : undefined,
priority: filters?.priority ? filters?.priority.join(",") : undefined, priority: filters?.priority ? filters?.priority.join(",") : undefined,

View File

@ -6,33 +6,25 @@ import useSWR, { mutate } from "swr";
import workspaceService from "services/workspace.service"; import workspaceService from "services/workspace.service";
// types // types
import { import {
IIssueDisplayFilterOptions,
IIssueFilterOptions, IIssueFilterOptions,
IWorkspaceMember, IWorkspaceMember,
IWorkspaceViewProps, IWorkspaceViewProps,
Properties, Properties,
TIssueGroupByOptions,
TIssueOrderByOptions,
TIssueViewOptions,
} from "types"; } from "types";
// fetch-keys // fetch-keys
import { WORKSPACE_MEMBERS_ME } from "constants/fetch-keys"; import { WORKSPACE_MEMBERS_ME } from "constants/fetch-keys";
const initialValues: IWorkspaceViewProps = { const initialValues: IWorkspaceViewProps = {
issueView: "list", display_filters: {
filters: { group_by: null,
assignees: null, layout: "list",
created_by: null, order_by: "-created_at",
labels: null, show_empty_groups: true,
priority: null, sub_issue: true,
state_group: null,
subscriber: null,
start_date: null,
target_date: null,
type: null, type: null,
}, },
groupByProperty: null, display_properties: {
orderBy: "-created_at",
properties: {
assignee: true, assignee: true,
start_date: true, start_date: true,
due_date: true, due_date: true,
@ -47,7 +39,16 @@ const initialValues: IWorkspaceViewProps = {
created_on: true, created_on: true,
updated_on: true, updated_on: true,
}, },
showEmptyGroups: true, filters: {
assignees: null,
created_by: null,
labels: null,
priority: null,
state_group: null,
subscriber: null,
start_date: null,
target_date: null,
},
}; };
const useMyIssuesFilters = (workspaceSlug: string | undefined) => { const useMyIssuesFilters = (workspaceSlug: string | undefined) => {
@ -90,59 +91,39 @@ const useMyIssuesFilters = (workspaceSlug: string | undefined) => {
[myWorkspace, workspaceSlug] [myWorkspace, workspaceSlug]
); );
const issueView = (myWorkspace?.view_props ?? initialValues).issueView; const groupBy = (myWorkspace?.view_props ?? initialValues).display_filters?.group_by;
const groupBy = (myWorkspace?.view_props ?? initialValues).groupByProperty;
const orderBy = (myWorkspace?.view_props ?? initialValues).orderBy;
const showEmptyGroups = (myWorkspace?.view_props ?? initialValues).showEmptyGroups;
const filters = (myWorkspace?.view_props ?? initialValues).filters; const filters = (myWorkspace?.view_props ?? initialValues).filters;
const setIssueView = useCallback( const setDisplayFilters = useCallback(
(newView: TIssueViewOptions) => { (displayFilter: Partial<IIssueDisplayFilterOptions>) => {
const payload: Partial<IWorkspaceViewProps> = { const payload: Partial<IWorkspaceViewProps> = {
issueView: newView, display_filters: {
...myWorkspace?.view_props?.display_filters,
...displayFilter,
},
}; };
if (newView === "kanban" && groupBy === null) payload.groupByProperty = "state_detail.group"; if (
displayFilter.layout &&
displayFilter.layout === "kanban" &&
groupBy === null &&
payload.display_filters
)
payload.display_filters.group_by = "state_detail.group";
saveData(payload); saveData(payload);
}, },
[groupBy, saveData] [groupBy, myWorkspace?.view_props.display_filters, saveData]
); );
const setGroupBy = useCallback(
(newGroup: TIssueGroupByOptions) => {
saveData({
groupByProperty: newGroup,
});
},
[saveData]
);
const setOrderBy = useCallback(
(newOrderBy: TIssueOrderByOptions) => {
saveData({
orderBy: newOrderBy,
});
},
[saveData]
);
const setShowEmptyGroups = useCallback(() => {
if (!myWorkspace) return;
saveData({
showEmptyGroups: !myWorkspace?.view_props?.showEmptyGroups,
});
}, [myWorkspace, saveData]);
const setProperty = useCallback( const setProperty = useCallback(
(key: keyof Properties) => { (key: keyof Properties) => {
if (!myWorkspace) return; if (!myWorkspace) return;
saveData({ saveData({
properties: { display_properties: {
...myWorkspace.view_props?.properties, ...myWorkspace.view_props?.display_properties,
[key]: !myWorkspace.view_props?.properties[key], [key]: !myWorkspace.view_props?.display_properties[key],
}, },
}); });
}, },
@ -174,30 +155,24 @@ const useMyIssuesFilters = (workspaceSlug: string | undefined) => {
}, [myWorkspace, workspaceSlug]); }, [myWorkspace, workspaceSlug]);
const newProperties: Properties = { const newProperties: Properties = {
assignee: myWorkspace?.view_props.properties.assignee ?? true, assignee: myWorkspace?.view_props.display_properties?.assignee ?? true,
start_date: myWorkspace?.view_props.properties.start_date ?? true, start_date: myWorkspace?.view_props.display_properties?.start_date ?? true,
due_date: myWorkspace?.view_props.properties.due_date ?? true, due_date: myWorkspace?.view_props.display_properties?.due_date ?? true,
key: myWorkspace?.view_props.properties.key ?? true, key: myWorkspace?.view_props.display_properties?.key ?? true,
labels: myWorkspace?.view_props.properties.labels ?? true, labels: myWorkspace?.view_props.display_properties?.labels ?? true,
priority: myWorkspace?.view_props.properties.priority ?? true, priority: myWorkspace?.view_props.display_properties?.priority ?? true,
state: myWorkspace?.view_props.properties.state ?? true, state: myWorkspace?.view_props.display_properties?.state ?? true,
sub_issue_count: myWorkspace?.view_props.properties.sub_issue_count ?? true, sub_issue_count: myWorkspace?.view_props.display_properties?.sub_issue_count ?? true,
attachment_count: myWorkspace?.view_props.properties.attachment_count ?? true, attachment_count: myWorkspace?.view_props.display_properties?.attachment_count ?? true,
link: myWorkspace?.view_props.properties.link ?? true, link: myWorkspace?.view_props.display_properties?.link ?? true,
estimate: myWorkspace?.view_props.properties.estimate ?? true, estimate: myWorkspace?.view_props.display_properties?.estimate ?? true,
created_on: myWorkspace?.view_props.properties.created_on ?? true, created_on: myWorkspace?.view_props.display_properties?.created_on ?? true,
updated_on: myWorkspace?.view_props.properties.updated_on ?? true, updated_on: myWorkspace?.view_props.display_properties?.updated_on ?? true,
}; };
return { return {
issueView, displayFilters: myWorkspace?.view_props?.display_filters,
setIssueView, setDisplayFilters,
groupBy,
setGroupBy,
orderBy,
setOrderBy,
showEmptyGroups,
setShowEmptyGroups,
properties: newProperties, properties: newProperties,
setProperty, setProperty,
filters, filters,

View File

@ -16,20 +16,20 @@ import { USER_ISSUES } from "constants/fetch-keys";
const useMyIssues = (workspaceSlug: string | undefined) => { const useMyIssues = (workspaceSlug: string | undefined) => {
const router = useRouter(); const router = useRouter();
const { filters, groupBy, orderBy } = useMyIssuesFilters(workspaceSlug); const { filters, displayFilters } = useMyIssuesFilters(workspaceSlug);
const params: any = { const params: any = {
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined, assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined, created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
group_by: groupBy, group_by: displayFilters?.group_by,
labels: filters?.labels ? filters?.labels.join(",") : undefined, labels: filters?.labels ? filters?.labels.join(",") : undefined,
order_by: orderBy, order_by: displayFilters?.order_by,
priority: filters?.priority ? filters?.priority.join(",") : undefined, priority: filters?.priority ? filters?.priority.join(",") : undefined,
state_group: filters?.state_group ? filters?.state_group.join(",") : undefined, state_group: filters?.state_group ? filters?.state_group.join(",") : undefined,
subscriber: filters?.subscriber ? filters?.subscriber.join(",") : undefined, subscriber: filters?.subscriber ? filters?.subscriber.join(",") : undefined,
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined, start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,
target_date: filters?.target_date ? filters?.target_date.join(",") : undefined, target_date: filters?.target_date ? filters?.target_date.join(",") : undefined,
type: filters?.type ? filters?.type : undefined, type: displayFilters?.type,
}; };
const { data: myIssues, mutate: mutateMyIssues } = useSWR( const { data: myIssues, mutate: mutateMyIssues } = useSWR(
@ -53,7 +53,7 @@ const useMyIssues = (workspaceSlug: string | undefined) => {
allIssues: myIssues, allIssues: myIssues,
}; };
if (groupBy === "state_detail.group") { if (displayFilters?.group_by === "state_detail.group") {
return myIssues return myIssues
? Object.assign( ? Object.assign(
{ {
@ -69,7 +69,7 @@ const useMyIssues = (workspaceSlug: string | undefined) => {
} }
return myIssues; return myIssues;
}, [groupBy, myIssues]); }, [displayFilters, myIssues]);
const isEmpty = const isEmpty =
Object.values(groupedIssues ?? {}).every((group) => group.length === 0) || Object.values(groupedIssues ?? {}).every((group) => group.length === 0) ||

View File

@ -22,14 +22,12 @@ import {
const useCalendarIssuesView = () => { const useCalendarIssuesView = () => {
const { const {
issueView, display_filters: displayFilters,
calendarDateRange, setDisplayFilters,
setCalendarDateRange,
filters, filters,
setFilters, setFilters,
resetFilterToDefault, resetFilterToDefault,
setNewFilterDefaultView, setNewFilterDefaultView,
setIssueView,
} = useContext(issueViewContext); } = useContext(issueViewContext);
const router = useRouter(); const router = useRouter();
@ -39,11 +37,11 @@ const useCalendarIssuesView = () => {
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined, assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
state: filters?.state ? filters?.state.join(",") : undefined, state: filters?.state ? filters?.state.join(",") : undefined,
priority: filters?.priority ? filters?.priority.join(",") : undefined, priority: filters?.priority ? filters?.priority.join(",") : undefined,
type: filters?.type ? filters?.type : undefined, type: displayFilters?.type ? displayFilters?.type : undefined,
labels: filters?.labels ? filters?.labels.join(",") : undefined, labels: filters?.labels ? filters?.labels.join(",") : undefined,
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined, created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined, start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,
target_date: calendarDateRange, target_date: displayFilters?.calendar_date_range,
}; };
const { data: projectCalendarIssues } = useSWR( const { data: projectCalendarIssues } = useSWR(
@ -103,16 +101,14 @@ const useCalendarIssuesView = () => {
: (projectCalendarIssues as IIssue[]); : (projectCalendarIssues as IIssue[]);
return { return {
issueView, displayFilters,
setDisplayFilters,
calendarIssues: calendarIssues ?? [], calendarIssues: calendarIssues ?? [],
calendarDateRange,
setCalendarDateRange,
filters, filters,
setFilters, setFilters,
params, params,
resetFilterToDefault, resetFilterToDefault,
setNewFilterDefaultView, setNewFilterDefaultView,
setIssueView,
} as const; } as const;
}; };

View File

@ -27,22 +27,12 @@ import {
const useIssuesView = () => { const useIssuesView = () => {
const { const {
issueView, display_filters: displayFilters,
groupByProperty, setDisplayFilters,
setGroupByProperty,
orderBy,
setOrderBy,
showEmptyGroups,
showSubIssues,
setShowEmptyGroups,
setShowSubIssues,
calendarDateRange,
setCalendarDateRange,
filters, filters,
setFilters, setFilters,
resetFilterToDefault, resetFilterToDefault,
setNewFilterDefaultView, setNewFilterDefaultView,
setIssueView,
} = useContext(issueViewContext); } = useContext(issueViewContext);
const router = useRouter(); const router = useRouter();
@ -50,17 +40,17 @@ const useIssuesView = () => {
const isArchivedIssues = router.pathname.includes("archived-issues"); const isArchivedIssues = router.pathname.includes("archived-issues");
const params: any = { const params: any = {
order_by: orderBy, order_by: displayFilters?.order_by,
group_by: groupByProperty, group_by: displayFilters?.group_by,
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined, assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
state: filters?.state ? filters?.state.join(",") : undefined, state: filters?.state ? filters?.state.join(",") : undefined,
priority: filters?.priority ? filters?.priority.join(",") : undefined, priority: filters?.priority ? filters?.priority.join(",") : undefined,
type: filters?.type ? filters?.type : undefined, type: displayFilters?.type ? displayFilters?.type : undefined,
labels: filters?.labels ? filters?.labels.join(",") : undefined, labels: filters?.labels ? filters?.labels.join(",") : undefined,
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined, created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined, start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,
target_date: filters?.target_date ? filters?.target_date.join(",") : undefined, target_date: filters?.target_date ? filters?.target_date.join(",") : undefined,
sub_issue: showSubIssues, sub_issue: displayFilters?.sub_issue,
}; };
const { data: projectIssues, mutate: mutateProjectIssues } = useSWR( const { data: projectIssues, mutate: mutateProjectIssues } = useSWR(
@ -133,9 +123,9 @@ const useIssuesView = () => {
const backlogStatesList = statesList?.filter((state) => state.group === "backlog"); const backlogStatesList = statesList?.filter((state) => state.group === "backlog");
const stateIds = const stateIds =
filters && filters?.type === "active" displayFilters && displayFilters?.type === "active"
? activeStatesList?.map((state) => state.id) ? activeStatesList?.map((state) => state.id)
: filters?.type === "backlog" : displayFilters?.type === "backlog"
? backlogStatesList?.map((state) => state.id) ? backlogStatesList?.map((state) => state.id)
: statesList?.map((state) => state.id); : statesList?.map((state) => state.id);
@ -164,17 +154,17 @@ const useIssuesView = () => {
: projectIssues; : projectIssues;
if (Array.isArray(issuesToGroup)) return { allIssues: issuesToGroup }; if (Array.isArray(issuesToGroup)) return { allIssues: issuesToGroup };
if (groupByProperty === "state") if (displayFilters?.group_by === "state")
return issuesToGroup ? Object.assign(emptyStatesObject, issuesToGroup) : undefined; return issuesToGroup ? Object.assign(emptyStatesObject, issuesToGroup) : undefined;
return issuesToGroup; return issuesToGroup;
}, [ }, [
displayFilters?.group_by,
projectIssues, projectIssues,
cycleIssues, cycleIssues,
moduleIssues, moduleIssues,
viewIssues, viewIssues,
projectArchivedIssues, projectArchivedIssues,
groupByProperty,
cycleId, cycleId,
moduleId, moduleId,
viewId, viewId,
@ -187,6 +177,11 @@ const useIssuesView = () => {
Object.keys(groupedByIssues ?? {}).length === 0; Object.keys(groupedByIssues ?? {}).length === 0;
return { return {
displayFilters: {
...displayFilters,
layout: isArchivedIssues ? "list" : displayFilters?.layout,
},
setDisplayFilters,
groupedByIssues, groupedByIssues,
mutateIssues: cycleId mutateIssues: cycleId
? mutateCycleIssues ? mutateCycleIssues
@ -197,24 +192,12 @@ const useIssuesView = () => {
: isArchivedIssues : isArchivedIssues
? mutateProjectArchivedIssues ? mutateProjectArchivedIssues
: mutateProjectIssues, : mutateProjectIssues,
issueView: isArchivedIssues ? "list" : issueView,
groupByProperty,
setGroupByProperty,
orderBy,
setOrderBy,
showEmptyGroups: isArchivedIssues ? false : showEmptyGroups,
showSubIssues,
setShowEmptyGroups,
setShowSubIssues,
calendarDateRange,
setCalendarDateRange,
filters, filters,
setFilters, setFilters,
params, params,
isEmpty, isEmpty,
resetFilterToDefault, resetFilterToDefault,
setNewFilterDefaultView, setNewFilterDefaultView,
setIssueView,
} as const; } as const;
}; };

View File

@ -16,19 +16,11 @@ import { useWorkspaceMyMembership } from "contexts/workspace-member.context";
const useProfileIssues = (workspaceSlug: string | undefined, userId: string | undefined) => { const useProfileIssues = (workspaceSlug: string | undefined, userId: string | undefined) => {
const { const {
issueView, display_filters: displayFilters,
setIssueView, setDisplayFilters,
groupByProperty,
setGroupByProperty,
orderBy,
setOrderBy,
showEmptyGroups,
setShowEmptyGroups,
showSubIssues,
setShowSubIssues,
filters, filters,
setFilters, setFilters,
properties, display_properties: displayProperties,
setProperties, setProperties,
} = useContext(profileIssuesContext); } = useContext(profileIssuesContext);
@ -39,14 +31,14 @@ const useProfileIssues = (workspaceSlug: string | undefined, userId: string | un
const params: any = { const params: any = {
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined, assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined, created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
group_by: groupByProperty, group_by: displayFilters?.group_by,
labels: filters?.labels ? filters?.labels.join(",") : undefined, labels: filters?.labels ? filters?.labels.join(",") : undefined,
order_by: orderBy, order_by: displayFilters?.order_by,
priority: filters?.priority ? filters?.priority.join(",") : undefined, priority: filters?.priority ? filters?.priority.join(",") : undefined,
state_group: filters?.state_group ? filters?.state_group.join(",") : undefined, state_group: filters?.state_group ? filters?.state_group.join(",") : undefined,
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined, start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,
target_date: filters?.target_date ? filters?.target_date.join(",") : undefined, target_date: filters?.target_date ? filters?.target_date.join(",") : undefined,
type: filters?.type ? filters?.type : undefined, type: displayFilters?.type ? displayFilters?.type : undefined,
subscriber: filters?.subscriber ? filters?.subscriber.join(",") : undefined, subscriber: filters?.subscriber ? filters?.subscriber.join(",") : undefined,
}; };
@ -71,7 +63,7 @@ const useProfileIssues = (workspaceSlug: string | undefined, userId: string | un
allIssues: userProfileIssues, allIssues: userProfileIssues,
}; };
if (groupByProperty === "state_detail.group") { if (displayFilters?.group_by === "state_detail.group") {
return userProfileIssues return userProfileIssues
? Object.assign( ? Object.assign(
{ {
@ -87,7 +79,7 @@ const useProfileIssues = (workspaceSlug: string | undefined, userId: string | un
} }
return userProfileIssues; return userProfileIssues;
}, [groupByProperty, userProfileIssues]); }, [displayFilters?.group_by, userProfileIssues]);
useEffect(() => { useEffect(() => {
if (!userId || !filters) return; if (!userId || !filters) return;
@ -120,19 +112,11 @@ const useProfileIssues = (workspaceSlug: string | undefined, userId: string | un
return { return {
groupedIssues, groupedIssues,
issueView, displayFilters,
setIssueView, setDisplayFilters,
groupByProperty,
setGroupByProperty,
orderBy,
setOrderBy,
showEmptyGroups,
setShowEmptyGroups,
showSubIssues,
setShowSubIssues,
filters, filters,
setFilters, setFilters,
properties, displayProperties,
setProperties, setProperties,
isEmpty, isEmpty,
mutateProfileIssues, mutateProfileIssues,

View File

@ -22,25 +22,23 @@ import {
const useSpreadsheetIssuesView = () => { const useSpreadsheetIssuesView = () => {
const { const {
issueView, display_filters: displayFilters,
orderBy, setDisplayFilters,
setOrderBy,
filters, filters,
setFilters, setFilters,
resetFilterToDefault, resetFilterToDefault,
setNewFilterDefaultView, setNewFilterDefaultView,
setIssueView,
} = useContext(issueViewContext); } = useContext(issueViewContext);
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
const params: any = { const params: any = {
order_by: orderBy, order_by: displayFilters?.order_by,
assignees: filters?.assignees ? filters?.assignees.join(",") : undefined, assignees: filters?.assignees ? filters?.assignees.join(",") : undefined,
state: filters?.state ? filters?.state.join(",") : undefined, state: filters?.state ? filters?.state.join(",") : undefined,
priority: filters?.priority ? filters?.priority.join(",") : undefined, priority: filters?.priority ? filters?.priority.join(",") : undefined,
type: filters?.type ? filters?.type : undefined, type: displayFilters?.type ? displayFilters?.type : undefined,
labels: filters?.labels ? filters?.labels.join(",") : undefined, labels: filters?.labels ? filters?.labels.join(",") : undefined,
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined, created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined, start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,
@ -105,7 +103,8 @@ const useSpreadsheetIssuesView = () => {
: (projectSpreadsheetIssues as IIssue[]); : (projectSpreadsheetIssues as IIssue[]);
return { return {
issueView, displayFilters,
setDisplayFilters,
mutateIssues: cycleId mutateIssues: cycleId
? mutateCycleSpreadsheetIssues ? mutateCycleSpreadsheetIssues
: moduleId : moduleId
@ -114,14 +113,11 @@ const useSpreadsheetIssuesView = () => {
? mutateViewSpreadsheetIssues ? mutateViewSpreadsheetIssues
: mutateProjectSpreadsheetIssues, : mutateProjectSpreadsheetIssues,
spreadsheetIssues: spreadsheetIssues ?? [], spreadsheetIssues: spreadsheetIssues ?? [],
orderBy,
setOrderBy,
filters, filters,
setFilters, setFilters,
params, params,
resetFilterToDefault, resetFilterToDefault,
setNewFilterDefaultView, setNewFilterDefaultView,
setIssueView,
} as const; } as const;
}; };

View File

@ -7,7 +7,6 @@ import { PlusIcon } from "@heroicons/react/24/outline";
// layouts // layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout";
// hooks // hooks
import useProjects from "hooks/use-projects";
import useMyIssuesFilters from "hooks/my-issues/use-my-issues-filter"; import useMyIssuesFilters from "hooks/my-issues/use-my-issues-filter";
// components // components
import { MyIssuesView, MyIssuesViewOptions } from "components/issues"; import { MyIssuesView, MyIssuesViewOptions } from "components/issues";

View File

@ -4,8 +4,6 @@ import useSWR from "swr";
// services // services
import projectService from "services/project.service"; import projectService from "services/project.service";
// hooks
import useIssuesView from "hooks/use-issues-view";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
// contexts // contexts
@ -28,8 +26,6 @@ const ProjectArchivedIssues: NextPage = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
const { showEmptyGroups, setShowEmptyGroups } = useIssuesView();
const { data: projectDetails } = useSWR( const { data: projectDetails } = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
workspaceSlug && projectId workspaceSlug && projectId

View File

@ -8,7 +8,6 @@ import type {
IIssueActivity, IIssueActivity,
IIssueComment, IIssueComment,
IIssueLabels, IIssueLabels,
IIssueViewOptions,
ISubIssueResponse, ISubIssueResponse,
} from "types"; } from "types";

View File

@ -3,7 +3,7 @@ import APIService from "services/api.service";
import trackEventServices from "./track-event.service"; import trackEventServices from "./track-event.service";
// types // types
import type { IIssueViewOptions, IModule, IIssue, ICurrentUserResponse } from "types"; import type { IModule, IIssue, ICurrentUserResponse } from "types";
const { NEXT_PUBLIC_API_BASE_URL } = process.env; const { NEXT_PUBLIC_API_BASE_URL } = process.env;

View File

@ -12,7 +12,7 @@ import type {
IProjectMemberInvitation, IProjectMemberInvitation,
ISearchIssueResponse, ISearchIssueResponse,
ProjectPreferences, ProjectPreferences,
ProjectViewTheme, IProjectViewProps,
TProjectIssuesSearchParams, TProjectIssuesSearchParams,
} from "types"; } from "types";
@ -297,8 +297,8 @@ export class ProjectServices extends APIService {
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
data: { data: {
view_props?: ProjectViewTheme; view_props?: IProjectViewProps;
default_props?: ProjectViewTheme; default_props?: IProjectViewProps;
preferences?: ProjectPreferences; preferences?: ProjectPreferences;
sort_order?: number; sort_order?: number;
} }

View File

@ -18,6 +18,7 @@ export * from "./calendar";
export * from "./notifications"; export * from "./notifications";
export * from "./waitlist"; export * from "./waitlist";
export * from "./reaction"; export * from "./reaction";
export * from "./view-props";
export type NestedKeyOf<ObjectType extends object> = { export type NestedKeyOf<ObjectType extends object> = {
[Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object [Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object

59
web/types/issues.d.ts vendored
View File

@ -11,6 +11,11 @@ import type {
IStateLite, IStateLite,
TStateGroups, TStateGroups,
Properties, Properties,
IIssueFilterOptions,
TIssueGroupByOptions,
TIssueViewOptions,
TIssueOrderByOptions,
IIssueDisplayFilterOptions,
} from "types"; } from "types";
export interface IIssueCycle { export interface IIssueCycle {
@ -213,55 +218,6 @@ export interface IIssueLite {
workspace__slug: string; workspace__slug: string;
} }
export interface IIssueFilterOptions {
type: "active" | "backlog" | null;
assignees: string[] | null;
start_date: string[] | null;
target_date: string[] | null;
state: string[] | null;
state_group: TStateGroups[] | null;
subscriber: string[] | null;
labels: string[] | null;
priority: string[] | null;
created_by: string[] | null;
}
export type TIssueViewOptions = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt_chart";
export type TIssueGroupByOptions =
| "state"
| "priority"
| "labels"
| "created_by"
| "state_detail.group"
| "project"
| "assignees"
| null;
export type TIssueOrderByOptions =
| "-created_at"
| "-updated_at"
| "priority"
| "sort_order"
| "state__name"
| "-state__name"
| "assignees__name"
| "-assignees__name"
| "labels__name"
| "-labels__name"
| "target_date"
| "-target_date"
| "estimate__point"
| "-estimate__point"
| "start_date"
| "-start_date";
export interface IIssueViewOptions {
group_by: TIssueGroupByOptions;
order_by: TIssueOrderByOptions;
filters: IIssueFilterOptions;
}
export interface IIssueAttachment { export interface IIssueAttachment {
asset: string; asset: string;
attributes: { attributes: {
@ -280,19 +236,16 @@ export interface IIssueAttachment {
export interface IIssueViewProps { export interface IIssueViewProps {
groupedIssues: { [key: string]: IIssue[] } | undefined; groupedIssues: { [key: string]: IIssue[] } | undefined;
groupByProperty: TIssueGroupByOptions; displayFilters: IIssueDisplayFilterOptions | undefined;
isEmpty: boolean; isEmpty: boolean;
issueView: TIssueViewOptions;
mutateIssues: KeyedMutator< mutateIssues: KeyedMutator<
| IIssue[] | IIssue[]
| { | {
[key: string]: IIssue[]; [key: string]: IIssue[];
} }
>; >;
orderBy: TIssueOrderByOptions;
params: any; params: any;
properties: Properties; properties: Properties;
showEmptyGroups: boolean;
} }
export type TIssuePriorities = "urgent" | "high" | "medium" | "low" | null; export type TIssuePriorities = "urgent" | "high" | "medium" | "low" | null;

View File

@ -8,6 +8,7 @@ import type {
TIssueOrderByOptions, TIssueOrderByOptions,
TIssueViewOptions, TIssueViewOptions,
TStateGroups, TStateGroups,
IProjectViewProps,
} from "."; } from ".";
export interface IProject { export interface IProject {
@ -66,14 +67,6 @@ export interface IProjectLite {
identifier: string; identifier: string;
} }
type ProjectViewTheme = {
issueView: TIssueViewOptions;
groupByProperty: TIssueGroupByOptions;
orderBy: TIssueOrderByOptions;
calendarDateRange: string;
filters: IIssueFilterOptions;
};
type ProjectPreferences = { type ProjectPreferences = {
pages: { pages: {
block_display: boolean; block_display: boolean;
@ -90,8 +83,8 @@ export interface IProjectMember {
preferences: ProjectPreferences; preferences: ProjectPreferences;
view_props: ProjectViewTheme; view_props: IProjectViewProps;
default_props: ProjectViewTheme; default_props: IProjectViewProps;
created_at: Date; created_at: Date;
updated_at: Date; updated_at: Date;

63
web/types/view-props.d.ts vendored Normal file
View File

@ -0,0 +1,63 @@
export type TIssueViewOptions = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt_chart";
export type TIssueGroupByOptions =
| "state"
| "priority"
| "labels"
| "created_by"
| "state_detail.group"
| "project"
| "assignees"
| null;
export type TIssueOrderByOptions =
| "-created_at"
| "-updated_at"
| "priority"
| "sort_order"
| "state__name"
| "-state__name"
| "assignees__name"
| "-assignees__name"
| "labels__name"
| "-labels__name"
| "target_date"
| "-target_date"
| "estimate__point"
| "-estimate__point"
| "start_date"
| "-start_date";
export interface IIssueFilterOptions {
assignees?: string[] | null;
created_by?: string[] | null;
labels?: string[] | null;
priority?: string[] | null;
start_date?: string[] | null;
state?: string[] | null;
state_group?: TStateGroups[] | null;
subscriber?: string[] | null;
target_date?: string[] | null;
}
export interface IIssueDisplayFilterOptions {
calendar_date_range?: string;
group_by?: TIssueGroupByOptions;
layout?: TIssueViewOptions;
order_by?: TIssueOrderByOptions;
show_empty_groups?: boolean;
start_target_date?: boolean;
sub_issue?: boolean;
type?: "active" | "backlog" | null;
}
export interface IProjectViewProps {
display_filters: IIssueDisplayFilterOptions | undefined;
filters: IIssueFilterOptions;
}
export interface IWorkspaceViewProps {
display_filters: IIssueDisplayFilterOptions | undefined;
display_properties: Properties | undefined;
filters: IIssueFilterOptions;
}

View File

@ -3,6 +3,7 @@ import type {
IProjectMember, IProjectMember,
IUser, IUser,
IUserMemberLite, IUserMemberLite,
IWorkspaceViewProps,
TIssueGroupByOptions, TIssueGroupByOptions,
TIssueOrderByOptions, TIssueOrderByOptions,
TIssueViewOptions, TIssueViewOptions,
@ -63,15 +64,6 @@ export type Properties = {
updated_on: boolean; updated_on: boolean;
}; };
export interface IWorkspaceViewProps {
properties: Properties;
issueView: TIssueViewOptions;
groupByProperty: TIssueGroupByOptions;
orderBy: TIssueOrderByOptions;
filters: Partial<IIssueFilterOptions>;
showEmptyGroups: boolean;
}
export interface IWorkspaceMember { export interface IWorkspaceMember {
readonly id: string; readonly id: string;
workspace: IWorkspace; workspace: IWorkspace;