Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0106689c89 | |||
| 31dc1f193f | |||
| c2070a09ed | |||
| 2b595cfe62 | |||
| 7a6b50a6e1 | |||
| a5c2acb5f1 | |||
| 4cf0c702ce | |||
| d36c3acbf7 | |||
| e244f48776 | |||
| 89d1926727 | |||
| 9bd70cdb4e | |||
| 99f3d5810d | |||
| 10b5c625ef | |||
| c14fb814c4 | |||
| c82dd6901e | |||
| a03a41ea5f | |||
| 9f4dd771fc | |||
| 0deec92d91 | |||
| d2a6307bb0 | |||
| 66be0b1862 | |||
| 5b7ee22c02 | |||
| 35b552d6f8 |
+3
-3
@@ -7,15 +7,15 @@ import { DefaultLayout } from "@/layouts/default-layout";
|
||||
export const metadata: Metadata = {
|
||||
title: "Plane | Simple, extensible, open-source project management tool.",
|
||||
description:
|
||||
"Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.",
|
||||
"Open-source project management tool to manage work items, sprints, and product roadmaps with peace of mind.",
|
||||
openGraph: {
|
||||
title: "Plane | Simple, extensible, open-source project management tool.",
|
||||
description:
|
||||
"Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.",
|
||||
"Open-source project management tool to manage work items, sprints, and product roadmaps with peace of mind.",
|
||||
url: "https://plane.so/",
|
||||
},
|
||||
keywords:
|
||||
"software development, customer feedback, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration",
|
||||
"software development, customer feedback, software, accelerate, code management, release management, project management, work items tracking, agile, scrum, kanban, collaboration",
|
||||
twitter: {
|
||||
site: "@planepowers",
|
||||
},
|
||||
|
||||
+2
-2
@@ -19,13 +19,13 @@
|
||||
"@plane/ui": "*",
|
||||
"@plane/utils": "*",
|
||||
"@plane/services": "*",
|
||||
"@sentry/nextjs": "^8.32.0",
|
||||
"@sentry/nextjs": "^8.54.0",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@types/lodash": "^4.17.0",
|
||||
"autoprefixer": "10.4.14",
|
||||
"axios": "^1.7.9",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.356.0",
|
||||
"lucide-react": "^0.469.0",
|
||||
"mobx": "^6.12.0",
|
||||
"mobx-react": "^9.1.1",
|
||||
"next": "^14.2.20",
|
||||
|
||||
@@ -28,7 +28,7 @@ from plane.db.models import (
|
||||
Workspace,
|
||||
UserFavorite,
|
||||
)
|
||||
from plane.bgtasks.webhook_task import model_activity
|
||||
from plane.bgtasks.webhook_task import model_activity, webhook_activity
|
||||
from .base import BaseAPIView
|
||||
|
||||
|
||||
@@ -326,6 +326,19 @@ class ProjectAPIEndpoint(BaseAPIView):
|
||||
entity_type="project", entity_identifier=pk, project_id=pk
|
||||
).delete()
|
||||
project.delete()
|
||||
webhook_activity.delay(
|
||||
event="project",
|
||||
verb="deleted",
|
||||
field=None,
|
||||
old_value=None,
|
||||
new_value=None,
|
||||
actor_id=request.user.id,
|
||||
slug=slug,
|
||||
current_site=request.META.get("HTTP_ORIGIN"),
|
||||
event_id=project.id,
|
||||
old_identifier=None,
|
||||
new_identifier=None,
|
||||
)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
|
||||
@@ -90,17 +90,7 @@ class ProjectLiteSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class ProjectListSerializer(DynamicBaseSerializer):
|
||||
total_issues = serializers.IntegerField(read_only=True)
|
||||
archived_issues = serializers.IntegerField(read_only=True)
|
||||
archived_sub_issues = serializers.IntegerField(read_only=True)
|
||||
draft_issues = serializers.IntegerField(read_only=True)
|
||||
draft_sub_issues = serializers.IntegerField(read_only=True)
|
||||
sub_issues = serializers.IntegerField(read_only=True)
|
||||
is_favorite = serializers.BooleanField(read_only=True)
|
||||
total_members = serializers.IntegerField(read_only=True)
|
||||
total_cycles = serializers.IntegerField(read_only=True)
|
||||
total_modules = serializers.IntegerField(read_only=True)
|
||||
is_member = serializers.BooleanField(read_only=True)
|
||||
sort_order = serializers.FloatField(read_only=True)
|
||||
member_role = serializers.IntegerField(read_only=True)
|
||||
anchor = serializers.CharField(read_only=True)
|
||||
@@ -113,14 +103,9 @@ class ProjectListSerializer(DynamicBaseSerializer):
|
||||
if project_members is not None:
|
||||
# Filter members by the project ID
|
||||
return [
|
||||
{
|
||||
"id": member.id,
|
||||
"member_id": member.member_id,
|
||||
"member__display_name": member.member.display_name,
|
||||
"member__avatar": member.member.avatar,
|
||||
"member__avatar_url": member.member.avatar_url,
|
||||
}
|
||||
member.member_id
|
||||
for member in project_members
|
||||
if member.is_active and not member.member.is_bot
|
||||
]
|
||||
return []
|
||||
|
||||
@@ -134,10 +119,6 @@ class ProjectDetailSerializer(BaseSerializer):
|
||||
default_assignee = UserLiteSerializer(read_only=True)
|
||||
project_lead = UserLiteSerializer(read_only=True)
|
||||
is_favorite = serializers.BooleanField(read_only=True)
|
||||
total_members = serializers.IntegerField(read_only=True)
|
||||
total_cycles = serializers.IntegerField(read_only=True)
|
||||
total_modules = serializers.IntegerField(read_only=True)
|
||||
is_member = serializers.BooleanField(read_only=True)
|
||||
sort_order = serializers.FloatField(read_only=True)
|
||||
member_role = serializers.IntegerField(read_only=True)
|
||||
anchor = serializers.CharField(read_only=True)
|
||||
|
||||
@@ -7,6 +7,7 @@ from plane.app.views import (
|
||||
SavedAnalyticEndpoint,
|
||||
ExportAnalyticsEndpoint,
|
||||
DefaultAnalyticsEndpoint,
|
||||
ProjectStatsEndpoint,
|
||||
)
|
||||
|
||||
|
||||
@@ -43,4 +44,9 @@ urlpatterns = [
|
||||
DefaultAnalyticsEndpoint.as_view(),
|
||||
name="default-analytics",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/project-stats/",
|
||||
ProjectStatsEndpoint.as_view(),
|
||||
name="project-analytics",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -23,6 +23,11 @@ urlpatterns = [
|
||||
ProjectViewSet.as_view({"get": "list", "post": "create"}),
|
||||
name="project",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/details/",
|
||||
ProjectViewSet.as_view({"get": "list_detail"}),
|
||||
name="project",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:pk>/",
|
||||
ProjectViewSet.as_view(
|
||||
|
||||
@@ -190,6 +190,7 @@ from .analytic.base import (
|
||||
SavedAnalyticEndpoint,
|
||||
ExportAnalyticsEndpoint,
|
||||
DefaultAnalyticsEndpoint,
|
||||
ProjectStatsEndpoint,
|
||||
)
|
||||
|
||||
from .notification.base import (
|
||||
|
||||
@@ -3,7 +3,7 @@ from django.db.models import Count, F, Sum, Q
|
||||
from django.db.models.functions import ExtractMonth
|
||||
from django.utils import timezone
|
||||
from django.db.models.functions import Concat
|
||||
from django.db.models import Case, When, Value
|
||||
from django.db.models import Case, When, Value, OuterRef, Func
|
||||
from django.db import models
|
||||
|
||||
# Third party imports
|
||||
@@ -15,7 +15,16 @@ from plane.app.permissions import WorkSpaceAdminPermission
|
||||
from plane.app.serializers import AnalyticViewSerializer
|
||||
from plane.app.views.base import BaseAPIView, BaseViewSet
|
||||
from plane.bgtasks.analytic_plot_export import analytic_export_task
|
||||
from plane.db.models import AnalyticView, Issue, Workspace
|
||||
from plane.db.models import (
|
||||
AnalyticView,
|
||||
Issue,
|
||||
Workspace,
|
||||
Project,
|
||||
ProjectMember,
|
||||
Cycle,
|
||||
Module,
|
||||
)
|
||||
|
||||
from plane.utils.analytics_plot import build_graph_plot
|
||||
from plane.utils.issue_filters import issue_filters
|
||||
from plane.app.permissions import allow_permission, ROLE
|
||||
@@ -441,3 +450,74 @@ class DefaultAnalyticsEndpoint(BaseAPIView):
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
class ProjectStatsEndpoint(BaseAPIView):
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
|
||||
def get(self, request, slug):
|
||||
fields = request.GET.get("fields", "").split(",")
|
||||
project_ids = request.GET.get("project_ids", "")
|
||||
|
||||
valid_fields = {
|
||||
"total_issues",
|
||||
"completed_issues",
|
||||
"total_members",
|
||||
"total_cycles",
|
||||
"total_modules",
|
||||
}
|
||||
requested_fields = set(filter(None, fields)) & valid_fields
|
||||
|
||||
if not requested_fields:
|
||||
requested_fields = valid_fields
|
||||
|
||||
projects = Project.objects.filter(workspace__slug=slug)
|
||||
if project_ids:
|
||||
projects = projects.filter(id__in=project_ids.split(","))
|
||||
|
||||
annotations = {}
|
||||
if "total_issues" in requested_fields:
|
||||
annotations["total_issues"] = (
|
||||
Issue.issue_objects.filter(project_id=OuterRef("pk"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
|
||||
if "completed_issues" in requested_fields:
|
||||
annotations["completed_issues"] = (
|
||||
Issue.issue_objects.filter(
|
||||
project_id=OuterRef("pk"), state__group="completed"
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
|
||||
if "total_cycles" in requested_fields:
|
||||
annotations["total_cycles"] = (
|
||||
Cycle.objects.filter(project_id=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
|
||||
if "total_modules" in requested_fields:
|
||||
annotations["total_modules"] = (
|
||||
Module.objects.filter(project_id=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
|
||||
if "total_members" in requested_fields:
|
||||
annotations["total_members"] = (
|
||||
ProjectMember.objects.filter(
|
||||
project_id=OuterRef("id"), member__is_bot=False, is_active=True
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
|
||||
projects = projects.annotate(**annotations).values("id", *requested_fields)
|
||||
return Response(projects, status=status.HTTP_200_OK)
|
||||
|
||||
@@ -6,7 +6,7 @@ import json
|
||||
|
||||
# Django imports
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Exists, F, Func, OuterRef, Prefetch, Q, Subquery
|
||||
from django.db.models import Exists, F, OuterRef, Prefetch, Q, Subquery
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
|
||||
# Third Party imports
|
||||
@@ -25,12 +25,9 @@ from plane.app.serializers import (
|
||||
from plane.app.permissions import ProjectMemberPermission, allow_permission, ROLE
|
||||
from plane.db.models import (
|
||||
UserFavorite,
|
||||
Cycle,
|
||||
Intake,
|
||||
DeployBoard,
|
||||
IssueUserProperty,
|
||||
Issue,
|
||||
Module,
|
||||
Project,
|
||||
ProjectIdentifier,
|
||||
ProjectMember,
|
||||
@@ -39,7 +36,7 @@ from plane.db.models import (
|
||||
WorkspaceMember,
|
||||
)
|
||||
from plane.utils.cache import cache_response
|
||||
from plane.bgtasks.webhook_task import model_activity
|
||||
from plane.bgtasks.webhook_task import model_activity, webhook_activity
|
||||
from plane.bgtasks.recent_visited_task import recent_visited_task
|
||||
from plane.utils.exception_logger import log_exception
|
||||
|
||||
@@ -73,36 +70,6 @@ class ProjectViewSet(BaseViewSet):
|
||||
)
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
is_member=Exists(
|
||||
ProjectMember.objects.filter(
|
||||
member=self.request.user,
|
||||
project_id=OuterRef("pk"),
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
is_active=True,
|
||||
)
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
total_members=ProjectMember.objects.filter(
|
||||
project_id=OuterRef("id"), member__is_bot=False, is_active=True
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
total_cycles=Cycle.objects.filter(project_id=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
total_modules=Module.objects.filter(project_id=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
member_role=ProjectMember.objects.filter(
|
||||
project_id=OuterRef("pk"),
|
||||
@@ -133,7 +100,7 @@ class ProjectViewSet(BaseViewSet):
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
def list(self, request, slug):
|
||||
def list_detail(self, request, slug):
|
||||
fields = [field for field in request.GET.get("fields", "").split(",") if field]
|
||||
projects = self.get_queryset().order_by("sort_order", "name")
|
||||
if WorkspaceMember.objects.filter(
|
||||
@@ -170,6 +137,73 @@ class ProjectViewSet(BaseViewSet):
|
||||
).data
|
||||
return Response(projects, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
def list(self, request, slug):
|
||||
sort_order = ProjectMember.objects.filter(
|
||||
member=self.request.user,
|
||||
project_id=OuterRef("pk"),
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
is_active=True,
|
||||
).values("sort_order")
|
||||
|
||||
projects = (
|
||||
Project.objects.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.select_related(
|
||||
"workspace", "workspace__owner", "default_assignee", "project_lead"
|
||||
)
|
||||
.annotate(
|
||||
member_role=ProjectMember.objects.filter(
|
||||
project_id=OuterRef("pk"),
|
||||
member_id=self.request.user.id,
|
||||
is_active=True,
|
||||
).values("role")
|
||||
)
|
||||
.annotate(inbox_view=F("intake_view"))
|
||||
.annotate(sort_order=Subquery(sort_order))
|
||||
.distinct()
|
||||
).values(
|
||||
"id",
|
||||
"name",
|
||||
"identifier",
|
||||
"sort_order",
|
||||
"logo_props",
|
||||
"member_role",
|
||||
"archived_at",
|
||||
"workspace",
|
||||
"cycle_view",
|
||||
"issue_views_view",
|
||||
"module_view",
|
||||
"page_view",
|
||||
"inbox_view",
|
||||
"project_lead",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
)
|
||||
|
||||
if WorkspaceMember.objects.filter(
|
||||
member=request.user, workspace__slug=slug, is_active=True, role=5
|
||||
).exists():
|
||||
projects = projects.filter(
|
||||
project_projectmember__member=self.request.user,
|
||||
project_projectmember__is_active=True,
|
||||
)
|
||||
|
||||
if WorkspaceMember.objects.filter(
|
||||
member=request.user, workspace__slug=slug, is_active=True, role=15
|
||||
).exists():
|
||||
projects = projects.filter(
|
||||
Q(
|
||||
project_projectmember__member=self.request.user,
|
||||
project_projectmember__is_active=True,
|
||||
)
|
||||
| Q(network=2)
|
||||
)
|
||||
return Response(projects, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@@ -182,58 +216,6 @@ class ProjectViewSet(BaseViewSet):
|
||||
)
|
||||
.filter(archived_at__isnull=True)
|
||||
.filter(pk=pk)
|
||||
.annotate(
|
||||
total_issues=Issue.issue_objects.filter(
|
||||
project_id=self.kwargs.get("pk")
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
sub_issues=Issue.issue_objects.filter(
|
||||
project_id=self.kwargs.get("pk"), parent__isnull=False
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
archived_issues=Issue.objects.filter(
|
||||
project_id=self.kwargs.get("pk"), archived_at__isnull=False
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
archived_sub_issues=Issue.objects.filter(
|
||||
project_id=self.kwargs.get("pk"),
|
||||
archived_at__isnull=False,
|
||||
parent__isnull=False,
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
draft_issues=Issue.objects.filter(
|
||||
project_id=self.kwargs.get("pk"), is_draft=True
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
draft_sub_issues=Issue.objects.filter(
|
||||
project_id=self.kwargs.get("pk"),
|
||||
is_draft=True,
|
||||
parent__isnull=False,
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
).first()
|
||||
|
||||
if project is None:
|
||||
@@ -462,7 +444,19 @@ class ProjectViewSet(BaseViewSet):
|
||||
):
|
||||
project = Project.objects.get(pk=pk)
|
||||
project.delete()
|
||||
|
||||
webhook_activity.delay(
|
||||
event="project",
|
||||
verb="deleted",
|
||||
field=None,
|
||||
old_value=None,
|
||||
new_value=None,
|
||||
actor_id=request.user.id,
|
||||
slug=slug,
|
||||
current_site=request.META.get("HTTP_ORIGIN"),
|
||||
event_id=project.id,
|
||||
old_identifier=None,
|
||||
new_identifier=None,
|
||||
)
|
||||
# Delete the project members
|
||||
DeployBoard.objects.filter(project_id=pk, workspace__slug=slug).delete()
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ class WebhookLogsEndpoint(BaseAPIView):
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE")
|
||||
def get(self, request, slug, webhook_id):
|
||||
webhook_logs = WebhookLog.objects.filter(
|
||||
workspace__slug=slug, webhook_id=webhook_id
|
||||
workspace__slug=slug, webhook=webhook_id
|
||||
)
|
||||
serializer = WebhookLogSerializer(webhook_logs, many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@@ -738,8 +738,10 @@ def delete_comment_activity(
|
||||
issue_activities,
|
||||
epoch,
|
||||
):
|
||||
requested_data = json.loads(requested_data) if requested_data is not None else None
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_comment_id=requested_data.get("comment_id", None),
|
||||
issue_id=issue_id,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
|
||||
@@ -136,7 +136,7 @@ def webhook_task(self, webhook, slug, event, event_data, action, current_site):
|
||||
# Log the webhook request
|
||||
WebhookLog.objects.create(
|
||||
workspace_id=str(webhook.workspace_id),
|
||||
webhook_id=str(webhook.id),
|
||||
webhook=str(webhook.id),
|
||||
event_type=str(event),
|
||||
request_method=str(action),
|
||||
request_headers=str(headers),
|
||||
@@ -153,7 +153,7 @@ def webhook_task(self, webhook, slug, event, event_data, action, current_site):
|
||||
# Log the failed webhook request
|
||||
WebhookLog.objects.create(
|
||||
workspace_id=str(webhook.workspace_id),
|
||||
webhook_id=str(webhook.id),
|
||||
webhook=str(webhook.id),
|
||||
event_type=str(event),
|
||||
request_method=str(action),
|
||||
request_headers=str(headers),
|
||||
@@ -304,7 +304,7 @@ def webhook_send_task(
|
||||
# Log the webhook request
|
||||
WebhookLog.objects.create(
|
||||
workspace_id=str(webhook.workspace_id),
|
||||
webhook_id=str(webhook.id),
|
||||
webhook=str(webhook.id),
|
||||
event_type=str(event),
|
||||
request_method=str(action),
|
||||
request_headers=str(headers),
|
||||
@@ -319,7 +319,7 @@ def webhook_send_task(
|
||||
# Log the failed webhook request
|
||||
WebhookLog.objects.create(
|
||||
workspace_id=str(webhook.workspace_id),
|
||||
webhook_id=str(webhook.id),
|
||||
webhook=str(webhook.id),
|
||||
event_type=str(event),
|
||||
request_method=str(action),
|
||||
request_headers=str(headers),
|
||||
@@ -387,7 +387,11 @@ def webhook_activity(
|
||||
webhook=webhook.id,
|
||||
slug=slug,
|
||||
event=event,
|
||||
event_data=get_model_data(event=event, event_id=event_id),
|
||||
event_data=(
|
||||
{"id": event_id}
|
||||
if verb == "deleted"
|
||||
else get_model_data(event=event, event_id=event_id)
|
||||
),
|
||||
action=verb,
|
||||
current_site=current_site,
|
||||
activity={
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 4.2.17 on 2025-01-30 16:08
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('db', '0090_rename_dashboard_deprecateddashboard_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='issuecomment',
|
||||
name='edited_at',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='profile',
|
||||
name='is_smooth_cursor_enabled',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='userrecentvisit',
|
||||
name='entity_name',
|
||||
field=models.CharField(max_length=30),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='webhooklog',
|
||||
name='webhook',
|
||||
field=models.UUIDField(),
|
||||
)
|
||||
]
|
||||
@@ -467,6 +467,7 @@ class IssueComment(ProjectBaseModel):
|
||||
)
|
||||
external_source = models.CharField(max_length=255, null=True, blank=True)
|
||||
external_id = models.CharField(max_length=255, blank=True, null=True)
|
||||
edited_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.comment_stripped = (
|
||||
|
||||
@@ -17,7 +17,7 @@ class EntityNameEnum(models.TextChoices):
|
||||
|
||||
class UserRecentVisit(WorkspaceBaseModel):
|
||||
entity_identifier = models.UUIDField(null=True)
|
||||
entity_name = models.CharField(max_length=30, choices=EntityNameEnum.choices)
|
||||
entity_name = models.CharField(max_length=30)
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
|
||||
@@ -186,6 +186,8 @@ class Profile(TimeAuditModel):
|
||||
billing_address = models.JSONField(null=True)
|
||||
has_billing_address = models.BooleanField(default=False)
|
||||
company_name = models.CharField(max_length=255, blank=True)
|
||||
|
||||
is_smooth_cursor_enabled = models.BooleanField(default=False)
|
||||
# mobile
|
||||
is_mobile_onboarded = models.BooleanField(default=False)
|
||||
mobile_onboarding_step = models.JSONField(default=get_mobile_default_onboarding)
|
||||
|
||||
@@ -66,7 +66,7 @@ class WebhookLog(BaseModel):
|
||||
"db.Workspace", on_delete=models.CASCADE, related_name="webhook_logs"
|
||||
)
|
||||
# Associated webhook
|
||||
webhook = models.ForeignKey(Webhook, on_delete=models.CASCADE, related_name="logs")
|
||||
webhook = models.UUIDField()
|
||||
|
||||
# Basic request details
|
||||
event_type = models.CharField(max_length=255, blank=True, null=True)
|
||||
@@ -89,4 +89,4 @@ class WebhookLog(BaseModel):
|
||||
ordering = ("-created_at",)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.event_type} {str(self.webhook.url)}"
|
||||
return f"{self.event_type} {str(self.webhook)}"
|
||||
|
||||
@@ -396,6 +396,8 @@ class WorkspaceUserPreference(BaseModel):
|
||||
ANALYTICS = "analytics", "Analytics"
|
||||
CYCLES = "cycles", "Cycles"
|
||||
VIEWS = "views", "Views"
|
||||
YOUR_WORK = "your_work", "Your Work"
|
||||
|
||||
|
||||
workspace = models.ForeignKey(
|
||||
"db.Workspace",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
.next
|
||||
.turbo
|
||||
out/
|
||||
dist/
|
||||
build/
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
@@ -2,8 +2,11 @@
|
||||
import { TXAxisValues, TYAxisValues } from "@plane/types";
|
||||
|
||||
export const ANALYTICS_TABS = [
|
||||
{ key: "scope_and_demand", title: "Scope and Demand" },
|
||||
{ key: "custom", title: "Custom Analytics" },
|
||||
{
|
||||
key: "scope_and_demand",
|
||||
i18n_title: "workspace_analytics.tabs.scope_and_demand",
|
||||
},
|
||||
{ key: "custom", i18n_title: "workspace_analytics.tabs.custom" },
|
||||
];
|
||||
|
||||
export const ANALYTICS_X_AXIS_VALUES: { value: TXAxisValues; label: string }[] =
|
||||
@@ -62,7 +65,7 @@ export const ANALYTICS_Y_AXIS_VALUES: { value: TYAxisValues; label: string }[] =
|
||||
[
|
||||
{
|
||||
value: "issue_count",
|
||||
label: "Issue Count",
|
||||
label: "Work item Count",
|
||||
},
|
||||
{
|
||||
value: "estimate",
|
||||
|
||||
@@ -1,56 +1,40 @@
|
||||
// types
|
||||
import { TCycleLayoutOptions, TCycleTabOptions } from "@plane/types";
|
||||
|
||||
export const CYCLE_TABS_LIST: {
|
||||
key: TCycleTabOptions;
|
||||
name: string;
|
||||
}[] = [
|
||||
{
|
||||
key: "active",
|
||||
name: "Active",
|
||||
},
|
||||
{
|
||||
key: "all",
|
||||
name: "All",
|
||||
},
|
||||
];
|
||||
|
||||
export const CYCLE_STATUS: {
|
||||
label: string;
|
||||
i18n_label: string;
|
||||
value: "current" | "upcoming" | "completed" | "draft";
|
||||
title: string;
|
||||
i18n_title: string;
|
||||
color: string;
|
||||
textColor: string;
|
||||
bgColor: string;
|
||||
}[] = [
|
||||
{
|
||||
label: "day left",
|
||||
i18n_label: "project_cycles.status.days_left",
|
||||
value: "current",
|
||||
title: "In progress",
|
||||
i18n_title: "project_cycles.status.in_progress",
|
||||
color: "#F59E0B",
|
||||
textColor: "text-amber-500",
|
||||
bgColor: "bg-amber-50",
|
||||
},
|
||||
{
|
||||
label: "Yet to start",
|
||||
i18n_label: "project_cycles.status.yet_to_start",
|
||||
value: "upcoming",
|
||||
title: "Yet to start",
|
||||
i18n_title: "project_cycles.status.yet_to_start",
|
||||
color: "#3F76FF",
|
||||
textColor: "text-blue-500",
|
||||
bgColor: "bg-indigo-50",
|
||||
},
|
||||
{
|
||||
label: "Completed",
|
||||
i18n_label: "project_cycles.status.completed",
|
||||
value: "completed",
|
||||
title: "Completed",
|
||||
i18n_title: "project_cycles.status.completed",
|
||||
color: "#16A34A",
|
||||
textColor: "text-green-600",
|
||||
bgColor: "bg-green-50",
|
||||
},
|
||||
{
|
||||
label: "Draft",
|
||||
i18n_label: "project_cycles.status.draft",
|
||||
value: "draft",
|
||||
title: "Draft",
|
||||
i18n_title: "project_cycles.status.draft",
|
||||
color: "#525252",
|
||||
textColor: "text-custom-text-300",
|
||||
bgColor: "bg-custom-background-90",
|
||||
@@ -0,0 +1,92 @@
|
||||
// types
|
||||
import { TIssuesListTypes } from "@plane/types";
|
||||
|
||||
export enum EDurationFilters {
|
||||
NONE = "none",
|
||||
TODAY = "today",
|
||||
THIS_WEEK = "this_week",
|
||||
THIS_MONTH = "this_month",
|
||||
THIS_YEAR = "this_year",
|
||||
CUSTOM = "custom",
|
||||
}
|
||||
|
||||
// filter duration options
|
||||
export const DURATION_FILTER_OPTIONS: {
|
||||
key: EDurationFilters;
|
||||
label: string;
|
||||
}[] = [
|
||||
{
|
||||
key: EDurationFilters.NONE,
|
||||
label: "All time",
|
||||
},
|
||||
{
|
||||
key: EDurationFilters.TODAY,
|
||||
label: "Due today",
|
||||
},
|
||||
{
|
||||
key: EDurationFilters.THIS_WEEK,
|
||||
label: "Due this week",
|
||||
},
|
||||
{
|
||||
key: EDurationFilters.THIS_MONTH,
|
||||
label: "Due this month",
|
||||
},
|
||||
{
|
||||
key: EDurationFilters.THIS_YEAR,
|
||||
label: "Due this year",
|
||||
},
|
||||
{
|
||||
key: EDurationFilters.CUSTOM,
|
||||
label: "Custom",
|
||||
},
|
||||
];
|
||||
|
||||
// random background colors for project cards
|
||||
export const PROJECT_BACKGROUND_COLORS = [
|
||||
"bg-gray-500/20",
|
||||
"bg-green-500/20",
|
||||
"bg-red-500/20",
|
||||
"bg-orange-500/20",
|
||||
"bg-blue-500/20",
|
||||
"bg-yellow-500/20",
|
||||
"bg-pink-500/20",
|
||||
"bg-purple-500/20",
|
||||
];
|
||||
|
||||
// assigned and created issues widgets tabs list
|
||||
export const FILTERED_ISSUES_TABS_LIST: {
|
||||
key: TIssuesListTypes;
|
||||
label: string;
|
||||
}[] = [
|
||||
{
|
||||
key: "upcoming",
|
||||
label: "Upcoming",
|
||||
},
|
||||
{
|
||||
key: "overdue",
|
||||
label: "Overdue",
|
||||
},
|
||||
{
|
||||
key: "completed",
|
||||
label: "Marked completed",
|
||||
},
|
||||
];
|
||||
|
||||
// assigned and created issues widgets tabs list
|
||||
export const UNFILTERED_ISSUES_TABS_LIST: {
|
||||
key: TIssuesListTypes;
|
||||
label: string;
|
||||
}[] = [
|
||||
{
|
||||
key: "pending",
|
||||
label: "Pending",
|
||||
},
|
||||
{
|
||||
key: "completed",
|
||||
label: "Marked completed",
|
||||
},
|
||||
];
|
||||
|
||||
export type TLinkOptions = {
|
||||
userId: string | undefined;
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
export enum E_ARCHIVE_ERROR_CODES {
|
||||
"INVALID_ARCHIVE_STATE_GROUP" = 4091,
|
||||
"INVALID_ISSUE_START_DATE" = 4101,
|
||||
"INVALID_ISSUE_TARGET_DATE" = 4102,
|
||||
}
|
||||
@@ -104,7 +104,10 @@ export const getIssueEventPayload = (props: IssueEventProps) => {
|
||||
module_id: payload.module_id,
|
||||
archived_at: payload.archived_at,
|
||||
state: payload.state,
|
||||
view_id: path?.includes("workspace-views") || path?.includes("views") ? path.split("/").pop() : "",
|
||||
view_id:
|
||||
path?.includes("workspace-views") || path?.includes("views")
|
||||
? path.split("/").pop()
|
||||
: "",
|
||||
};
|
||||
|
||||
if (eventName === ISSUE_UPDATED) {
|
||||
@@ -166,12 +169,12 @@ export const MODULE_LINK_CREATED = "Module link created";
|
||||
export const MODULE_LINK_UPDATED = "Module link updated";
|
||||
export const MODULE_LINK_DELETED = "Module link deleted";
|
||||
// Issue Events
|
||||
export const ISSUE_CREATED = "Issue created";
|
||||
export const ISSUE_UPDATED = "Issue updated";
|
||||
export const ISSUE_DELETED = "Issue deleted";
|
||||
export const ISSUE_ARCHIVED = "Issue archived";
|
||||
export const ISSUE_RESTORED = "Issue restored";
|
||||
export const ISSUE_OPENED = "Issue opened";
|
||||
export const ISSUE_CREATED = "Work item created";
|
||||
export const ISSUE_UPDATED = "Work item updated";
|
||||
export const ISSUE_DELETED = "Work item deleted";
|
||||
export const ISSUE_ARCHIVED = "Work item archived";
|
||||
export const ISSUE_RESTORED = "Work item restored";
|
||||
export const ISSUE_OPENED = "Work item opened";
|
||||
// Project State Events
|
||||
export const STATE_CREATED = "State created";
|
||||
export const STATE_UPDATED = "State updated";
|
||||
@@ -1 +0,0 @@
|
||||
export const SIDEBAR_CLICKED = "Sidenav clicked";
|
||||
@@ -2,3 +2,56 @@ export enum E_SORT_ORDER {
|
||||
ASC = "asc",
|
||||
DESC = "desc",
|
||||
}
|
||||
export const DATE_AFTER_FILTER_OPTIONS = [
|
||||
{
|
||||
name: "1 week from now",
|
||||
value: "1_weeks;after;fromnow",
|
||||
},
|
||||
{
|
||||
name: "2 weeks from now",
|
||||
value: "2_weeks;after;fromnow",
|
||||
},
|
||||
{
|
||||
name: "1 month from now",
|
||||
value: "1_months;after;fromnow",
|
||||
},
|
||||
{
|
||||
name: "2 months from now",
|
||||
value: "2_months;after;fromnow",
|
||||
},
|
||||
];
|
||||
|
||||
export const DATE_BEFORE_FILTER_OPTIONS = [
|
||||
{
|
||||
name: "1 week ago",
|
||||
value: "1_weeks;before;fromnow",
|
||||
},
|
||||
{
|
||||
name: "2 weeks ago",
|
||||
value: "2_weeks;before;fromnow",
|
||||
},
|
||||
{
|
||||
name: "1 month ago",
|
||||
i18n_name: "date_filters.1_month_ago",
|
||||
value: "1_months;before;fromnow",
|
||||
},
|
||||
];
|
||||
|
||||
export const PROJECT_CREATED_AT_FILTER_OPTIONS = [
|
||||
{
|
||||
name: "Today",
|
||||
value: "today;custom;custom",
|
||||
},
|
||||
{
|
||||
name: "Yesterday",
|
||||
value: "yesterday;custom;custom",
|
||||
},
|
||||
{
|
||||
name: "Last 7 days",
|
||||
value: "last_7_days;custom;custom",
|
||||
},
|
||||
{
|
||||
name: "Last 30 days",
|
||||
value: "last_30_days;custom;custom",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { TInboxDuplicateIssueDetails, TIssue } from "@plane/types";
|
||||
|
||||
export enum EInboxIssueCurrentTab {
|
||||
OPEN = "open",
|
||||
CLOSED = "closed",
|
||||
}
|
||||
|
||||
export enum EInboxIssueStatus {
|
||||
PENDING = -2,
|
||||
DECLINED = -1,
|
||||
SNOOZED = 0,
|
||||
ACCEPTED = 1,
|
||||
DUPLICATE = 2,
|
||||
}
|
||||
|
||||
export type TInboxIssueCurrentTab = EInboxIssueCurrentTab;
|
||||
export type TInboxIssueStatus = EInboxIssueStatus;
|
||||
export type TInboxIssue = {
|
||||
id: string;
|
||||
status: TInboxIssueStatus;
|
||||
snoozed_till: Date | null;
|
||||
duplicate_to: string | undefined;
|
||||
source: string;
|
||||
issue: TIssue;
|
||||
created_by: string;
|
||||
duplicate_issue_detail: TInboxDuplicateIssueDetails | undefined;
|
||||
};
|
||||
|
||||
export const INBOX_STATUS: {
|
||||
key: string;
|
||||
status: TInboxIssueStatus;
|
||||
i18n_title: string;
|
||||
i18n_description: () => string;
|
||||
}[] = [
|
||||
{
|
||||
key: "pending",
|
||||
i18n_title: "inbox_issue.status.pending.title",
|
||||
status: EInboxIssueStatus.PENDING,
|
||||
i18n_description: () => `inbox_issue.status.pending.description`,
|
||||
},
|
||||
{
|
||||
key: "declined",
|
||||
i18n_title: "inbox_issue.status.declined.title",
|
||||
status: EInboxIssueStatus.DECLINED,
|
||||
i18n_description: () => `inbox_issue.status.declined.description`,
|
||||
},
|
||||
{
|
||||
key: "snoozed",
|
||||
i18n_title: "inbox_issue.status.snoozed.title",
|
||||
status: EInboxIssueStatus.SNOOZED,
|
||||
i18n_description: () => `inbox_issue.status.snoozed.description`,
|
||||
},
|
||||
{
|
||||
key: "accepted",
|
||||
i18n_title: "inbox_issue.status.accepted.title",
|
||||
status: EInboxIssueStatus.ACCEPTED,
|
||||
i18n_description: () => `inbox_issue.status.accepted.description`,
|
||||
},
|
||||
{
|
||||
key: "duplicate",
|
||||
i18n_title: "inbox_issue.status.duplicate.title",
|
||||
status: EInboxIssueStatus.DUPLICATE,
|
||||
i18n_description: () => `inbox_issue.status.duplicate.description`,
|
||||
},
|
||||
];
|
||||
|
||||
export const INBOX_ISSUE_ORDER_BY_OPTIONS = [
|
||||
{
|
||||
key: "issue__created_at",
|
||||
i18n_label: "inbox_issue.order_by.created_at",
|
||||
},
|
||||
{
|
||||
key: "issue__updated_at",
|
||||
i18n_label: "inbox_issue.order_by.updated_at",
|
||||
},
|
||||
{
|
||||
key: "issue__sequence_id",
|
||||
i18n_label: "inbox_issue.order_by.id",
|
||||
},
|
||||
];
|
||||
|
||||
export const INBOX_ISSUE_SORT_BY_OPTIONS = [
|
||||
{
|
||||
key: "asc",
|
||||
i18n_label: "common.sort.asc",
|
||||
},
|
||||
{
|
||||
key: "desc",
|
||||
i18n_label: "common.sort.desc",
|
||||
},
|
||||
];
|
||||
@@ -2,15 +2,29 @@ export * from "./ai";
|
||||
export * from "./analytics";
|
||||
export * from "./auth";
|
||||
export * from "./endpoints";
|
||||
export * from "./event";
|
||||
export * from "./file";
|
||||
export * from "./filter";
|
||||
export * from "./graph";
|
||||
export * from "./instance";
|
||||
export * from "./issue";
|
||||
export * from "./metadata";
|
||||
export * from "./notification";
|
||||
export * from "./state";
|
||||
export * from "./swr";
|
||||
export * from "./tab-indices";
|
||||
export * from "./user";
|
||||
export * from "./workspace";
|
||||
export * from "./stickies";
|
||||
export * from "./cycle";
|
||||
export * from "./module";
|
||||
export * from "./project";
|
||||
export * from "./views";
|
||||
export * from "./themes";
|
||||
export * from "./inbox";
|
||||
export * from "./profile";
|
||||
export * from "./workspace-drafts";
|
||||
export * from "./label";
|
||||
export * from "./event-tracker";
|
||||
export * from "./spreadsheet";
|
||||
export * from "./dashboard";
|
||||
export * from "./page";
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
import { List, Kanban } from "lucide-react";
|
||||
|
||||
export const ALL_ISSUES = "All Issues";
|
||||
|
||||
export type TIssuePriorities = "urgent" | "high" | "medium" | "low" | "none";
|
||||
|
||||
export type TIssueFilterKeys = "priority" | "state" | "labels";
|
||||
|
||||
export type TIssueLayout =
|
||||
| "list"
|
||||
| "kanban"
|
||||
| "calendar"
|
||||
| "spreadsheet"
|
||||
| "gantt";
|
||||
|
||||
export type TIssueFilterPriorityObject = {
|
||||
key: TIssuePriorities;
|
||||
title: string;
|
||||
className: string;
|
||||
icon: string;
|
||||
};
|
||||
|
||||
export enum EIssueGroupByToServerOptions {
|
||||
"state" = "state_id",
|
||||
"priority" = "priority",
|
||||
"labels" = "labels__id",
|
||||
"state_detail.group" = "state__group",
|
||||
"assignees" = "assignees__id",
|
||||
"cycle" = "cycle_id",
|
||||
"module" = "issue_module__module_id",
|
||||
"target_date" = "target_date",
|
||||
"project" = "project_id",
|
||||
"created_by" = "created_by",
|
||||
"team_project" = "project_id",
|
||||
}
|
||||
|
||||
export enum EIssueGroupBYServerToProperty {
|
||||
"state_id" = "state_id",
|
||||
"priority" = "priority",
|
||||
"labels__id" = "label_ids",
|
||||
"state__group" = "state__group",
|
||||
"assignees__id" = "assignee_ids",
|
||||
"cycle_id" = "cycle_id",
|
||||
"issue_module__module_id" = "module_ids",
|
||||
"target_date" = "target_date",
|
||||
"project_id" = "project_id",
|
||||
"created_by" = "created_by",
|
||||
}
|
||||
|
||||
export enum EServerGroupByToFilterOptions {
|
||||
"state_id" = "state",
|
||||
"priority" = "priority",
|
||||
"labels__id" = "labels",
|
||||
"state__group" = "state_group",
|
||||
"assignees__id" = "assignees",
|
||||
"cycle_id" = "cycle",
|
||||
"issue_module__module_id" = "module",
|
||||
"target_date" = "target_date",
|
||||
"project_id" = "project",
|
||||
"created_by" = "created_by",
|
||||
}
|
||||
|
||||
export enum EIssueServiceType {
|
||||
ISSUES = "issues",
|
||||
EPICS = "epics",
|
||||
}
|
||||
|
||||
export enum EIssueLayoutTypes {
|
||||
LIST = "list",
|
||||
KANBAN = "kanban",
|
||||
CALENDAR = "calendar",
|
||||
GANTT = "gantt_chart",
|
||||
SPREADSHEET = "spreadsheet",
|
||||
}
|
||||
|
||||
export enum EIssuesStoreType {
|
||||
GLOBAL = "GLOBAL",
|
||||
PROFILE = "PROFILE",
|
||||
TEAM = "TEAM",
|
||||
PROJECT = "PROJECT",
|
||||
CYCLE = "CYCLE",
|
||||
MODULE = "MODULE",
|
||||
TEAM_VIEW = "TEAM_VIEW",
|
||||
PROJECT_VIEW = "PROJECT_VIEW",
|
||||
ARCHIVED = "ARCHIVED",
|
||||
DRAFT = "DRAFT",
|
||||
DEFAULT = "DEFAULT",
|
||||
WORKSPACE_DRAFT = "WORKSPACE_DRAFT",
|
||||
EPIC = "EPIC",
|
||||
}
|
||||
|
||||
export enum EIssueFilterType {
|
||||
FILTERS = "filters",
|
||||
DISPLAY_FILTERS = "display_filters",
|
||||
DISPLAY_PROPERTIES = "display_properties",
|
||||
KANBAN_FILTERS = "kanban_filters",
|
||||
}
|
||||
|
||||
export enum EIssueCommentAccessSpecifier {
|
||||
EXTERNAL = "EXTERNAL",
|
||||
INTERNAL = "INTERNAL",
|
||||
}
|
||||
|
||||
export enum EIssueListRow {
|
||||
HEADER = "HEADER",
|
||||
ISSUE = "ISSUE",
|
||||
NO_ISSUES = "NO_ISSUES",
|
||||
QUICK_ADD = "QUICK_ADD",
|
||||
}
|
||||
|
||||
export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
[key in TIssueLayout]: Record<"filters", TIssueFilterKeys[]>;
|
||||
} = {
|
||||
list: {
|
||||
filters: ["priority", "state", "labels"],
|
||||
},
|
||||
kanban: {
|
||||
filters: ["priority", "state", "labels"],
|
||||
},
|
||||
calendar: {
|
||||
filters: ["priority", "state", "labels"],
|
||||
},
|
||||
spreadsheet: {
|
||||
filters: ["priority", "state", "labels"],
|
||||
},
|
||||
gantt: {
|
||||
filters: ["priority", "state", "labels"],
|
||||
},
|
||||
};
|
||||
|
||||
export const ISSUE_PRIORITIES: {
|
||||
key: TIssuePriorities;
|
||||
title: string;
|
||||
}[] = [
|
||||
{ key: "urgent", title: "Urgent" },
|
||||
{ key: "high", title: "High" },
|
||||
{ key: "medium", title: "Medium" },
|
||||
{ key: "low", title: "Low" },
|
||||
{ key: "none", title: "None" },
|
||||
];
|
||||
|
||||
export const ISSUE_PRIORITY_FILTERS: TIssueFilterPriorityObject[] = [
|
||||
{
|
||||
key: "urgent",
|
||||
title: "Urgent",
|
||||
className: "bg-red-500 border-red-500 text-white",
|
||||
icon: "error",
|
||||
},
|
||||
{
|
||||
key: "high",
|
||||
title: "High",
|
||||
className: "text-orange-500 border-custom-border-300",
|
||||
icon: "signal_cellular_alt",
|
||||
},
|
||||
{
|
||||
key: "medium",
|
||||
title: "Medium",
|
||||
className: "text-yellow-500 border-custom-border-300",
|
||||
icon: "signal_cellular_alt_2_bar",
|
||||
},
|
||||
{
|
||||
key: "low",
|
||||
title: "Low",
|
||||
className: "text-green-500 border-custom-border-300",
|
||||
icon: "signal_cellular_alt_1_bar",
|
||||
},
|
||||
{
|
||||
key: "none",
|
||||
title: "None",
|
||||
className: "text-gray-500 border-custom-border-300",
|
||||
icon: "block",
|
||||
},
|
||||
];
|
||||
|
||||
export const SITES_ISSUE_LAYOUTS: {
|
||||
key: TIssueLayout;
|
||||
title: string;
|
||||
icon: any;
|
||||
}[] = [
|
||||
{ key: "list", title: "List", icon: List },
|
||||
{ key: "kanban", title: "Kanban", icon: Kanban },
|
||||
// { key: "calendar", title: "Calendar", icon: Calendar },
|
||||
// { key: "spreadsheet", title: "Spreadsheet", icon: Sheet },
|
||||
// { key: "gantt", title: "Gantt chart", icon: GanttChartSquare },
|
||||
];
|
||||
@@ -0,0 +1,217 @@
|
||||
import {
|
||||
TIssueGroupByOptions,
|
||||
TIssueOrderByOptions,
|
||||
IIssueDisplayProperties,
|
||||
} from "@plane/types";
|
||||
|
||||
export const ALL_ISSUES = "All Issues";
|
||||
|
||||
export type TIssuePriorities = "urgent" | "high" | "medium" | "low" | "none";
|
||||
|
||||
export type TIssueFilterPriorityObject = {
|
||||
key: TIssuePriorities;
|
||||
titleTranslationKey: string;
|
||||
className: string;
|
||||
icon: string;
|
||||
};
|
||||
|
||||
export enum EIssueGroupByToServerOptions {
|
||||
"state" = "state_id",
|
||||
"priority" = "priority",
|
||||
"labels" = "labels__id",
|
||||
"state_detail.group" = "state__group",
|
||||
"assignees" = "assignees__id",
|
||||
"cycle" = "cycle_id",
|
||||
"module" = "issue_module__module_id",
|
||||
"target_date" = "target_date",
|
||||
"project" = "project_id",
|
||||
"created_by" = "created_by",
|
||||
"team_project" = "project_id",
|
||||
}
|
||||
|
||||
export enum EIssueGroupBYServerToProperty {
|
||||
"state_id" = "state_id",
|
||||
"priority" = "priority",
|
||||
"labels__id" = "label_ids",
|
||||
"state__group" = "state__group",
|
||||
"assignees__id" = "assignee_ids",
|
||||
"cycle_id" = "cycle_id",
|
||||
"issue_module__module_id" = "module_ids",
|
||||
"target_date" = "target_date",
|
||||
"project_id" = "project_id",
|
||||
"created_by" = "created_by",
|
||||
}
|
||||
|
||||
export enum EIssueServiceType {
|
||||
ISSUES = "issues",
|
||||
EPICS = "epics",
|
||||
}
|
||||
|
||||
export enum EIssuesStoreType {
|
||||
GLOBAL = "GLOBAL",
|
||||
PROFILE = "PROFILE",
|
||||
TEAM = "TEAM",
|
||||
PROJECT = "PROJECT",
|
||||
CYCLE = "CYCLE",
|
||||
MODULE = "MODULE",
|
||||
TEAM_VIEW = "TEAM_VIEW",
|
||||
PROJECT_VIEW = "PROJECT_VIEW",
|
||||
ARCHIVED = "ARCHIVED",
|
||||
DRAFT = "DRAFT",
|
||||
DEFAULT = "DEFAULT",
|
||||
WORKSPACE_DRAFT = "WORKSPACE_DRAFT",
|
||||
EPIC = "EPIC",
|
||||
}
|
||||
|
||||
export enum EIssueCommentAccessSpecifier {
|
||||
EXTERNAL = "EXTERNAL",
|
||||
INTERNAL = "INTERNAL",
|
||||
}
|
||||
|
||||
export enum EIssueListRow {
|
||||
HEADER = "HEADER",
|
||||
ISSUE = "ISSUE",
|
||||
NO_ISSUES = "NO_ISSUES",
|
||||
QUICK_ADD = "QUICK_ADD",
|
||||
}
|
||||
|
||||
export const ISSUE_PRIORITIES: {
|
||||
key: TIssuePriorities;
|
||||
title: string;
|
||||
}[] = [
|
||||
{
|
||||
key: "urgent",
|
||||
title: "Urgent",
|
||||
},
|
||||
{
|
||||
key: "high",
|
||||
title: "High",
|
||||
},
|
||||
{
|
||||
key: "medium",
|
||||
title: "Medium",
|
||||
},
|
||||
{
|
||||
key: "low",
|
||||
title: "Low",
|
||||
},
|
||||
{
|
||||
key: "none",
|
||||
title: "None",
|
||||
},
|
||||
];
|
||||
|
||||
export const DRAG_ALLOWED_GROUPS: TIssueGroupByOptions[] = [
|
||||
"state",
|
||||
"priority",
|
||||
"assignees",
|
||||
"labels",
|
||||
"module",
|
||||
"cycle",
|
||||
];
|
||||
|
||||
export type TCreateModalStoreTypes =
|
||||
| EIssuesStoreType.TEAM
|
||||
| EIssuesStoreType.PROJECT
|
||||
| EIssuesStoreType.TEAM_VIEW
|
||||
| EIssuesStoreType.PROJECT_VIEW
|
||||
| EIssuesStoreType.PROFILE
|
||||
| EIssuesStoreType.CYCLE
|
||||
| EIssuesStoreType.MODULE
|
||||
| EIssuesStoreType.EPIC;
|
||||
|
||||
export const ISSUE_GROUP_BY_OPTIONS: {
|
||||
key: TIssueGroupByOptions;
|
||||
titleTranslationKey: string;
|
||||
}[] = [
|
||||
{ key: "state", titleTranslationKey: "common.states" },
|
||||
{ key: "state_detail.group", titleTranslationKey: "common.state_groups" },
|
||||
{ key: "priority", titleTranslationKey: "common.priority" },
|
||||
{ key: "team_project", titleTranslationKey: "common.team_project" }, // required this on team issues
|
||||
{ key: "project", titleTranslationKey: "common.project" }, // required this on my issues
|
||||
{ key: "cycle", titleTranslationKey: "common.cycle" }, // required this on my issues
|
||||
{ key: "module", titleTranslationKey: "common.module" }, // required this on my issues
|
||||
{ key: "labels", titleTranslationKey: "common.labels" },
|
||||
{ key: "assignees", titleTranslationKey: "common.assignees" },
|
||||
{ key: "created_by", titleTranslationKey: "common.created_by" },
|
||||
{ key: null, titleTranslationKey: "common.none" },
|
||||
];
|
||||
|
||||
export const ISSUE_ORDER_BY_OPTIONS: {
|
||||
key: TIssueOrderByOptions;
|
||||
titleTranslationKey: string;
|
||||
}[] = [
|
||||
{ key: "sort_order", titleTranslationKey: "common.order_by.manual" },
|
||||
{ key: "-created_at", titleTranslationKey: "common.order_by.last_created" },
|
||||
{ key: "-updated_at", titleTranslationKey: "common.order_by.last_updated" },
|
||||
{ key: "start_date", titleTranslationKey: "common.order_by.start_date" },
|
||||
{ key: "target_date", titleTranslationKey: "common.order_by.due_date" },
|
||||
{ key: "-priority", titleTranslationKey: "common.priority" },
|
||||
];
|
||||
|
||||
export const ISSUE_DISPLAY_PROPERTIES_KEYS: (keyof IIssueDisplayProperties)[] =
|
||||
[
|
||||
"assignee",
|
||||
"start_date",
|
||||
"due_date",
|
||||
"labels",
|
||||
"key",
|
||||
"priority",
|
||||
"state",
|
||||
"sub_issue_count",
|
||||
"link",
|
||||
"attachment_count",
|
||||
"estimate",
|
||||
"created_on",
|
||||
"updated_on",
|
||||
"modules",
|
||||
"cycle",
|
||||
"issue_type",
|
||||
];
|
||||
|
||||
export const ISSUE_DISPLAY_PROPERTIES: {
|
||||
key: keyof IIssueDisplayProperties;
|
||||
titleTranslationKey: string;
|
||||
}[] = [
|
||||
{
|
||||
key: "key",
|
||||
titleTranslationKey: "issue.display.properties.id",
|
||||
},
|
||||
{
|
||||
key: "issue_type",
|
||||
titleTranslationKey: "issue.display.properties.issue_type",
|
||||
},
|
||||
{
|
||||
key: "assignee",
|
||||
titleTranslationKey: "common.assignee",
|
||||
},
|
||||
{
|
||||
key: "start_date",
|
||||
titleTranslationKey: "common.order_by.start_date",
|
||||
},
|
||||
{
|
||||
key: "due_date",
|
||||
titleTranslationKey: "common.order_by.due_date",
|
||||
},
|
||||
{ key: "labels", titleTranslationKey: "common.labels" },
|
||||
{
|
||||
key: "priority",
|
||||
titleTranslationKey: "common.priority",
|
||||
},
|
||||
{ key: "state", titleTranslationKey: "common.state" },
|
||||
{
|
||||
key: "sub_issue_count",
|
||||
titleTranslationKey: "issue.display.properties.sub_issue_count",
|
||||
},
|
||||
{
|
||||
key: "attachment_count",
|
||||
titleTranslationKey: "issue.display.properties.attachment_count",
|
||||
},
|
||||
{ key: "link", titleTranslationKey: "common.link" },
|
||||
{
|
||||
key: "estimate",
|
||||
titleTranslationKey: "common.estimate",
|
||||
},
|
||||
{ key: "modules", titleTranslationKey: "common.module" },
|
||||
{ key: "cycle", titleTranslationKey: "common.cycle" },
|
||||
];
|
||||
@@ -0,0 +1,530 @@
|
||||
import {
|
||||
ILayoutDisplayFiltersOptions,
|
||||
TIssueActivityComment,
|
||||
} from "@plane/types";
|
||||
import {
|
||||
TIssueFilterPriorityObject,
|
||||
ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
EIssuesStoreType,
|
||||
} from "./common";
|
||||
|
||||
import { TIssueLayout } from "./layout";
|
||||
|
||||
export type TIssueFilterKeys = "priority" | "state" | "labels";
|
||||
|
||||
export enum EServerGroupByToFilterOptions {
|
||||
"state_id" = "state",
|
||||
"priority" = "priority",
|
||||
"labels__id" = "labels",
|
||||
"state__group" = "state_group",
|
||||
"assignees__id" = "assignees",
|
||||
"cycle_id" = "cycle",
|
||||
"issue_module__module_id" = "module",
|
||||
"target_date" = "target_date",
|
||||
"project_id" = "project",
|
||||
"created_by" = "created_by",
|
||||
}
|
||||
|
||||
export enum EIssueFilterType {
|
||||
FILTERS = "filters",
|
||||
DISPLAY_FILTERS = "display_filters",
|
||||
DISPLAY_PROPERTIES = "display_properties",
|
||||
KANBAN_FILTERS = "kanban_filters",
|
||||
}
|
||||
|
||||
export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
[key in TIssueLayout]: Record<"filters", TIssueFilterKeys[]>;
|
||||
} = {
|
||||
list: {
|
||||
filters: ["priority", "state", "labels"],
|
||||
},
|
||||
kanban: {
|
||||
filters: ["priority", "state", "labels"],
|
||||
},
|
||||
calendar: {
|
||||
filters: ["priority", "state", "labels"],
|
||||
},
|
||||
spreadsheet: {
|
||||
filters: ["priority", "state", "labels"],
|
||||
},
|
||||
gantt: {
|
||||
filters: ["priority", "state", "labels"],
|
||||
},
|
||||
};
|
||||
|
||||
export const ISSUE_PRIORITY_FILTERS: TIssueFilterPriorityObject[] = [
|
||||
{
|
||||
key: "urgent",
|
||||
titleTranslationKey: "issue.priority.urgent",
|
||||
className: "bg-red-500 border-red-500 text-white",
|
||||
icon: "error",
|
||||
},
|
||||
{
|
||||
key: "high",
|
||||
titleTranslationKey: "issue.priority.high",
|
||||
className: "text-orange-500 border-custom-border-300",
|
||||
icon: "signal_cellular_alt",
|
||||
},
|
||||
{
|
||||
key: "medium",
|
||||
titleTranslationKey: "issue.priority.medium",
|
||||
className: "text-yellow-500 border-custom-border-300",
|
||||
icon: "signal_cellular_alt_2_bar",
|
||||
},
|
||||
{
|
||||
key: "low",
|
||||
titleTranslationKey: "issue.priority.low",
|
||||
className: "text-green-500 border-custom-border-300",
|
||||
icon: "signal_cellular_alt_1_bar",
|
||||
},
|
||||
{
|
||||
key: "none",
|
||||
titleTranslationKey: "common.none",
|
||||
className: "text-gray-500 border-custom-border-300",
|
||||
icon: "block",
|
||||
},
|
||||
];
|
||||
|
||||
export type TFiltersByLayout = {
|
||||
[layoutType: string]: ILayoutDisplayFiltersOptions;
|
||||
};
|
||||
|
||||
export type TIssueFiltersToDisplayByPageType = {
|
||||
[pageType: string]: TFiltersByLayout;
|
||||
};
|
||||
|
||||
export const ISSUE_DISPLAY_FILTERS_BY_PAGE: TIssueFiltersToDisplayByPageType = {
|
||||
profile_issues: {
|
||||
list: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state_group",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: ["state_detail.group", "priority", "project", "labels", null],
|
||||
order_by: [
|
||||
"sort_order",
|
||||
"-created_at",
|
||||
"-updated_at",
|
||||
"start_date",
|
||||
"-priority",
|
||||
],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups", "sub_issue"],
|
||||
},
|
||||
},
|
||||
kanban: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state_group",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: ["state_detail.group", "priority", "project", "labels"],
|
||||
order_by: [
|
||||
"sort_order",
|
||||
"-created_at",
|
||||
"-updated_at",
|
||||
"start_date",
|
||||
"-priority",
|
||||
],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups"],
|
||||
},
|
||||
},
|
||||
},
|
||||
archived_issues: {
|
||||
list: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"assignees",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"issue_type",
|
||||
],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: [
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"state_detail.group",
|
||||
"priority",
|
||||
"labels",
|
||||
"assignees",
|
||||
"created_by",
|
||||
null,
|
||||
],
|
||||
order_by: [
|
||||
"sort_order",
|
||||
"-created_at",
|
||||
"-updated_at",
|
||||
"start_date",
|
||||
"-priority",
|
||||
],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups"],
|
||||
},
|
||||
},
|
||||
},
|
||||
draft_issues: {
|
||||
list: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state_group",
|
||||
"cycle",
|
||||
"module",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"issue_type",
|
||||
],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: [
|
||||
"state_detail.group",
|
||||
"cycle",
|
||||
"module",
|
||||
"priority",
|
||||
"project",
|
||||
"labels",
|
||||
null,
|
||||
],
|
||||
order_by: [
|
||||
"sort_order",
|
||||
"-created_at",
|
||||
"-updated_at",
|
||||
"start_date",
|
||||
"-priority",
|
||||
],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups"],
|
||||
},
|
||||
},
|
||||
kanban: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state_group",
|
||||
"cycle",
|
||||
"module",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"issue_type",
|
||||
],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: [
|
||||
"state_detail.group",
|
||||
"cycle",
|
||||
"module",
|
||||
"priority",
|
||||
"project",
|
||||
"labels",
|
||||
],
|
||||
order_by: [
|
||||
"sort_order",
|
||||
"-created_at",
|
||||
"-updated_at",
|
||||
"start_date",
|
||||
"-priority",
|
||||
],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups"],
|
||||
},
|
||||
},
|
||||
},
|
||||
my_issues: {
|
||||
spreadsheet: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state_group",
|
||||
"labels",
|
||||
"assignees",
|
||||
"created_by",
|
||||
"subscriber",
|
||||
"project",
|
||||
"start_date",
|
||||
"target_date",
|
||||
],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
order_by: [],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["sub_issue"],
|
||||
},
|
||||
},
|
||||
list: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state_group",
|
||||
"labels",
|
||||
"assignees",
|
||||
"created_by",
|
||||
"subscriber",
|
||||
"project",
|
||||
"start_date",
|
||||
"target_date",
|
||||
],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: false,
|
||||
values: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
issues: {
|
||||
list: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"issue_type",
|
||||
],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: [
|
||||
"state",
|
||||
"priority",
|
||||
"cycle",
|
||||
"module",
|
||||
"labels",
|
||||
"assignees",
|
||||
"created_by",
|
||||
null,
|
||||
],
|
||||
order_by: [
|
||||
"sort_order",
|
||||
"-created_at",
|
||||
"-updated_at",
|
||||
"start_date",
|
||||
"-priority",
|
||||
],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups", "sub_issue"],
|
||||
},
|
||||
},
|
||||
kanban: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"issue_type",
|
||||
],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: [
|
||||
"state",
|
||||
"priority",
|
||||
"cycle",
|
||||
"module",
|
||||
"labels",
|
||||
"assignees",
|
||||
"created_by",
|
||||
],
|
||||
sub_group_by: [
|
||||
"state",
|
||||
"priority",
|
||||
"cycle",
|
||||
"module",
|
||||
"labels",
|
||||
"assignees",
|
||||
"created_by",
|
||||
null,
|
||||
],
|
||||
order_by: [
|
||||
"sort_order",
|
||||
"-created_at",
|
||||
"-updated_at",
|
||||
"start_date",
|
||||
"-priority",
|
||||
"target_date",
|
||||
],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups", "sub_issue"],
|
||||
},
|
||||
},
|
||||
calendar: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"issue_type",
|
||||
],
|
||||
display_properties: ["key", "issue_type"],
|
||||
display_filters: {
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["sub_issue"],
|
||||
},
|
||||
},
|
||||
spreadsheet: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"issue_type",
|
||||
],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
order_by: [
|
||||
"sort_order",
|
||||
"-created_at",
|
||||
"-updated_at",
|
||||
"start_date",
|
||||
"-priority",
|
||||
],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["sub_issue"],
|
||||
},
|
||||
},
|
||||
gantt_chart: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"issue_type",
|
||||
],
|
||||
display_properties: ["key", "issue_type"],
|
||||
display_filters: {
|
||||
order_by: [
|
||||
"sort_order",
|
||||
"-created_at",
|
||||
"-updated_at",
|
||||
"start_date",
|
||||
"-priority",
|
||||
],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["sub_issue"],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ISSUE_STORE_TO_FILTERS_MAP: Partial<
|
||||
Record<EIssuesStoreType, TFiltersByLayout>
|
||||
> = {
|
||||
[EIssuesStoreType.PROJECT]: ISSUE_DISPLAY_FILTERS_BY_PAGE.issues,
|
||||
};
|
||||
|
||||
export enum EActivityFilterType {
|
||||
ACTIVITY = "ACTIVITY",
|
||||
COMMENT = "COMMENT",
|
||||
}
|
||||
|
||||
export type TActivityFilters = EActivityFilterType;
|
||||
|
||||
export const ACTIVITY_FILTER_TYPE_OPTIONS: Record<
|
||||
TActivityFilters,
|
||||
{ labelTranslationKey: string }
|
||||
> = {
|
||||
[EActivityFilterType.ACTIVITY]: {
|
||||
labelTranslationKey: "common.updates",
|
||||
},
|
||||
[EActivityFilterType.COMMENT]: {
|
||||
labelTranslationKey: "common.comments",
|
||||
},
|
||||
};
|
||||
|
||||
export type TActivityFilterOption = {
|
||||
key: TActivityFilters;
|
||||
labelTranslationKey: string;
|
||||
isSelected: boolean;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export const defaultActivityFilters: TActivityFilters[] = [
|
||||
EActivityFilterType.ACTIVITY,
|
||||
EActivityFilterType.COMMENT,
|
||||
];
|
||||
|
||||
export const filterActivityOnSelectedFilters = (
|
||||
activity: TIssueActivityComment[],
|
||||
filters: TActivityFilters[]
|
||||
): TIssueActivityComment[] =>
|
||||
activity.filter((activity) =>
|
||||
filters.includes(activity.activity_type as TActivityFilters)
|
||||
);
|
||||
|
||||
export const ENABLE_ISSUE_DEPENDENCIES = false;
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./common";
|
||||
export * from "./filter";
|
||||
export * from "./layout";
|
||||
@@ -0,0 +1,76 @@
|
||||
export type TIssueLayout =
|
||||
| "list"
|
||||
| "kanban"
|
||||
| "calendar"
|
||||
| "spreadsheet"
|
||||
| "gantt";
|
||||
|
||||
export enum EIssueLayoutTypes {
|
||||
LIST = "list",
|
||||
KANBAN = "kanban",
|
||||
CALENDAR = "calendar",
|
||||
GANTT = "gantt_chart",
|
||||
SPREADSHEET = "spreadsheet",
|
||||
}
|
||||
|
||||
export type TIssueLayoutMap = Record<
|
||||
EIssueLayoutTypes,
|
||||
{
|
||||
key: EIssueLayoutTypes;
|
||||
i18n_title: string;
|
||||
i18n_label: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export const SITES_ISSUE_LAYOUTS: {
|
||||
key: TIssueLayout;
|
||||
titleTranslationKey: string;
|
||||
icon: any;
|
||||
}[] = [
|
||||
{
|
||||
key: "list",
|
||||
icon: "List",
|
||||
titleTranslationKey: "issue.layouts.list",
|
||||
},
|
||||
{
|
||||
key: "kanban",
|
||||
icon: "Kanban",
|
||||
titleTranslationKey: "issue.layouts.kanban",
|
||||
},
|
||||
// { key: "calendar", title: "Calendar", icon: Calendar },
|
||||
// { key: "spreadsheet", title: "Spreadsheet", icon: Sheet },
|
||||
// { key: "gantt", title: "Gantt chart", icon: GanttChartSquare },
|
||||
];
|
||||
|
||||
export const ISSUE_LAYOUT_MAP: TIssueLayoutMap = {
|
||||
[EIssueLayoutTypes.LIST]: {
|
||||
key: EIssueLayoutTypes.LIST,
|
||||
i18n_title: "issue.layouts.title.list",
|
||||
i18n_label: "issue.layouts.list",
|
||||
},
|
||||
[EIssueLayoutTypes.KANBAN]: {
|
||||
key: EIssueLayoutTypes.KANBAN,
|
||||
i18n_title: "issue.layouts.title.kanban",
|
||||
i18n_label: "issue.layouts.kanban",
|
||||
},
|
||||
[EIssueLayoutTypes.CALENDAR]: {
|
||||
key: EIssueLayoutTypes.CALENDAR,
|
||||
i18n_title: "issue.layouts.title.calendar",
|
||||
i18n_label: "issue.layouts.calendar",
|
||||
},
|
||||
[EIssueLayoutTypes.SPREADSHEET]: {
|
||||
key: EIssueLayoutTypes.SPREADSHEET,
|
||||
i18n_title: "issue.layouts.title.spreadsheet",
|
||||
i18n_label: "issue.layouts.spreadsheet",
|
||||
},
|
||||
[EIssueLayoutTypes.GANTT]: {
|
||||
key: EIssueLayoutTypes.GANTT,
|
||||
i18n_title: "issue.layouts.title.gantt",
|
||||
i18n_label: "issue.layouts.gantt",
|
||||
},
|
||||
};
|
||||
|
||||
export const ISSUE_LAYOUTS: {
|
||||
key: EIssueLayoutTypes;
|
||||
i18n_title: string;
|
||||
}[] = Object.values(ISSUE_LAYOUT_MAP);
|
||||
@@ -3,9 +3,9 @@ export const SITE_NAME =
|
||||
export const SITE_TITLE =
|
||||
"Plane | Simple, extensible, open-source project management tool.";
|
||||
export const SITE_DESCRIPTION =
|
||||
"Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.";
|
||||
"Open-source project management tool to manage work items, cycles, and product roadmaps easily";
|
||||
export const SITE_KEYWORDS =
|
||||
"software development, plan, ship, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration";
|
||||
"software development, plan, ship, software, accelerate, code management, release management, project management, work items tracking, agile, scrum, kanban, collaboration";
|
||||
export const SITE_URL = "https://app.plane.so/";
|
||||
export const TWITTER_USER_NAME =
|
||||
"Plane | Simple, extensible, open-source project management tool.";
|
||||
@@ -18,6 +18,6 @@ export const SPACE_SITE_TITLE =
|
||||
export const SPACE_SITE_DESCRIPTION =
|
||||
"Plane Publish is a customer feedback management tool built on top of plane.so";
|
||||
export const SPACE_SITE_KEYWORDS =
|
||||
"software development, customer feedback, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration";
|
||||
"software development, customer feedback, software, accelerate, code management, release management, project management, work items tracking, agile, scrum, kanban, collaboration";
|
||||
export const SPACE_SITE_URL = "https://app.plane.so/";
|
||||
export const SPACE_TWITTER_USER_NAME = "planepowers";
|
||||
|
||||
@@ -1,51 +1,54 @@
|
||||
import { GanttChartSquare, LayoutGrid, List } from "lucide-react";
|
||||
// types
|
||||
import { TModuleLayoutOptions, TModuleOrderByOptions, TModuleStatus } from "@plane/types";
|
||||
import {
|
||||
TModuleLayoutOptions,
|
||||
TModuleOrderByOptions,
|
||||
TModuleStatus,
|
||||
} from "@plane/types";
|
||||
|
||||
export const MODULE_STATUS: {
|
||||
label: string;
|
||||
i18n_label: string;
|
||||
value: TModuleStatus;
|
||||
color: string;
|
||||
textColor: string;
|
||||
bgColor: string;
|
||||
}[] = [
|
||||
{
|
||||
label: "Backlog",
|
||||
i18n_label: "project_modules.status.backlog",
|
||||
value: "backlog",
|
||||
color: "#a3a3a2",
|
||||
textColor: "text-custom-text-400",
|
||||
bgColor: "bg-custom-background-80",
|
||||
},
|
||||
{
|
||||
label: "Planned",
|
||||
i18n_label: "project_modules.status.planned",
|
||||
value: "planned",
|
||||
color: "#3f76ff",
|
||||
textColor: "text-blue-500",
|
||||
bgColor: "bg-indigo-50",
|
||||
},
|
||||
{
|
||||
label: "In Progress",
|
||||
i18n_label: "project_modules.status.in_progress",
|
||||
value: "in-progress",
|
||||
color: "#f39e1f",
|
||||
textColor: "text-amber-500",
|
||||
bgColor: "bg-amber-50",
|
||||
},
|
||||
{
|
||||
label: "Paused",
|
||||
i18n_label: "project_modules.status.paused",
|
||||
value: "paused",
|
||||
color: "#525252",
|
||||
textColor: "text-custom-text-300",
|
||||
bgColor: "bg-custom-background-90",
|
||||
},
|
||||
{
|
||||
label: "Completed",
|
||||
i18n_label: "project_modules.status.completed",
|
||||
value: "completed",
|
||||
color: "#16a34a",
|
||||
textColor: "text-green-600",
|
||||
bgColor: "bg-green-100",
|
||||
},
|
||||
{
|
||||
label: "Cancelled",
|
||||
i18n_label: "project_modules.status.cancelled",
|
||||
value: "cancelled",
|
||||
color: "#ef4444",
|
||||
textColor: "text-red-500",
|
||||
@@ -53,47 +56,50 @@ export const MODULE_STATUS: {
|
||||
},
|
||||
];
|
||||
|
||||
export const MODULE_VIEW_LAYOUTS: { key: TModuleLayoutOptions; icon: any; title: string }[] = [
|
||||
export const MODULE_VIEW_LAYOUTS: {
|
||||
key: TModuleLayoutOptions;
|
||||
i18n_title: string;
|
||||
}[] = [
|
||||
{
|
||||
key: "list",
|
||||
icon: List,
|
||||
title: "List layout",
|
||||
i18n_title: "project_modules.layout.list",
|
||||
},
|
||||
{
|
||||
key: "board",
|
||||
icon: LayoutGrid,
|
||||
title: "Gallery layout",
|
||||
i18n_title: "project_modules.layout.board",
|
||||
},
|
||||
{
|
||||
key: "gantt",
|
||||
icon: GanttChartSquare,
|
||||
title: "Timeline layout",
|
||||
i18n_title: "project_modules.layout.timeline",
|
||||
},
|
||||
];
|
||||
|
||||
export const MODULE_ORDER_BY_OPTIONS: { key: TModuleOrderByOptions; label: string }[] = [
|
||||
export const MODULE_ORDER_BY_OPTIONS: {
|
||||
key: TModuleOrderByOptions;
|
||||
i18n_label: string;
|
||||
}[] = [
|
||||
{
|
||||
key: "name",
|
||||
label: "Name",
|
||||
i18n_label: "project_modules.order_by.name",
|
||||
},
|
||||
{
|
||||
key: "progress",
|
||||
label: "Progress",
|
||||
i18n_label: "project_modules.order_by.progress",
|
||||
},
|
||||
{
|
||||
key: "issues_length",
|
||||
label: "Number of issues",
|
||||
i18n_label: "project_modules.order_by.issues",
|
||||
},
|
||||
{
|
||||
key: "target_date",
|
||||
label: "Due date",
|
||||
i18n_label: "project_modules.order_by.due_date",
|
||||
},
|
||||
{
|
||||
key: "created_at",
|
||||
label: "Created date",
|
||||
i18n_label: "project_modules.order_by.created_at",
|
||||
},
|
||||
{
|
||||
key: "sort_order",
|
||||
label: "Manual",
|
||||
i18n_label: "project_modules.order_by.manual",
|
||||
},
|
||||
];
|
||||
@@ -29,12 +29,13 @@ export type TNotificationTab = ENotificationTab.ALL | ENotificationTab.MENTIONS;
|
||||
|
||||
export const NOTIFICATION_TABS = [
|
||||
{
|
||||
label: "All",
|
||||
i18n_label: "notification.tabs.all",
|
||||
value: ENotificationTab.ALL,
|
||||
count: (unReadNotification: TUnreadNotificationsCount) => unReadNotification?.total_unread_notifications_count || 0,
|
||||
count: (unReadNotification: TUnreadNotificationsCount) =>
|
||||
unReadNotification?.total_unread_notifications_count || 0,
|
||||
},
|
||||
{
|
||||
label: "Mentions",
|
||||
i18n_label: "notification.tabs.mentions",
|
||||
value: ENotificationTab.MENTIONS,
|
||||
count: (unReadNotification: TUnreadNotificationsCount) =>
|
||||
unReadNotification?.mention_unread_notifications_count || 0,
|
||||
@@ -43,15 +44,15 @@ export const NOTIFICATION_TABS = [
|
||||
|
||||
export const FILTER_TYPE_OPTIONS = [
|
||||
{
|
||||
label: "Assigned to me",
|
||||
i18n_label: "notification.filter.assigned",
|
||||
value: ENotificationFilterType.ASSIGNED,
|
||||
},
|
||||
{
|
||||
label: "Created by me",
|
||||
i18n_label: "notification.filter.created",
|
||||
value: ENotificationFilterType.CREATED,
|
||||
},
|
||||
{
|
||||
label: "Subscribed by me",
|
||||
i18n_label: "notification.filter.subscribed",
|
||||
value: ENotificationFilterType.SUBSCRIBED,
|
||||
},
|
||||
];
|
||||
@@ -59,7 +60,7 @@ export const FILTER_TYPE_OPTIONS = [
|
||||
export const NOTIFICATION_SNOOZE_OPTIONS = [
|
||||
{
|
||||
key: "1_day",
|
||||
label: "1 day",
|
||||
i18n_label: "notification.snooze.1_day",
|
||||
value: () => {
|
||||
const date = new Date();
|
||||
return new Date(date.getTime() + 24 * 60 * 60 * 1000);
|
||||
@@ -67,7 +68,7 @@ export const NOTIFICATION_SNOOZE_OPTIONS = [
|
||||
},
|
||||
{
|
||||
key: "3_days",
|
||||
label: "3 days",
|
||||
i18n_label: "notification.snooze.3_days",
|
||||
value: () => {
|
||||
const date = new Date();
|
||||
return new Date(date.getTime() + 3 * 24 * 60 * 60 * 1000);
|
||||
@@ -75,7 +76,7 @@ export const NOTIFICATION_SNOOZE_OPTIONS = [
|
||||
},
|
||||
{
|
||||
key: "5_days",
|
||||
label: "5 days",
|
||||
i18n_label: "notification.snooze.5_days",
|
||||
value: () => {
|
||||
const date = new Date();
|
||||
return new Date(date.getTime() + 5 * 24 * 60 * 60 * 1000);
|
||||
@@ -83,7 +84,7 @@ export const NOTIFICATION_SNOOZE_OPTIONS = [
|
||||
},
|
||||
{
|
||||
key: "1_week",
|
||||
label: "1 week",
|
||||
i18n_label: "notification.snooze.1_week",
|
||||
value: () => {
|
||||
const date = new Date();
|
||||
return new Date(date.getTime() + 7 * 24 * 60 * 60 * 1000);
|
||||
@@ -91,7 +92,7 @@ export const NOTIFICATION_SNOOZE_OPTIONS = [
|
||||
},
|
||||
{
|
||||
key: "2_weeks",
|
||||
label: "2 weeks",
|
||||
i18n_label: "notification.snooze.2_weeks",
|
||||
value: () => {
|
||||
const date = new Date();
|
||||
return new Date(date.getTime() + 14 * 24 * 60 * 60 * 1000);
|
||||
@@ -99,7 +100,7 @@ export const NOTIFICATION_SNOOZE_OPTIONS = [
|
||||
},
|
||||
{
|
||||
key: "custom",
|
||||
label: "Custom",
|
||||
i18n_label: "notification.snooze.custom",
|
||||
value: undefined,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,14 @@
|
||||
export enum EPageAccess {
|
||||
PUBLIC = 0,
|
||||
PRIVATE = 1,
|
||||
}
|
||||
|
||||
export type TCreatePageModal = {
|
||||
isOpen: boolean;
|
||||
pageAccess?: EPageAccess;
|
||||
};
|
||||
|
||||
export const DEFAULT_CREATE_PAGE_MODAL_DATA: TCreatePageModal = {
|
||||
isOpen: false,
|
||||
pageAccess: EPageAccess.PUBLIC,
|
||||
};
|
||||
@@ -1,48 +1,38 @@
|
||||
import React from "react";
|
||||
// icons
|
||||
import { Activity, Bell, CircleUser, KeyRound, LucideProps, Settings2 } from "lucide-react";
|
||||
|
||||
export const PROFILE_ACTION_LINKS: {
|
||||
key: string;
|
||||
label: string;
|
||||
i18n_label: string;
|
||||
href: string;
|
||||
highlight: (pathname: string) => boolean;
|
||||
Icon: React.FC<LucideProps>;
|
||||
}[] = [
|
||||
{
|
||||
key: "profile",
|
||||
label: "Profile",
|
||||
i18n_label: "profile.actions.profile",
|
||||
href: `/profile`,
|
||||
highlight: (pathname: string) => pathname === "/profile/",
|
||||
Icon: CircleUser,
|
||||
},
|
||||
{
|
||||
key: "security",
|
||||
label: "Security",
|
||||
i18n_label: "profile.actions.security",
|
||||
href: `/profile/security`,
|
||||
highlight: (pathname: string) => pathname === "/profile/security/",
|
||||
Icon: KeyRound,
|
||||
},
|
||||
{
|
||||
key: "activity",
|
||||
label: "Activity",
|
||||
i18n_label: "profile.actions.activity",
|
||||
href: `/profile/activity`,
|
||||
highlight: (pathname: string) => pathname === "/profile/activity/",
|
||||
Icon: Activity,
|
||||
},
|
||||
{
|
||||
key: "appearance",
|
||||
label: "Appearance",
|
||||
i18n_label: "profile.actions.appearance",
|
||||
href: `/profile/appearance`,
|
||||
highlight: (pathname: string) => pathname.includes("/profile/appearance"),
|
||||
Icon: Settings2,
|
||||
},
|
||||
{
|
||||
key: "notifications",
|
||||
label: "Notifications",
|
||||
i18n_label: "profile.actions.notifications",
|
||||
href: `/profile/notifications`,
|
||||
highlight: (pathname: string) => pathname === "/profile/notifications/",
|
||||
Icon: Bell,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -50,7 +40,7 @@ export const PROFILE_VIEWER_TAB = [
|
||||
{
|
||||
key: "summary",
|
||||
route: "",
|
||||
label: "Summary",
|
||||
i18n_label: "profile.tabs.summary",
|
||||
selected: "/",
|
||||
},
|
||||
];
|
||||
@@ -59,24 +49,25 @@ export const PROFILE_ADMINS_TAB = [
|
||||
{
|
||||
key: "assigned",
|
||||
route: "assigned",
|
||||
label: "Assigned",
|
||||
i18n_label: "profile.tabs.assigned",
|
||||
selected: "/assigned/",
|
||||
},
|
||||
{
|
||||
key: "created",
|
||||
route: "created",
|
||||
label: "Created",
|
||||
i18n_label: "profile.tabs.created",
|
||||
selected: "/created/",
|
||||
},
|
||||
{
|
||||
key: "subscribed",
|
||||
route: "subscribed",
|
||||
label: "Subscribed",
|
||||
i18n_label: "profile.tabs.subscribed",
|
||||
selected: "/subscribed/",
|
||||
},
|
||||
{
|
||||
key: "activity",
|
||||
route: "activity",
|
||||
label: "Activity",
|
||||
i18n_label: "profile.tabs.activity",
|
||||
selected: "/activity/",
|
||||
},
|
||||
];
|
||||
@@ -1,41 +1,65 @@
|
||||
// icons
|
||||
import { Globe2, Lock, LucideIcon } from "lucide-react";
|
||||
import { TProjectAppliedDisplayFilterKeys, TProjectOrderByOptions } from "@plane/types";
|
||||
import {
|
||||
TProjectAppliedDisplayFilterKeys,
|
||||
TProjectOrderByOptions,
|
||||
} from "@plane/types";
|
||||
|
||||
export const NETWORK_CHOICES: {
|
||||
export type TNetworkChoiceIconKey = "Lock" | "Globe2";
|
||||
|
||||
export type TNetworkChoice = {
|
||||
key: 0 | 2;
|
||||
label: string;
|
||||
labelKey: string;
|
||||
i18n_label: string;
|
||||
description: string;
|
||||
icon: LucideIcon;
|
||||
}[] = [
|
||||
iconKey: TNetworkChoiceIconKey;
|
||||
};
|
||||
|
||||
export const NETWORK_CHOICES: TNetworkChoice[] = [
|
||||
{
|
||||
key: 0,
|
||||
label: "Private",
|
||||
description: "Accessible only by invite",
|
||||
icon: Lock,
|
||||
labelKey: "Private",
|
||||
i18n_label: "workspace_projects.network.private.title",
|
||||
description: "workspace_projects.network.private.description", //"Accessible only by invite",
|
||||
iconKey: "Lock",
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
label: "Public",
|
||||
description: "Anyone in the workspace except Guests can join",
|
||||
icon: Globe2,
|
||||
labelKey: "Public",
|
||||
i18n_label: "workspace_projects.network.public.title",
|
||||
description: "workspace_projects.network.public.description", //"Anyone in the workspace except Guests can join",
|
||||
iconKey: "Globe2",
|
||||
},
|
||||
];
|
||||
|
||||
export const GROUP_CHOICES = {
|
||||
backlog: "Backlog",
|
||||
unstarted: "Unstarted",
|
||||
started: "Started",
|
||||
completed: "Completed",
|
||||
cancelled: "Cancelled",
|
||||
backlog: {
|
||||
key: "backlog",
|
||||
i18n_label: "workspace_projects.state.backlog",
|
||||
},
|
||||
unstarted: {
|
||||
key: "unstarted",
|
||||
i18n_label: "workspace_projects.state.unstarted",
|
||||
},
|
||||
started: {
|
||||
key: "started",
|
||||
i18n_label: "workspace_projects.state.started",
|
||||
},
|
||||
completed: {
|
||||
key: "completed",
|
||||
i18n_label: "workspace_projects.state.completed",
|
||||
},
|
||||
cancelled: {
|
||||
key: "cancelled",
|
||||
i18n_label: "workspace_projects.state.cancelled",
|
||||
},
|
||||
};
|
||||
|
||||
export const PROJECT_AUTOMATION_MONTHS = [
|
||||
{ label: "1 month", value: 1 },
|
||||
{ label: "3 months", value: 3 },
|
||||
{ label: "6 months", value: 6 },
|
||||
{ label: "9 months", value: 9 },
|
||||
{ label: "12 months", value: 12 },
|
||||
{ i18n_label: "common.months_count", value: 1 },
|
||||
{ i18n_label: "common.months_count", value: 3 },
|
||||
{ i18n_label: "common.months_count", value: 6 },
|
||||
{ i18n_label: "common.months_count", value: 9 },
|
||||
{ i18n_label: "common.months_count", value: 12 },
|
||||
];
|
||||
|
||||
export const PROJECT_UNSPLASH_COVERS = [
|
||||
@@ -59,55 +83,55 @@ export const PROJECT_UNSPLASH_COVERS = [
|
||||
|
||||
export const PROJECT_ORDER_BY_OPTIONS: {
|
||||
key: TProjectOrderByOptions;
|
||||
label: string;
|
||||
i18n_label: string;
|
||||
}[] = [
|
||||
{
|
||||
key: "sort_order",
|
||||
label: "Manual",
|
||||
i18n_label: "workspace_projects.sort.manual",
|
||||
},
|
||||
{
|
||||
key: "name",
|
||||
label: "Name",
|
||||
i18n_label: "workspace_projects.sort.name",
|
||||
},
|
||||
{
|
||||
key: "created_at",
|
||||
label: "Created date",
|
||||
i18n_label: "workspace_projects.sort.created_at",
|
||||
},
|
||||
{
|
||||
key: "members_length",
|
||||
label: "Number of members",
|
||||
i18n_label: "workspace_projects.sort.members_length",
|
||||
},
|
||||
];
|
||||
|
||||
export const PROJECT_DISPLAY_FILTER_OPTIONS: {
|
||||
key: TProjectAppliedDisplayFilterKeys;
|
||||
label: string;
|
||||
i18n_label: string;
|
||||
}[] = [
|
||||
{
|
||||
key: "my_projects",
|
||||
label: "My projects",
|
||||
i18n_label: "workspace_projects.scope.my_projects",
|
||||
},
|
||||
{
|
||||
key: "archived_projects",
|
||||
label: "Archived",
|
||||
i18n_label: "workspace_projects.scope.archived_projects",
|
||||
},
|
||||
];
|
||||
|
||||
export const PROJECT_ERROR_MESSAGES = {
|
||||
permissionError: {
|
||||
title: "You don't have permission to perform this action.",
|
||||
message: undefined,
|
||||
i18n_title: "workspace_projects.error.permission",
|
||||
i18n_message: undefined,
|
||||
},
|
||||
cycleDeleteError: {
|
||||
title: "Error",
|
||||
message: "Failed to delete cycle",
|
||||
i18n_title: "error",
|
||||
i18n_message: "workspace_projects.error.cycle_delete",
|
||||
},
|
||||
moduleDeleteError: {
|
||||
title: "Error",
|
||||
message: "Failed to delete module",
|
||||
i18n_title: "error",
|
||||
i18n_message: "workspace_projects.error.module_delete",
|
||||
},
|
||||
issueDeleteError: {
|
||||
title: "Error",
|
||||
message: "Failed to delete issue",
|
||||
i18n_title: "error",
|
||||
i18n_message: "workspace_projects.error.issue_delete",
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export const SPREADSHEET_SELECT_GROUP = "spreadsheet-issues";
|
||||
@@ -5,6 +5,11 @@ export type TStateGroups =
|
||||
| "completed"
|
||||
| "cancelled";
|
||||
|
||||
export type TDraggableData = {
|
||||
groupKey: TStateGroups;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const STATE_GROUPS: {
|
||||
[key in TStateGroups]: {
|
||||
key: TStateGroups;
|
||||
@@ -43,6 +48,13 @@ export const ARCHIVABLE_STATE_GROUPS = [
|
||||
STATE_GROUPS.completed.key,
|
||||
STATE_GROUPS.cancelled.key,
|
||||
];
|
||||
export const COMPLETED_STATE_GROUPS = [STATE_GROUPS.completed.key];
|
||||
export const PENDING_STATE_GROUPS = [
|
||||
STATE_GROUPS.backlog.key,
|
||||
STATE_GROUPS.unstarted.key,
|
||||
STATE_GROUPS.started.key,
|
||||
STATE_GROUPS.cancelled.key,
|
||||
];
|
||||
|
||||
export const PROGRESS_STATE_GROUPS_DETAILS = [
|
||||
{
|
||||
@@ -66,3 +78,5 @@ export const PROGRESS_STATE_GROUPS_DETAILS = [
|
||||
color: "#A3A3A3",
|
||||
},
|
||||
];
|
||||
|
||||
export const DISPLAY_WORKFLOW_PRO_CTA = false;
|
||||
|
||||
@@ -6,3 +6,11 @@ export const DEFAULT_SWR_CONFIG = {
|
||||
refreshInterval: 600000,
|
||||
errorRetryCount: 3,
|
||||
};
|
||||
|
||||
export const WEB_SWR_CONFIG = {
|
||||
refreshWhenHidden: false,
|
||||
revalidateIfStale: true,
|
||||
revalidateOnFocus: true,
|
||||
revalidateOnMount: true,
|
||||
errorRetryCount: 3,
|
||||
};
|
||||
|
||||
@@ -54,7 +54,14 @@ export const PROJECT_CREATE_TAB_INDICES = [
|
||||
"logo_props",
|
||||
];
|
||||
|
||||
export const PROJECT_CYCLE_TAB_INDICES = ["name", "description", "date_range", "cancel", "submit", "project_id"];
|
||||
export const PROJECT_CYCLE_TAB_INDICES = [
|
||||
"name",
|
||||
"description",
|
||||
"date_range",
|
||||
"cancel",
|
||||
"submit",
|
||||
"project_id",
|
||||
];
|
||||
|
||||
export const PROJECT_MODULE_TAB_INDICES = [
|
||||
"name",
|
||||
@@ -67,9 +74,21 @@ export const PROJECT_MODULE_TAB_INDICES = [
|
||||
"submit",
|
||||
];
|
||||
|
||||
export const PROJECT_VIEW_TAB_INDICES = ["name", "description", "filters", "cancel", "submit"];
|
||||
export const PROJECT_VIEW_TAB_INDICES = [
|
||||
"name",
|
||||
"description",
|
||||
"filters",
|
||||
"cancel",
|
||||
"submit",
|
||||
];
|
||||
|
||||
export const PROJECT_PAGE_TAB_INDICES = ["name", "public", "private", "cancel", "submit"];
|
||||
export const PROJECT_PAGE_TAB_INDICES = [
|
||||
"name",
|
||||
"public",
|
||||
"private",
|
||||
"cancel",
|
||||
"submit",
|
||||
];
|
||||
|
||||
export enum ETabIndices {
|
||||
ISSUE_FORM = "issue-form",
|
||||
@@ -1,9 +1,15 @@
|
||||
export const THEMES = ["light", "dark", "light-contrast", "dark-contrast", "custom"];
|
||||
export const THEMES = [
|
||||
"light",
|
||||
"dark",
|
||||
"light-contrast",
|
||||
"dark-contrast",
|
||||
"custom",
|
||||
];
|
||||
|
||||
export interface I_THEME_OPTION {
|
||||
key: string;
|
||||
value: string;
|
||||
label: string;
|
||||
i18n_label: string;
|
||||
type: string;
|
||||
icon: {
|
||||
border: string;
|
||||
@@ -16,7 +22,7 @@ export const THEME_OPTIONS: I_THEME_OPTION[] = [
|
||||
{
|
||||
key: "system_preference",
|
||||
value: "system",
|
||||
label: "System preference",
|
||||
i18n_label: "System preference",
|
||||
type: "light",
|
||||
icon: {
|
||||
border: "#DEE2E6",
|
||||
@@ -27,7 +33,7 @@ export const THEME_OPTIONS: I_THEME_OPTION[] = [
|
||||
{
|
||||
key: "light",
|
||||
value: "light",
|
||||
label: "Light",
|
||||
i18n_label: "Light",
|
||||
type: "light",
|
||||
icon: {
|
||||
border: "#DEE2E6",
|
||||
@@ -38,7 +44,7 @@ export const THEME_OPTIONS: I_THEME_OPTION[] = [
|
||||
{
|
||||
key: "dark",
|
||||
value: "dark",
|
||||
label: "Dark",
|
||||
i18n_label: "Dark",
|
||||
type: "dark",
|
||||
icon: {
|
||||
border: "#2E3234",
|
||||
@@ -49,7 +55,7 @@ export const THEME_OPTIONS: I_THEME_OPTION[] = [
|
||||
{
|
||||
key: "light_contrast",
|
||||
value: "light-contrast",
|
||||
label: "Light high contrast",
|
||||
i18n_label: "Light high contrast",
|
||||
type: "light",
|
||||
icon: {
|
||||
border: "#000000",
|
||||
@@ -60,7 +66,7 @@ export const THEME_OPTIONS: I_THEME_OPTION[] = [
|
||||
{
|
||||
key: "dark_contrast",
|
||||
value: "dark-contrast",
|
||||
label: "Dark high contrast",
|
||||
i18n_label: "Dark high contrast",
|
||||
type: "dark",
|
||||
icon: {
|
||||
border: "#FFFFFF",
|
||||
@@ -71,7 +77,7 @@ export const THEME_OPTIONS: I_THEME_OPTION[] = [
|
||||
{
|
||||
key: "custom",
|
||||
value: "custom",
|
||||
label: "Custom theme",
|
||||
i18n_label: "Custom theme",
|
||||
type: "light",
|
||||
icon: {
|
||||
border: "#FFC9C9",
|
||||
@@ -36,3 +36,40 @@ export enum EUserProjectRoles {
|
||||
MEMBER = 15,
|
||||
GUEST = 5,
|
||||
}
|
||||
|
||||
export type TUserPermissionsLevel = EUserPermissionsLevel;
|
||||
|
||||
export enum EUserPermissions {
|
||||
ADMIN = 20,
|
||||
MEMBER = 15,
|
||||
GUEST = 5,
|
||||
}
|
||||
export type TUserPermissions = EUserPermissions;
|
||||
|
||||
export type TUserAllowedPermissionsObject = {
|
||||
create: TUserPermissions[];
|
||||
update: TUserPermissions[];
|
||||
delete: TUserPermissions[];
|
||||
read: TUserPermissions[];
|
||||
};
|
||||
export type TUserAllowedPermissions = {
|
||||
workspace: {
|
||||
[key: string]: Partial<TUserAllowedPermissionsObject>;
|
||||
};
|
||||
project: {
|
||||
[key: string]: Partial<TUserAllowedPermissionsObject>;
|
||||
};
|
||||
};
|
||||
|
||||
export const USER_ALLOWED_PERMISSIONS: TUserAllowedPermissions = {
|
||||
workspace: {
|
||||
dashboard: {
|
||||
read: [
|
||||
EUserPermissions.ADMIN,
|
||||
EUserPermissions.MEMBER,
|
||||
EUserPermissions.GUEST,
|
||||
],
|
||||
},
|
||||
},
|
||||
project: {},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
export enum EViewAccess {
|
||||
PRIVATE,
|
||||
PUBLIC,
|
||||
}
|
||||
|
||||
export const VIEW_ACCESS_SPECIFIERS: {
|
||||
key: EViewAccess;
|
||||
i18n_label: string;
|
||||
}[] = [
|
||||
{ key: EViewAccess.PUBLIC, i18n_label: "common.access.public" },
|
||||
{ key: EViewAccess.PRIVATE, i18n_label: "common.access.private" },
|
||||
];
|
||||
|
||||
export const VIEW_SORTING_KEY_OPTIONS = [
|
||||
{ key: "name", i18n_label: "project_view.sort_by.name" },
|
||||
{ key: "created_at", i18n_label: "project_view.sort_by.created_at" },
|
||||
{ key: "updated_at", i18n_label: "project_view.sort_by.updated_at" },
|
||||
];
|
||||
|
||||
export const VIEW_SORT_BY_OPTIONS = [
|
||||
{ key: "asc", i18n_label: "common.order_by.asc" },
|
||||
{ key: "desc", i18n_label: "common.order_by.desc" },
|
||||
];
|
||||
@@ -1,3 +1,6 @@
|
||||
import { TStaticViewTypes } from "@plane/types";
|
||||
import { EUserWorkspaceRoles } from "./user";
|
||||
|
||||
export const ORGANIZATION_SIZE = [
|
||||
"Just myself", // TODO: translate
|
||||
"2-10",
|
||||
@@ -74,3 +77,182 @@ export const RESTRICTED_URLS = [
|
||||
"instances",
|
||||
"instance",
|
||||
];
|
||||
|
||||
export const WORKSPACE_SETTINGS = {
|
||||
general: {
|
||||
key: "general",
|
||||
i18n_label: "workspace_settings.settings.general.title",
|
||||
href: `/settings`,
|
||||
access: [EUserWorkspaceRoles.ADMIN],
|
||||
highlight: (pathname: string, baseUrl: string) =>
|
||||
pathname === `${baseUrl}/settings/`,
|
||||
},
|
||||
members: {
|
||||
key: "members",
|
||||
i18n_label: "workspace_settings.settings.members.title",
|
||||
href: `/settings/members`,
|
||||
access: [EUserWorkspaceRoles.ADMIN],
|
||||
highlight: (pathname: string, baseUrl: string) =>
|
||||
pathname === `${baseUrl}/settings/members/`,
|
||||
},
|
||||
"billing-and-plans": {
|
||||
key: "billing-and-plans",
|
||||
i18n_label: "workspace_settings.settings.billing_and_plans.title",
|
||||
href: `/settings/billing`,
|
||||
access: [EUserWorkspaceRoles.ADMIN],
|
||||
highlight: (pathname: string, baseUrl: string) =>
|
||||
pathname === `${baseUrl}/settings/billing/`,
|
||||
},
|
||||
export: {
|
||||
key: "export",
|
||||
i18n_label: "workspace_settings.settings.exports.title",
|
||||
href: `/settings/exports`,
|
||||
access: [EUserWorkspaceRoles.ADMIN],
|
||||
highlight: (pathname: string, baseUrl: string) =>
|
||||
pathname === `${baseUrl}/settings/exports/`,
|
||||
},
|
||||
webhooks: {
|
||||
key: "webhooks",
|
||||
i18n_label: "workspace_settings.settings.webhooks.title",
|
||||
href: `/settings/webhooks`,
|
||||
access: [EUserWorkspaceRoles.ADMIN],
|
||||
highlight: (pathname: string, baseUrl: string) =>
|
||||
pathname === `${baseUrl}/settings/webhooks/`,
|
||||
},
|
||||
"api-tokens": {
|
||||
key: "api-tokens",
|
||||
i18n_label: "workspace_settings.settings.api_tokens.title",
|
||||
href: `/settings/api-tokens`,
|
||||
access: [EUserWorkspaceRoles.ADMIN],
|
||||
highlight: (pathname: string, baseUrl: string) =>
|
||||
pathname === `${baseUrl}/settings/api-tokens/`,
|
||||
},
|
||||
};
|
||||
|
||||
export const WORKSPACE_SETTINGS_LINKS: {
|
||||
key: string;
|
||||
i18n_label: string;
|
||||
href: string;
|
||||
access: EUserWorkspaceRoles[];
|
||||
highlight: (pathname: string, baseUrl: string) => boolean;
|
||||
}[] = [
|
||||
WORKSPACE_SETTINGS["general"],
|
||||
WORKSPACE_SETTINGS["members"],
|
||||
WORKSPACE_SETTINGS["billing-and-plans"],
|
||||
WORKSPACE_SETTINGS["export"],
|
||||
WORKSPACE_SETTINGS["webhooks"],
|
||||
WORKSPACE_SETTINGS["api-tokens"],
|
||||
];
|
||||
|
||||
export const ROLE = {
|
||||
[EUserWorkspaceRoles.GUEST]: "Guest",
|
||||
[EUserWorkspaceRoles.MEMBER]: "Member",
|
||||
[EUserWorkspaceRoles.ADMIN]: "Admin",
|
||||
};
|
||||
|
||||
export const ROLE_DETAILS = {
|
||||
[EUserWorkspaceRoles.GUEST]: {
|
||||
i18n_title: "role_details.guest.title",
|
||||
i18n_description: "role_details.guest.description",
|
||||
},
|
||||
[EUserWorkspaceRoles.MEMBER]: {
|
||||
i18n_title: "role_details.member.title",
|
||||
i18n_description: "role_details.member.description",
|
||||
},
|
||||
[EUserWorkspaceRoles.ADMIN]: {
|
||||
i18n_title: "role_details.admin.title",
|
||||
i18n_description: "role_details.admin.description",
|
||||
},
|
||||
};
|
||||
|
||||
export const USER_ROLES = [
|
||||
{
|
||||
value: "Product / Project Manager",
|
||||
i18n_label: "user_roles.product_or_project_manager",
|
||||
},
|
||||
{
|
||||
value: "Development / Engineering",
|
||||
i18n_label: "user_roles.development_or_engineering",
|
||||
},
|
||||
{
|
||||
value: "Founder / Executive",
|
||||
i18n_label: "user_roles.founder_or_executive",
|
||||
},
|
||||
{
|
||||
value: "Freelancer / Consultant",
|
||||
i18n_label: "user_roles.freelancer_or_consultant",
|
||||
},
|
||||
{ value: "Marketing / Growth", i18n_label: "user_roles.marketing_or_growth" },
|
||||
{
|
||||
value: "Sales / Business Development",
|
||||
i18n_label: "user_roles.sales_or_business_development",
|
||||
},
|
||||
{
|
||||
value: "Support / Operations",
|
||||
i18n_label: "user_roles.support_or_operations",
|
||||
},
|
||||
{
|
||||
value: "Student / Professor",
|
||||
i18n_label: "user_roles.student_or_professor",
|
||||
},
|
||||
{ value: "Human Resources", i18n_label: "user_roles.human_resources" },
|
||||
{ value: "Other", i18n_label: "user_roles.other" },
|
||||
];
|
||||
|
||||
export const IMPORTERS_LIST = [
|
||||
{
|
||||
provider: "github",
|
||||
type: "import",
|
||||
i18n_title: "importer.github.title",
|
||||
i18n_description: "importer.github.description",
|
||||
},
|
||||
{
|
||||
provider: "jira",
|
||||
type: "import",
|
||||
i18n_title: "importer.jira.title",
|
||||
i18n_description: "importer.jira.description",
|
||||
},
|
||||
];
|
||||
|
||||
export const EXPORTERS_LIST = [
|
||||
{
|
||||
provider: "csv",
|
||||
type: "export",
|
||||
i18n_title: "exporter.csv.title",
|
||||
i18n_description: "exporter.csv.description",
|
||||
},
|
||||
{
|
||||
provider: "xlsx",
|
||||
type: "export",
|
||||
i18n_title: "exporter.excel.title",
|
||||
i18n_description: "exporter.csv.description",
|
||||
},
|
||||
{
|
||||
provider: "json",
|
||||
type: "export",
|
||||
i18n_title: "exporter.json.title",
|
||||
i18n_description: "exporter.csv.description",
|
||||
},
|
||||
];
|
||||
|
||||
export const DEFAULT_GLOBAL_VIEWS_LIST: {
|
||||
key: TStaticViewTypes;
|
||||
i18n_label: string;
|
||||
}[] = [
|
||||
{
|
||||
key: "all-issues",
|
||||
i18n_label: "default_global_view.all_issues",
|
||||
},
|
||||
{
|
||||
key: "assigned",
|
||||
i18n_label: "default_global_view.assigned",
|
||||
},
|
||||
{
|
||||
key: "created",
|
||||
i18n_label: "default_global_view.created",
|
||||
},
|
||||
{
|
||||
key: "subscribed",
|
||||
i18n_label: "default_global_view.subscribed",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
"jsx-dom-cjs": "^8.0.3",
|
||||
"linkifyjs": "^4.1.3",
|
||||
"lowlight": "^3.0.0",
|
||||
"lucide-react": "^0.378.0",
|
||||
"lucide-react": "^0.469.0",
|
||||
"prosemirror-codemark": "^0.4.2",
|
||||
"prosemirror-utils": "^1.2.2",
|
||||
"tippy.js": "^6.3.7",
|
||||
|
||||
@@ -202,7 +202,7 @@ export const useEditor = (props: CustomEditorProps) => {
|
||||
getDocument: () => {
|
||||
const documentBinary = provider?.document ? Y.encodeStateAsUpdate(provider?.document) : null;
|
||||
const documentHTML = editor?.getHTML() ?? "<p></p>";
|
||||
const documentJSON = editor.getJSON() ?? null;
|
||||
const documentJSON = editor?.getJSON() ?? null;
|
||||
|
||||
return {
|
||||
binary: documentBinary,
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
{
|
||||
"sidebar": {
|
||||
"projects": "Projects",
|
||||
"pages": "Pages",
|
||||
"new_work_item": "New work item",
|
||||
"home": "Home",
|
||||
"your_work": "Your work",
|
||||
"inbox": "Inbox",
|
||||
"workspace": "Workspace",
|
||||
"views": "Views",
|
||||
"analytics": "Analytics",
|
||||
"work_items": "Work items",
|
||||
"cycles": "Cycles",
|
||||
"modules": "Modules",
|
||||
"intake": "Intake",
|
||||
"drafts": "Drafts",
|
||||
"favorites": "Favorites",
|
||||
"pro": "Pro",
|
||||
"upgrade": "Upgrade"
|
||||
},
|
||||
|
||||
"auth": {
|
||||
"common": {
|
||||
"email": {
|
||||
"label": "Email",
|
||||
"placeholder": "name@company.com",
|
||||
"errors": {
|
||||
"required": "Email is required",
|
||||
"invalid": "Email is invalid"
|
||||
}
|
||||
},
|
||||
"password": {
|
||||
"label": "Password",
|
||||
"set_password": "Set a password",
|
||||
"placeholder": "Enter password",
|
||||
"confirm_password": {
|
||||
"label": "Confirm password",
|
||||
"placeholder": "Confirm password"
|
||||
},
|
||||
"current_password": {
|
||||
"label": "Current password"
|
||||
},
|
||||
"new_password": {
|
||||
"label": "New password",
|
||||
"placeholder": "Enter new password"
|
||||
},
|
||||
"change_password": {
|
||||
"label": {
|
||||
"default": "Change password",
|
||||
"submitting": "Changing password"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"match": "Passwords don't match",
|
||||
"empty": "Please enter your password",
|
||||
"length": "Password length should me more than 8 characters",
|
||||
"strength": {
|
||||
"weak": "Password is weak",
|
||||
"strong": "Password is strong"
|
||||
}
|
||||
},
|
||||
"submit": "Set password",
|
||||
"toast": {
|
||||
"change_password": {
|
||||
"success": {
|
||||
"title": "Success!",
|
||||
"message": "Password changed successfully."
|
||||
},
|
||||
"error": {
|
||||
"title": "Error!",
|
||||
"message": "Something went wrong. Please try again."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"unique_code": {
|
||||
"label": "Unique code",
|
||||
"placeholder": "gets-sets-flys",
|
||||
"paste_code": "Paste the code sent to your email",
|
||||
"requesting_new_code": "Requesting new code",
|
||||
"sending_code": "Sending code"
|
||||
},
|
||||
"already_have_an_account": "Already have an account?",
|
||||
"login": "Log in",
|
||||
"create_account": "Create an account",
|
||||
"new_to_plane": "New to Plane?",
|
||||
"back_to_sign_in": "Back to sign in",
|
||||
"resend_in": "Resend in {seconds} seconds",
|
||||
"sign_in_with_unique_code": "Sign in with unique code",
|
||||
"forgot_password": "Forgot your password?"
|
||||
},
|
||||
"sign_up": {
|
||||
"header": {
|
||||
"label": "Create an account to start managing work with your team.",
|
||||
"step": {
|
||||
"email": {
|
||||
"header": "Sign up",
|
||||
"sub_header": ""
|
||||
},
|
||||
"password": {
|
||||
"header": "Sign up",
|
||||
"sub_header": "Sign up using an email-password combination."
|
||||
},
|
||||
"unique_code": {
|
||||
"header": "Sign up",
|
||||
"sub_header": "Sign up using a unique code sent to the email address above."
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"password": {
|
||||
"strength": "Try setting-up a strong password to proceed"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sign_in": {
|
||||
"header": {
|
||||
"label": "Log in to start managing work with your team.",
|
||||
"step": {
|
||||
"email": {
|
||||
"header": "Log in or sign up",
|
||||
"sub_header": ""
|
||||
},
|
||||
"password": {
|
||||
"header": "Log in or sign up",
|
||||
"sub_header": "Use your email-password combination to log in."
|
||||
},
|
||||
"unique_code": {
|
||||
"header": "Log in or sign up",
|
||||
"sub_header": "Log in using a unique code sent to the email address above."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"forgot_password": {
|
||||
"title": "Reset your password",
|
||||
"description": "Enter your user account's verified email address and we will send you a password reset link.",
|
||||
"email_sent": "We sent the reset link to your email address",
|
||||
"send_reset_link": "Send reset link",
|
||||
"errors": {
|
||||
"smtp_not_enabled": "We see that your god hasn't enabled SMTP, we will not be able to send a password reset link"
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"title": "Email sent",
|
||||
"message": "Check your inbox for a link to reset your password. If it doesn't appear within a few minutes, check your spam folder."
|
||||
},
|
||||
"error": {
|
||||
"title": "Error!",
|
||||
"message": "Something went wrong. Please try again."
|
||||
}
|
||||
}
|
||||
},
|
||||
"reset_password": {
|
||||
"title": "Set new password",
|
||||
"description": "Secure your account with a strong password"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "Secure your account",
|
||||
"description": "Setting password helps you login securely"
|
||||
},
|
||||
"sign_out": {
|
||||
"toast": {
|
||||
"error": {
|
||||
"title": "Error!",
|
||||
"message": "Failed to sign out. Please try again."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,11 @@
|
||||
import IntlMessageFormat from "intl-messageformat";
|
||||
import get from "lodash/get";
|
||||
import { makeAutoObservable } from "mobx";
|
||||
import merge from "lodash/merge";
|
||||
import { makeAutoObservable, runInAction } from "mobx";
|
||||
// constants
|
||||
import { FALLBACK_LANGUAGE, SUPPORTED_LANGUAGES, STORAGE_KEY } from "../constants";
|
||||
// core translations imports
|
||||
import coreEn from "../locales/en/core.json";
|
||||
// types
|
||||
import { TLanguage, ILanguageOption, ITranslations } from "../types";
|
||||
|
||||
@@ -12,42 +15,35 @@ import { TLanguage, ILanguageOption, ITranslations } from "../types";
|
||||
* Uses IntlMessageFormat to format the translations
|
||||
*/
|
||||
export class TranslationStore {
|
||||
// Core translations that are always loaded
|
||||
private coreTranslations: ITranslations = {
|
||||
en: coreEn,
|
||||
};
|
||||
// List of translations for each language
|
||||
private translations: ITranslations = {};
|
||||
// Cache for IntlMessageFormat instances
|
||||
private messageCache: Map<string, IntlMessageFormat> = new Map();
|
||||
// Current language
|
||||
currentLocale: TLanguage = FALLBACK_LANGUAGE;
|
||||
// Loading state
|
||||
isLoading: boolean = true;
|
||||
isInitialized: boolean = false;
|
||||
// Set of loaded languages
|
||||
private loadedLanguages: Set<TLanguage> = new Set();
|
||||
|
||||
/**
|
||||
* Constructor for the TranslationStore class
|
||||
*/
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
// Initialize with core translations immediately
|
||||
this.translations = this.coreTranslations;
|
||||
// Initialize language
|
||||
this.initializeLanguage();
|
||||
// Load all the translations
|
||||
this.loadTranslations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads translations from JSON files and initializes the message cache
|
||||
*/
|
||||
private async loadTranslations() {
|
||||
try {
|
||||
// dynamic import of translations
|
||||
const translations = {
|
||||
en: (await import("../locales/en/translations.json")).default,
|
||||
fr: (await import("../locales/fr/translations.json")).default,
|
||||
es: (await import("../locales/es/translations.json")).default,
|
||||
ja: (await import("../locales/ja/translations.json")).default,
|
||||
"zh-CN": (await import("../locales/zh-CN/translations.json")).default,
|
||||
};
|
||||
this.translations = translations;
|
||||
this.messageCache.clear(); // Clear cache when translations change
|
||||
} catch (error) {
|
||||
console.error("Failed to load translations:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/** Initializes the language based on the local storage or browser language */
|
||||
private initializeLanguage() {
|
||||
if (typeof window === "undefined") return;
|
||||
@@ -62,6 +58,100 @@ export class TranslationStore {
|
||||
this.setLanguage(browserLang);
|
||||
}
|
||||
|
||||
/** Loads the translations for the current language */
|
||||
private async loadTranslations(): Promise<void> {
|
||||
try {
|
||||
// Set initialized to true (Core translations are already loaded)
|
||||
runInAction(() => {
|
||||
this.isInitialized = true;
|
||||
});
|
||||
// Load current and fallback languages in parallel
|
||||
await this.loadPrimaryLanguages();
|
||||
// Load all remaining languages in parallel
|
||||
this.loadRemainingLanguages();
|
||||
} catch (error) {
|
||||
console.error("Failed in translation initialization:", error);
|
||||
runInAction(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async loadPrimaryLanguages(): Promise<void> {
|
||||
try {
|
||||
// Load current and fallback languages in parallel
|
||||
const languagesToLoad = new Set<TLanguage>([this.currentLocale]);
|
||||
// Add fallback language only if different from current
|
||||
if (this.currentLocale !== FALLBACK_LANGUAGE) {
|
||||
languagesToLoad.add(FALLBACK_LANGUAGE);
|
||||
}
|
||||
// Load all primary languages in parallel
|
||||
const loadPromises = Array.from(languagesToLoad).map((lang) => this.loadLanguageTranslations(lang));
|
||||
await Promise.all(loadPromises);
|
||||
// Update loading state
|
||||
runInAction(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to load primary languages:", error);
|
||||
runInAction(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private loadRemainingLanguages(): void {
|
||||
const remainingLanguages = SUPPORTED_LANGUAGES.map((lang) => lang.value).filter(
|
||||
(lang) =>
|
||||
!this.loadedLanguages.has(lang as TLanguage) && lang !== this.currentLocale && lang !== FALLBACK_LANGUAGE
|
||||
);
|
||||
// Load all remaining languages in parallel
|
||||
Promise.all(remainingLanguages.map((lang) => this.loadLanguageTranslations(lang as TLanguage))).catch((error) => {
|
||||
console.error("Failed to load some remaining languages:", error);
|
||||
});
|
||||
}
|
||||
|
||||
private async loadLanguageTranslations(language: TLanguage): Promise<void> {
|
||||
// Skip if already loaded
|
||||
if (this.loadedLanguages.has(language)) return;
|
||||
|
||||
try {
|
||||
const translations = await this.importLanguageFile(language);
|
||||
runInAction(() => {
|
||||
// Use lodash merge for deep merging
|
||||
this.translations[language] = merge({}, this.coreTranslations[language] || {}, translations.default);
|
||||
// Add to loaded languages
|
||||
this.loadedLanguages.add(language);
|
||||
// Clear cache
|
||||
this.messageCache.clear();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Failed to load translations for ${language}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the translations for the given language
|
||||
* @param language - The language to import the translations for
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
private importLanguageFile(language: TLanguage): Promise<any> {
|
||||
switch (language) {
|
||||
case "en":
|
||||
return import("../locales/en/translations.json");
|
||||
case "fr":
|
||||
return import("../locales/fr/translations.json");
|
||||
case "es":
|
||||
return import("../locales/es/translations.json");
|
||||
case "ja":
|
||||
return import("../locales/ja/translations.json");
|
||||
case "zh-CN":
|
||||
return import("../locales/zh-CN/translations.json");
|
||||
default:
|
||||
throw new Error(`Unsupported language: ${language}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks if the language is valid based on the supported languages */
|
||||
private isValidLanguage(lang: string | null): lang is TLanguage {
|
||||
return lang !== null && this.availableLanguages.some((l) => l.value === lang);
|
||||
@@ -173,20 +263,26 @@ export class TranslationStore {
|
||||
* Sets the current language and updates the translations
|
||||
* @param lng - The new language
|
||||
*/
|
||||
setLanguage(lng: TLanguage): void {
|
||||
async setLanguage(lng: TLanguage): Promise<void> {
|
||||
try {
|
||||
if (!this.isValidLanguage(lng)) {
|
||||
throw new Error(`Invalid language: ${lng}`);
|
||||
}
|
||||
|
||||
// Safeguard in case background loading failed
|
||||
if (!this.loadedLanguages.has(lng)) {
|
||||
await this.loadLanguageTranslations(lng);
|
||||
}
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.setItem(STORAGE_KEY, lng);
|
||||
}
|
||||
this.currentLocale = lng;
|
||||
this.messageCache.clear(); // Clear cache when language changes
|
||||
if (typeof window !== "undefined") {
|
||||
document.documentElement.lang = lng;
|
||||
}
|
||||
|
||||
runInAction(() => {
|
||||
this.currentLocale = lng;
|
||||
this.messageCache.clear(); // Clear cache when language changes
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to set language:", error);
|
||||
}
|
||||
|
||||
Vendored
+2
@@ -24,3 +24,5 @@ export type TLogoProps = {
|
||||
};
|
||||
|
||||
export type TNameDescriptionLoader = "submitting" | "submitted" | "saved";
|
||||
|
||||
export type TFetchStatus = "partial" | "complete" | undefined;
|
||||
|
||||
Vendored
+1
-32
@@ -2,23 +2,6 @@ import { TPaginationInfo } from "./common";
|
||||
import { TIssuePriorities } from "./issues";
|
||||
import { TIssue } from "./issues/base";
|
||||
|
||||
enum EInboxIssueCurrentTab {
|
||||
OPEN = "open",
|
||||
CLOSED = "closed",
|
||||
}
|
||||
|
||||
enum EInboxIssueStatus {
|
||||
PENDING = -2,
|
||||
DECLINED = -1,
|
||||
SNOOZED = 0,
|
||||
ACCEPTED = 1,
|
||||
DUPLICATE = 2,
|
||||
}
|
||||
|
||||
export type TInboxIssueCurrentTab = EInboxIssueCurrentTab;
|
||||
|
||||
export type TInboxIssueStatus = EInboxIssueStatus;
|
||||
|
||||
// filters
|
||||
export type TInboxIssueFilterMemberKeys = "assignees" | "created_by";
|
||||
|
||||
@@ -38,10 +21,7 @@ export type TInboxIssueFilter = {
|
||||
// sorting filters
|
||||
export type TInboxIssueSortingKeys = "order_by" | "sort_by";
|
||||
|
||||
export type TInboxIssueSortingOrderByKeys =
|
||||
| "issue__created_at"
|
||||
| "issue__updated_at"
|
||||
| "issue__sequence_id";
|
||||
export type TInboxIssueSortingOrderByKeys = "issue__created_at" | "issue__updated_at" | "issue__sequence_id";
|
||||
|
||||
export type TInboxIssueSortingSortByKeys = "asc" | "desc";
|
||||
|
||||
@@ -78,17 +58,6 @@ export type TInboxDuplicateIssueDetails = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type TInboxIssue = {
|
||||
id: string;
|
||||
status: TInboxIssueStatus;
|
||||
snoozed_till: Date | null;
|
||||
duplicate_to: string | undefined;
|
||||
source: string;
|
||||
issue: TIssue;
|
||||
created_by: string;
|
||||
duplicate_issue_detail: TInboxDuplicateIssueDetails | undefined;
|
||||
};
|
||||
|
||||
export type TInboxIssuePaginationInfo = TPaginationInfo & {
|
||||
total_results: number;
|
||||
};
|
||||
|
||||
Vendored
+1
@@ -26,6 +26,7 @@ export * from "./waitlist";
|
||||
export * from "./webhook";
|
||||
export * from "./workspace-views";
|
||||
export * from "./common";
|
||||
export * from "./power-k";
|
||||
export * from "./pragmatic";
|
||||
export * from "./publish";
|
||||
export * from "./search";
|
||||
|
||||
Vendored
+1
-1
@@ -15,7 +15,7 @@ import type {
|
||||
TIssueGroupByOptions,
|
||||
TIssueOrderByOptions,
|
||||
TIssueGroupingFilters,
|
||||
TIssueExtraOptions
|
||||
TIssueExtraOptions,
|
||||
} from "@plane/types";
|
||||
|
||||
export interface IIssueCycle {
|
||||
|
||||
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
export type TPowerKPageKeys =
|
||||
// work-item actions
|
||||
| "change-work-item-assignee"
|
||||
| "change-work-item-priority"
|
||||
| "change-work-item-state"
|
||||
// module actions
|
||||
| "change-module-member"
|
||||
| "change-module-status"
|
||||
// configs
|
||||
| "workspace-settings"
|
||||
| "project-settings"
|
||||
| "profile-settings"
|
||||
// personalization
|
||||
| "change-theme";
|
||||
|
||||
export type TPowerKCreateActionKeys = "cycle" | "issue" | "module" | "page" | "project" | "view" | "workspace";
|
||||
export type TPowerKCreateAction = {
|
||||
key: TPowerKCreateActionKeys;
|
||||
icon: any;
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
shortcut?: string;
|
||||
shouldRender?: boolean;
|
||||
};
|
||||
+52
-45
@@ -1,3 +1,4 @@
|
||||
import { EUserProjectRoles } from "@plane/constants";
|
||||
import type {
|
||||
IProjectViewProps,
|
||||
IUser,
|
||||
@@ -10,58 +11,64 @@ import type {
|
||||
} from "..";
|
||||
import { TUserPermissions } from "../enums";
|
||||
|
||||
export interface IProject {
|
||||
archive_in: number;
|
||||
export interface IPartialProject {
|
||||
id: string;
|
||||
name: string;
|
||||
identifier: string;
|
||||
sort_order: number | null;
|
||||
logo_props: TLogoProps;
|
||||
member_role?: TUserPermissions | EUserProjectRoles | null;
|
||||
archived_at: string | null;
|
||||
archived_issues: number;
|
||||
archived_sub_issues: number;
|
||||
completed_issues: number;
|
||||
close_in: number;
|
||||
created_at: Date;
|
||||
created_by: string;
|
||||
// only for uploading the cover image
|
||||
cover_image_asset?: null;
|
||||
cover_image?: string;
|
||||
// only for rendering the cover image
|
||||
cover_image_url: readonly string;
|
||||
workspace: IWorkspace | string;
|
||||
cycle_view: boolean;
|
||||
issue_views_view: boolean;
|
||||
module_view: boolean;
|
||||
page_view: boolean;
|
||||
inbox_view: boolean;
|
||||
default_assignee: IUser | string | null;
|
||||
default_state: string | null;
|
||||
description: string;
|
||||
draft_issues: number;
|
||||
draft_sub_issues: number;
|
||||
estimate: string | null;
|
||||
guest_view_all_features: boolean;
|
||||
id: string;
|
||||
identifier: string;
|
||||
anchor: string | null;
|
||||
is_favorite: boolean;
|
||||
is_issue_type_enabled: boolean;
|
||||
is_member: boolean;
|
||||
is_time_tracking_enabled: boolean;
|
||||
logo_props: TLogoProps;
|
||||
member_role: TUserPermissions | null;
|
||||
members: IProjectMemberLite[];
|
||||
name: string;
|
||||
network: number;
|
||||
project_lead: IUserLite | string | null;
|
||||
sort_order: number | null;
|
||||
sub_issues: number;
|
||||
total_cycles: number;
|
||||
total_issues: number;
|
||||
total_members: number;
|
||||
total_modules: number;
|
||||
updated_at: Date;
|
||||
updated_by: string;
|
||||
workspace: IWorkspace | string;
|
||||
workspace_detail: IWorkspaceLite;
|
||||
timezone: string;
|
||||
project_lead?: IUserLite | string | null;
|
||||
// Timestamps
|
||||
created_at?: Date;
|
||||
updated_at?: Date;
|
||||
// actor
|
||||
created_by?: string;
|
||||
updated_by?: string;
|
||||
}
|
||||
|
||||
export interface IProject extends IPartialProject {
|
||||
archive_in?: number;
|
||||
close_in?: number;
|
||||
// only for uploading the cover image
|
||||
cover_image_asset?: null;
|
||||
cover_image?: string;
|
||||
// only for rendering the cover image
|
||||
readonly cover_image_url?: string;
|
||||
default_assignee?: IUser | string | null;
|
||||
default_state?: string | null;
|
||||
description?: string;
|
||||
estimate?: string | null;
|
||||
guest_view_all_features?: boolean;
|
||||
anchor?: string | null;
|
||||
is_favorite?: boolean;
|
||||
is_issue_type_enabled?: boolean;
|
||||
is_time_tracking_enabled?: boolean;
|
||||
members?: string[];
|
||||
network?: number;
|
||||
timezone?: string;
|
||||
}
|
||||
|
||||
export type TProjectAnalyticsCountParams = {
|
||||
project_ids?: string;
|
||||
fields?: string;
|
||||
};
|
||||
|
||||
export type TProjectAnalyticsCount = Pick<IProject, "id"> & {
|
||||
total_issues?: number;
|
||||
completed_issues?: number;
|
||||
total_cycles?: number;
|
||||
total_members?: number;
|
||||
total_modules?: number;
|
||||
};
|
||||
|
||||
export interface IProjectLite {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -112,7 +119,7 @@ export interface IProjectMembership {
|
||||
}
|
||||
|
||||
export interface IProjectBulkAddFormData {
|
||||
members: { role: TUserPermissions; member_id: string }[];
|
||||
members: { role: TUserPermissions | EUserProjectRoles; member_id: string }[];
|
||||
}
|
||||
|
||||
export interface IGithubRepository {
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { EViewAccess } from "@/constants/views";
|
||||
import { EViewAccess } from "@plane/constants";
|
||||
import { TLogoProps } from "./common";
|
||||
import {
|
||||
IIssueDisplayFilterOptions,
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { EViewAccess } from "@/constants/views";
|
||||
import { EViewAccess } from "@plane/constants";
|
||||
import {
|
||||
IWorkspaceViewProps,
|
||||
IIssueDisplayFilterOptions,
|
||||
|
||||
Vendored
+3
-2
@@ -5,6 +5,7 @@ import type {
|
||||
IUserLite,
|
||||
IWorkspaceViewProps,
|
||||
} from "@plane/types";
|
||||
import { EUserWorkspaceRoles } from "@plane/constants"; // TODO: check if importing this over here causes circular dependency
|
||||
import { TUserPermissions } from "./enums";
|
||||
|
||||
export interface IWorkspace {
|
||||
@@ -69,7 +70,7 @@ export type Properties = {
|
||||
export interface IWorkspaceMember {
|
||||
id: string;
|
||||
member: IUserLite;
|
||||
role: TUserPermissions;
|
||||
role: TUserPermissions | EUserWorkspaceRoles;
|
||||
created_at?: string;
|
||||
avatar_url?: string;
|
||||
email?: string;
|
||||
@@ -87,7 +88,7 @@ export interface IWorkspaceMemberMe {
|
||||
default_props: IWorkspaceViewProps;
|
||||
id: string;
|
||||
member: string;
|
||||
role: TUserPermissions;
|
||||
role: TUserPermissions | EUserWorkspaceRoles;
|
||||
updated_at: Date;
|
||||
updated_by: string;
|
||||
view_props: IWorkspaceViewProps;
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"clsx": "^2.0.0",
|
||||
"emoji-picker-react": "^4.5.16",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.379.0",
|
||||
"lucide-react": "^0.469.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-popper": "^2.3.0",
|
||||
"sonner": "^1.4.41",
|
||||
|
||||
@@ -15,7 +15,7 @@ export const metadata: Metadata = {
|
||||
url: "https://sites.plane.so/",
|
||||
},
|
||||
keywords:
|
||||
"software development, customer feedback, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration",
|
||||
"software development, customer feedback, software, accelerate, code management, release management, project management, work item tracking, agile, scrum, kanban, collaboration",
|
||||
twitter: {
|
||||
site: "@planepowers",
|
||||
},
|
||||
@@ -32,7 +32,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
<link rel="shortcut icon" href={`${SPACE_BASE_PATH}/favicon/favicon.ico`} />
|
||||
</head>
|
||||
<body>
|
||||
<AppProvider>{children}</AppProvider>
|
||||
<AppProvider>
|
||||
<>{children}</>
|
||||
</AppProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { FC, ReactNode } from "react";
|
||||
// components
|
||||
import { TranslationProvider } from "@plane/i18n";
|
||||
import { InstanceProvider } from "@/lib/instance-provider";
|
||||
import { StoreProvider } from "@/lib/store-provider";
|
||||
import { ToastProvider } from "@/lib/toast-provider";
|
||||
@@ -15,9 +16,11 @@ export const AppProvider: FC<IAppProvider> = (props) => {
|
||||
|
||||
return (
|
||||
<StoreProvider>
|
||||
<ToastProvider>
|
||||
<InstanceProvider>{children}</InstanceProvider>
|
||||
</ToastProvider>
|
||||
<TranslationProvider>
|
||||
<ToastProvider>
|
||||
<InstanceProvider>{children}</InstanceProvider>
|
||||
</ToastProvider>
|
||||
</TranslationProvider>
|
||||
</StoreProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ const Titles: TAuthHeaderDetails = {
|
||||
},
|
||||
[EAuthModes.SIGN_UP]: {
|
||||
header: "View, comment, and do more",
|
||||
subHeader: "Sign up or log in to work with Plane Issues and Pages.",
|
||||
subHeader: "Sign up or log in to work with Plane work items and Pages.",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -38,7 +38,7 @@ export const AuthHeader: FC<TAuthHeader> = (props) => {
|
||||
}
|
||||
|
||||
return {
|
||||
header: "Comment or react to issues",
|
||||
header: "Comment or react to work itemss",
|
||||
subHeader: "Use plane to add your valuable inputs to features.",
|
||||
};
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ export const InstanceFailureView: FC = () => {
|
||||
<h3 className="font-medium text-2xl text-white ">Unable to fetch instance details.</h3>
|
||||
<p className="font-medium text-base text-center">
|
||||
We were unable to fetch the details of the instance. <br />
|
||||
Fret not, it might just be a connectivity issue.
|
||||
Fret not, it might just be a connectivity work items.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { X } from "lucide-react";
|
||||
// types
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TFilters } from "@/types/issue";
|
||||
// components
|
||||
import { AppliedPriorityFilters } from "./priority";
|
||||
@@ -18,6 +19,7 @@ export const replaceUnderscoreIfSnakeCase = (str: string) => str.replace(/_/g, "
|
||||
|
||||
export const AppliedFiltersList: React.FC<Props> = observer((props) => {
|
||||
const { appliedFilters = {}, handleRemoveAllFilters, handleRemoveFilter } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-stretch gap-2">
|
||||
@@ -72,7 +74,7 @@ export const AppliedFiltersList: React.FC<Props> = observer((props) => {
|
||||
onClick={handleRemoveAllFilters}
|
||||
className="flex items-center gap-2 rounded-md border border-custom-border-200 px-2 py-1 text-xs text-custom-text-300 hover:text-custom-text-200"
|
||||
>
|
||||
Clear all
|
||||
{t("common.clear_all")}
|
||||
<X size={12} strokeWidth={2} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// ui
|
||||
import { ISSUE_PRIORITY_FILTERS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { PriorityIcon } from "@plane/ui";
|
||||
// components
|
||||
import { FilterHeader, FilterOption } from "./helpers";
|
||||
@@ -18,6 +19,9 @@ type Props = {
|
||||
export const FilterPriority: React.FC<Props> = observer((props) => {
|
||||
const { appliedFilters, handleUpdate, searchQuery } = props;
|
||||
|
||||
// hooks
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||
|
||||
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
||||
@@ -40,11 +44,11 @@ export const FilterPriority: React.FC<Props> = observer((props) => {
|
||||
isChecked={appliedFilters?.includes(priority.key) ? true : false}
|
||||
onClick={() => handleUpdate(priority.key)}
|
||||
icon={<PriorityIcon priority={priority.key} className="h-3.5 w-3.5" />}
|
||||
title={priority.title}
|
||||
title={t(priority.titleTranslationKey)}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<p className="text-xs italic text-custom-text-400">No matches found</p>
|
||||
<p className="text-xs italic text-custom-text-400">{t("common.search.no_matches_found")}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -26,7 +26,7 @@ export const IssueLayoutHOC = observer((props: Props) => {
|
||||
}
|
||||
|
||||
if (getGroupIssueCount(undefined, undefined, false) === 0) {
|
||||
return <div className="flex w-full h-full items-center justify-center">No Issues Found</div>;
|
||||
return <div className="flex w-full h-full items-center justify-center">No work items Found</div>;
|
||||
}
|
||||
|
||||
return <>{props.children}</>;
|
||||
|
||||
@@ -94,7 +94,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
<div className="sticky top-0 z-[2] w-full flex-shrink-0 bg-custom-background-90 py-1">
|
||||
<HeaderGroupByCard
|
||||
groupBy={groupBy}
|
||||
icon={subList.icon}
|
||||
icon={subList.icon as any}
|
||||
title={subList.name}
|
||||
count={getGroupIssueCount(subList.id, undefined, false) ?? 0}
|
||||
/>
|
||||
|
||||
@@ -131,10 +131,14 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = observer(
|
||||
const subGroupByVisibilityToggle = visibilitySubGroupByGroupCount(groupCount, showEmptyGroup);
|
||||
|
||||
if (subGroupByVisibilityToggle === false) return <></>;
|
||||
|
||||
return (
|
||||
<div key={`${subGroupBy}_${group.id}`} className="flex w-[350px] flex-shrink-0 flex-col">
|
||||
<HeaderGroupByCard groupBy={groupBy} icon={group.icon} title={group.name} count={groupCount} />
|
||||
<HeaderGroupByCard
|
||||
groupBy={groupBy}
|
||||
icon={group.icon}
|
||||
title={group.name}
|
||||
count={groupCount}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -262,7 +266,7 @@ const SubGroup: React.FC<ISubGroup> = observer((props) => {
|
||||
<div className="sticky top-[50px] z-[3] py-1 flex w-full items-center bg-custom-background-100 border-y-[0.5px] border-custom-border-200">
|
||||
<div className="sticky left-0 flex-shrink-0">
|
||||
<HeaderSubGroupByCard
|
||||
icon={group.icon}
|
||||
icon={group.icon as any}
|
||||
title={group.name || ""}
|
||||
count={issueCount}
|
||||
isExpanded={isExpanded}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { Fragment, MutableRefObject, forwardRef, useRef, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// plane types
|
||||
import { IGroupByColumn, TIssueGroupByOptions, IIssueDisplayProperties, TPaginationData, TLoader } from "@plane/types";
|
||||
// plane utils
|
||||
@@ -62,6 +63,8 @@ export const ListGroup = observer((props: Props) => {
|
||||
} = props;
|
||||
const [isExpanded, setIsExpanded] = useState(true);
|
||||
const groupRef = useRef<HTMLDivElement | null>(null);
|
||||
// hooks
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [intersectionElement, setIntersectionElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
@@ -85,7 +88,7 @@ export const ListGroup = observer((props: Props) => {
|
||||
}
|
||||
onClick={() => loadMoreIssues(group.id)}
|
||||
>
|
||||
Load More ↓
|
||||
{t("common.load_more")} ↓
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
||||
displayPropertyKey="sub_issue_count"
|
||||
shouldRenderProperty={(properties) => !!properties.sub_issue_count && !!issue.sub_issues_count}
|
||||
>
|
||||
<Tooltip tooltipHeading="Sub-issues" tooltipContent={`${issue.sub_issues_count}`}>
|
||||
<Tooltip tooltipHeading="Sub-work items" tooltipContent={`${issue.sub_issues_count}`}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { TIssuePriorities } from "@plane/types";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
@@ -13,17 +14,19 @@ export const IssueBlockPriority = ({
|
||||
priority: TIssuePriorities | null;
|
||||
shouldShowName?: boolean;
|
||||
}) => {
|
||||
// hooks
|
||||
const { t } = useTranslation();
|
||||
const priority_detail = priority != null ? getIssuePriorityFilters(priority) : null;
|
||||
|
||||
if (priority_detail === null) return <></>;
|
||||
|
||||
return (
|
||||
<Tooltip tooltipHeading="Priority" tooltipContent={priority_detail?.title}>
|
||||
<Tooltip tooltipHeading="Priority" tooltipContent={t(priority_detail?.titleTranslationKey || "")}>
|
||||
<div className="flex items-center relative w-full h-full">
|
||||
<div className={`grid h-5 w-5 place-items-center rounded border-[0.5px] gap-2 ${priority_detail?.className}`}>
|
||||
<span className="material-symbols-rounded text-sm">{priority_detail?.icon}</span>
|
||||
</div>
|
||||
{shouldShowName && <span className="pl-2 text-sm">{priority_detail?.title}</span>}
|
||||
{shouldShowName && <span className="pl-2 text-sm">{t(priority_detail?.titleTranslationKey || "")}</span>}
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -50,7 +50,7 @@ export const getGroupByColumns = (
|
||||
case "created_by":
|
||||
return getCreatedByColumns(member) as any;
|
||||
default:
|
||||
if (includeNone) return [{ id: `All Issues`, name: `All Issues`, payload: {}, icon: undefined }];
|
||||
if (includeNone) return [{ id: `All Issues`, name: `All work items`, payload: {}, icon: undefined }];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import { observer } from "mobx-react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
// ui
|
||||
import { SITES_ISSUE_LAYOUTS } from "@plane/constants";
|
||||
// plane i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// helpers
|
||||
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
||||
@@ -19,6 +21,8 @@ type Props = {
|
||||
|
||||
export const IssuesLayoutSelection: FC<Props> = observer((props) => {
|
||||
const { anchor } = props;
|
||||
// hooks
|
||||
const { t } = useTranslation();
|
||||
// router
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
@@ -45,7 +49,7 @@ export const IssuesLayoutSelection: FC<Props> = observer((props) => {
|
||||
if (!layoutOptions[layout.key]) return;
|
||||
|
||||
return (
|
||||
<Tooltip key={layout.key} tooltipContent={layout.title}>
|
||||
<Tooltip key={layout.key} tooltipContent={t(layout.titleTranslationKey)}>
|
||||
<button
|
||||
type="button"
|
||||
className={`group grid h-[22px] w-7 place-items-center overflow-hidden rounded transition-all hover:bg-custom-background-100 ${
|
||||
|
||||
@@ -50,7 +50,7 @@ export const PeekOverviewHeader: React.FC<Props> = observer((props) => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Link copied!",
|
||||
message: "Issue link copied to clipboard.",
|
||||
message: "Work item link copied to clipboard.",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { CalendarCheck2, Signal } from "lucide-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { DoubleCircleIcon, StateGroupIcon, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { getIssuePriorityFilters } from "@plane/utils";
|
||||
@@ -24,6 +25,8 @@ type Props = {
|
||||
};
|
||||
|
||||
export const PeekOverviewIssueProperties: React.FC<Props> = observer(({ issueDetails, mode }) => {
|
||||
// hooks
|
||||
const { t } = useTranslation();
|
||||
const { getStateById } = useStates();
|
||||
const state = getStateById(issueDetails?.state_id ?? undefined);
|
||||
|
||||
@@ -40,7 +43,7 @@ export const PeekOverviewIssueProperties: React.FC<Props> = observer(({ issueDet
|
||||
setToast({
|
||||
type: TOAST_TYPE.INFO,
|
||||
title: "Link copied!",
|
||||
message: "Issue link copied to clipboard",
|
||||
message: "Work item link copied to clipboard",
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -95,7 +98,7 @@ export const PeekOverviewIssueProperties: React.FC<Props> = observer(({ issueDet
|
||||
<Icon iconName={priority?.icon} />
|
||||
</span>
|
||||
)}
|
||||
<span>{priority?.title ?? "None"}</span>
|
||||
<span>{t(priority?.titleTranslationKey || "common.none")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+3
-2
@@ -20,10 +20,11 @@
|
||||
"@mui/material": "^5.14.1",
|
||||
"@plane/constants": "*",
|
||||
"@plane/editor": "*",
|
||||
"@plane/i18n": "*",
|
||||
"@plane/types": "*",
|
||||
"@plane/ui": "*",
|
||||
"@plane/services": "*",
|
||||
"@sentry/nextjs": "^8.32.0",
|
||||
"@sentry/nextjs": "^8.54.0",
|
||||
"axios": "^1.7.9",
|
||||
"clsx": "^2.0.0",
|
||||
"date-fns": "^4.1.0",
|
||||
@@ -31,7 +32,7 @@
|
||||
"dotenv": "^16.3.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lowlight": "^2.9.0",
|
||||
"lucide-react": "^0.378.0",
|
||||
"lucide-react": "^0.469.0",
|
||||
"mobx": "^6.10.0",
|
||||
"mobx-react": "^9.1.1",
|
||||
"mobx-utils": "^6.0.8",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "Plane Space",
|
||||
"short_name": "Plane Space",
|
||||
"description": "Plane helps you plan your issues, cycles, and product modules.",
|
||||
"description": "Plane helps you plan your work items, cycles, and product modules.",
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"background_color": "#f9fafb",
|
||||
|
||||
@@ -41,7 +41,12 @@ export const WorkspaceAnalyticsHeader = observer(() => {
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.BreadcrumbItem
|
||||
type="text"
|
||||
link={<BreadcrumbLink label={t("analytics")} icon={<BarChart2 className="h-4 w-4 text-custom-text-300" />} />}
|
||||
link={
|
||||
<BreadcrumbLink
|
||||
label={t("workspace_analytics.label")}
|
||||
icon={<BarChart2 className="h-4 w-4 text-custom-text-300" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Breadcrumbs>
|
||||
{analytics_tab === "custom" ? (
|
||||
|
||||
@@ -5,27 +5,40 @@ import { observer } from "mobx-react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Tab } from "@headlessui/react";
|
||||
// plane package imports
|
||||
import { ANALYTICS_TABS } from "@plane/constants";
|
||||
import { ANALYTICS_TABS, EUserPermissionsLevel, EUserPermissions } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Header, EHeaderVariant } from "@plane/ui";
|
||||
// components
|
||||
import { CustomAnalytics, ScopeAndDemand } from "@/components/analytics";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state";
|
||||
// hooks
|
||||
import { useCommandPalette, useEventTracker, useProject, useWorkspace } from "@/hooks/store";
|
||||
import { useCommandPalette, useEventTracker, useProject, useUserPermissions, useWorkspace } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
|
||||
const AnalyticsPage = observer(() => {
|
||||
const searchParams = useSearchParams();
|
||||
const analytics_tab = searchParams.get("analytics_tab");
|
||||
// plane imports
|
||||
const { t } = useTranslation();
|
||||
// store hooks
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { workspaceProjectIds, loader } = useProject();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// helper hooks
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/analytics" });
|
||||
// derived values
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Analytics` : undefined;
|
||||
const pageTitle = currentWorkspace?.name
|
||||
? t(`workspace_analytics.page_label`, { workspace: currentWorkspace?.name })
|
||||
: undefined;
|
||||
|
||||
// permissions
|
||||
const canPerformEmptyStateActions = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
);
|
||||
|
||||
// TODO: refactor loader implementation
|
||||
return (
|
||||
@@ -33,7 +46,7 @@ const AnalyticsPage = observer(() => {
|
||||
<PageHead title={pageTitle} />
|
||||
{workspaceProjectIds && (
|
||||
<>
|
||||
{workspaceProjectIds.length > 0 || loader ? (
|
||||
{workspaceProjectIds.length > 0 || loader === "init-loader" ? (
|
||||
<div className="flex h-full flex-col overflow-hidden bg-custom-background-100">
|
||||
<Tab.Group as={Fragment} defaultIndex={analytics_tab === "custom" ? 1 : 0}>
|
||||
<Header variant={EHeaderVariant.SECONDARY}>
|
||||
@@ -46,7 +59,7 @@ const AnalyticsPage = observer(() => {
|
||||
selected ? "text-custom-primary-100 " : "hover:text-custom-text-200"
|
||||
}`}
|
||||
>
|
||||
{tab.title}
|
||||
{t(tab.i18n_title)}
|
||||
<div
|
||||
className={`border absolute bottom-0 right-0 left-0 rounded-t-md ${selected ? "border-custom-primary-100" : "border-transparent group-hover:border-custom-border-200"}`}
|
||||
/>
|
||||
@@ -67,12 +80,22 @@ const AnalyticsPage = observer(() => {
|
||||
</Tab.Group>
|
||||
</div>
|
||||
) : (
|
||||
<EmptyState
|
||||
type={EmptyStateType.WORKSPACE_ANALYTICS}
|
||||
primaryButtonOnClick={() => {
|
||||
setTrackElement("Analytics empty state");
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
<DetailedEmptyState
|
||||
title={t("workspace_analytics.empty_state.general.title")}
|
||||
description={t("workspace_analytics.empty_state.general.description")}
|
||||
assetPath={resolvedPath}
|
||||
customPrimaryButton={
|
||||
<ComicBoxButton
|
||||
label={t("workspace_analytics.empty_state.general.primary_button.text")}
|
||||
title={t("workspace_analytics.empty_state.general.primary_button.comic.title")}
|
||||
description={t("workspace_analytics.empty_state.general.primary_button.comic.description")}
|
||||
onClick={() => {
|
||||
setTrackElement("Analytics empty state");
|
||||
toggleCreateProjectModal(true);
|
||||
}}
|
||||
disabled={!canPerformEmptyStateActions}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { PenSquare } from "lucide-react";
|
||||
import { EIssuesStoreType } from "@plane/constants";
|
||||
import { EIssuesStoreType, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { Breadcrumbs, Button, Header } from "@plane/ui";
|
||||
// components
|
||||
@@ -12,8 +13,6 @@ import { CreateUpdateIssueModal } from "@/components/issues";
|
||||
|
||||
// hooks
|
||||
import { useProject, useUserPermissions, useWorkspaceDraftIssues } from "@/hooks/store";
|
||||
// plane-web
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
export const WorkspaceDraftHeader = observer(() => {
|
||||
// state
|
||||
@@ -22,7 +21,9 @@ export const WorkspaceDraftHeader = observer(() => {
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
const { paginationInfo } = useWorkspaceDraftIssues();
|
||||
const { joinedProjectIds } = useProject();
|
||||
// check if user is authorized to create draft issue
|
||||
|
||||
const { t } = useTranslation();
|
||||
// check if user is authorized to create draft work item
|
||||
const isAuthorizedUser = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
@@ -42,7 +43,9 @@ export const WorkspaceDraftHeader = observer(() => {
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.BreadcrumbItem
|
||||
type="text"
|
||||
link={<BreadcrumbLink label={`Drafts`} icon={<PenSquare className="h-4 w-4 text-custom-text-300" />} />}
|
||||
link={
|
||||
<BreadcrumbLink label={t("drafts")} icon={<PenSquare className="h-4 w-4 text-custom-text-300" />} />
|
||||
}
|
||||
/>
|
||||
</Breadcrumbs>
|
||||
{paginationInfo?.total_count && paginationInfo?.total_count > 0 ? (
|
||||
@@ -62,7 +65,7 @@ export const WorkspaceDraftHeader = observer(() => {
|
||||
onClick={() => setIsDraftIssueModalOpen(true)}
|
||||
disabled={!isAuthorizedUser}
|
||||
>
|
||||
Draft<span className="hidden sm:inline-block"> an issue</span>
|
||||
{t("workspace_draft_issues.draft_an_issue")}
|
||||
</Button>
|
||||
)}
|
||||
</Header.RightItem>
|
||||
|
||||
@@ -7,11 +7,12 @@ import { Home } from "lucide-react";
|
||||
import githubBlackImage from "/public/logos/github-black.png";
|
||||
import githubWhiteImage from "/public/logos/github-white.png";
|
||||
// ui
|
||||
import { GITHUB_REDIRECTED } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Breadcrumbs, Header } from "@plane/ui";
|
||||
// components
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
// constants
|
||||
import { GITHUB_REDIRECTED } from "@/constants/event-tracker";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
|
||||
@@ -19,6 +20,7 @@ export const WorkspaceDashboardHeader = () => {
|
||||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { resolvedTheme } = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -28,7 +30,9 @@ export const WorkspaceDashboardHeader = () => {
|
||||
<Breadcrumbs>
|
||||
<Breadcrumbs.BreadcrumbItem
|
||||
type="text"
|
||||
link={<BreadcrumbLink label="Home" icon={<Home className="h-4 w-4 text-custom-text-300" />} />}
|
||||
link={
|
||||
<BreadcrumbLink label={t("home.title")} icon={<Home className="h-4 w-4 text-custom-text-300" />} />
|
||||
}
|
||||
/>
|
||||
</Breadcrumbs>
|
||||
</div>
|
||||
@@ -51,7 +55,7 @@ export const WorkspaceDashboardHeader = () => {
|
||||
width={16}
|
||||
alt="GitHub Logo"
|
||||
/>
|
||||
<span className="hidden text-xs font-medium sm:hidden md:block">Star us on GitHub</span>
|
||||
<span className="hidden text-xs font-medium sm:hidden md:block">{t("home.star_us_on_github")}</span>
|
||||
</a>
|
||||
</Header.RightItem>
|
||||
</Header>
|
||||
|
||||
@@ -4,21 +4,24 @@ import { useCallback, useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
import { ENotificationLoader, ENotificationQueryParamType } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// components
|
||||
import { LogoSpinner } from "@/components/common";
|
||||
import { PageHead } from "@/components/core";
|
||||
import { EmptyState } from "@/components/empty-state";
|
||||
import { SimpleEmptyState } from "@/components/empty-state";
|
||||
import { InboxContentRoot } from "@/components/inbox";
|
||||
import { IssuePeekOverview } from "@/components/issues";
|
||||
// constants
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
import { ENotificationLoader, ENotificationQueryParamType } from "@/constants/notification";
|
||||
// hooks
|
||||
import { useIssueDetail, useUserPermissions, useWorkspace, useWorkspaceNotifications } from "@/hooks/store";
|
||||
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
|
||||
import { useWorkspaceIssueProperties } from "@/hooks/use-workspace-issue-properties";
|
||||
|
||||
const WorkspaceDashboardPage = observer(() => {
|
||||
const { workspaceSlug } = useParams();
|
||||
// plane hooks
|
||||
const { t } = useTranslation();
|
||||
// hooks
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const {
|
||||
@@ -31,11 +34,14 @@ const WorkspaceDashboardPage = observer(() => {
|
||||
const { fetchUserProjectInfo } = useUserPermissions();
|
||||
const { setPeekIssue } = useIssueDetail();
|
||||
// derived values
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Inbox` : undefined;
|
||||
const pageTitle = currentWorkspace?.name
|
||||
? t("notification.page_label", { workspace: currentWorkspace?.name })
|
||||
: undefined;
|
||||
const { workspace_slug, project_id, issue_id, is_inbox_issue } =
|
||||
notificationLiteByNotificationId(currentSelectedNotificationId);
|
||||
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/intake/issue-detail" });
|
||||
|
||||
// fetching workspace issue properties
|
||||
// fetching workspace work item properties
|
||||
useWorkspaceIssueProperties(workspaceSlug);
|
||||
|
||||
// fetch workspace notifications
|
||||
@@ -82,7 +88,7 @@ const WorkspaceDashboardPage = observer(() => {
|
||||
<div className="w-full h-full overflow-hidden overflow-y-auto">
|
||||
{!currentSelectedNotificationId ? (
|
||||
<div className="w-full h-screen flex justify-center items-center">
|
||||
<EmptyState type={EmptyStateType.NOTIFICATION_DETAIL_EMPTY_STATE} layout="screen-simple" />
|
||||
<SimpleEmptyState title={t("notification.empty_state.detail.title")} assetPath={resolvedPath} />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { PageHead, AppHeader, ContentWrapper } from "@/components/core";
|
||||
import { WorkspaceHomeView } from "@/components/home";
|
||||
// hooks
|
||||
@@ -11,8 +12,9 @@ import { WorkspaceDashboardHeader } from "./header";
|
||||
|
||||
const WorkspaceDashboardPage = observer(() => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Home` : undefined;
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - ${t("home.title")}` : undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// ui
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/ui";
|
||||
// components
|
||||
import { PageHead } from "@/components/core";
|
||||
@@ -10,7 +12,6 @@ import { DownloadActivityButton, WorkspaceActivityListPage } from "@/components/
|
||||
// hooks
|
||||
import { useUserPermissions } from "@/hooks/store";
|
||||
// plane-web constants
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
const PER_PAGE = 100;
|
||||
|
||||
@@ -21,6 +22,8 @@ const ProfileActivityPage = observer(() => {
|
||||
const [resultsCount, setResultsCount] = useState(0);
|
||||
// router
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
//hooks
|
||||
const { t } = useTranslation();
|
||||
|
||||
const updateTotalPages = (count: number) => setTotalPages(count);
|
||||
|
||||
@@ -50,7 +53,7 @@ const ProfileActivityPage = observer(() => {
|
||||
<PageHead title="Profile - Activity" />
|
||||
<div className="flex h-full w-full flex-col overflow-hidden py-5">
|
||||
<div className="flex items-center justify-between gap-2 px-5 md:px-9">
|
||||
<h3 className="text-lg font-medium">Recent activity</h3>
|
||||
<h3 className="text-lg font-medium">{t("profile.stats.recent_activity.title")}</h3>
|
||||
{canDownloadActivity && <DownloadActivityButton />}
|
||||
</div>
|
||||
<div className="vertical-scrollbar scrollbar-md flex h-full flex-col overflow-y-auto px-5 md:px-9">
|
||||
@@ -58,7 +61,7 @@ const ProfileActivityPage = observer(() => {
|
||||
{pageCount < totalPages && resultsCount !== 0 && (
|
||||
<div className="flex w-full items-center justify-center text-xs">
|
||||
<Button variant="accent-primary" size="sm" onClick={handleLoadMore}>
|
||||
Load more
|
||||
{t("common.load_more")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -6,15 +6,15 @@ import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
import { ChevronDown, PanelRight } from "lucide-react";
|
||||
import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IUserProfileProjectSegregation } from "@plane/types";
|
||||
import { Breadcrumbs, Header, CustomMenu, UserActivityIcon } from "@plane/ui";
|
||||
import { BreadcrumbLink } from "@/components/common";
|
||||
// components
|
||||
import { ProfileIssuesFilter } from "@/components/profile";
|
||||
import { PROFILE_ADMINS_TAB, PROFILE_VIEWER_TAB } from "@/constants/profile";
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { useAppTheme, useUser, useUserPermissions } from "@/hooks/store";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
type TUserProfileHeader = {
|
||||
userProjectsData: IUserProfileProjectSegregation | undefined;
|
||||
@@ -30,6 +30,7 @@ export const UserProfileHeader: FC<TUserProfileHeader> = observer((props) => {
|
||||
const { toggleProfileSidebar, profileSidebarCollapsed } = useAppTheme();
|
||||
const { data: currentUser } = useUser();
|
||||
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const isAuthorized = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
@@ -44,7 +45,7 @@ export const UserProfileHeader: FC<TUserProfileHeader> = observer((props) => {
|
||||
|
||||
const isCurrentUser = currentUser?.id === userId;
|
||||
|
||||
const breadcrumbLabel = `${isCurrentUser ? "Your" : userName} Work`;
|
||||
const breadcrumbLabel = isCurrentUser ? t("profile.page_label") : `${userName} ${t("profile.work")}`;
|
||||
|
||||
return (
|
||||
<Header>
|
||||
@@ -86,7 +87,7 @@ export const UserProfileHeader: FC<TUserProfileHeader> = observer((props) => {
|
||||
href={`/${workspaceSlug}/profile/${userId}/${tab.route}`}
|
||||
className="w-full text-custom-text-300"
|
||||
>
|
||||
{tab.label}
|
||||
{t(tab.i18n_label)}
|
||||
</Link>
|
||||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
|
||||
@@ -4,16 +4,15 @@ import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// components
|
||||
import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { AppHeader, ContentWrapper } from "@/components/core";
|
||||
import { ProfileSidebar } from "@/components/profile";
|
||||
// constants
|
||||
import { USER_PROFILE_PROJECT_SEGREGATION } from "@/constants/fetch-keys";
|
||||
import { PROFILE_ADMINS_TAB, PROFILE_VIEWER_TAB } from "@/constants/profile";
|
||||
// hooks
|
||||
import { useUserPermissions } from "@/hooks/store";
|
||||
import useSize from "@/hooks/use-window-size";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
// local components
|
||||
import { UserService } from "@/services/user.service";
|
||||
import { UserProfileHeader } from "./header";
|
||||
@@ -66,7 +65,7 @@ const UseProfileLayout: React.FC<Props> = observer((props) => {
|
||||
<AppHeader
|
||||
header={
|
||||
<UserProfileHeader
|
||||
type={currentTab?.label}
|
||||
type={currentTab?.i18n_label}
|
||||
userProjectsData={userProjectsData}
|
||||
showProfileIssuesFilter={isIssuesTab}
|
||||
/>
|
||||
|
||||
@@ -6,21 +6,30 @@ import { useParams } from "next/navigation";
|
||||
// icons
|
||||
import { ChevronDown } from "lucide-react";
|
||||
// plane constants
|
||||
import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants";
|
||||
import {
|
||||
EIssueLayoutTypes,
|
||||
EIssueFilterType,
|
||||
EIssuesStoreType,
|
||||
ISSUE_LAYOUTS,
|
||||
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||
} from "@plane/constants";
|
||||
// plane i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// components
|
||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue";
|
||||
import { DisplayFiltersSelection, FilterSelection, FiltersDropdown, IssueLayoutIcon } from "@/components/issues";
|
||||
|
||||
// helpers
|
||||
import { isIssueFilterActive } from "@/helpers/filter.helper";
|
||||
// hooks
|
||||
import { useIssues, useLabel } from "@/hooks/store";
|
||||
|
||||
export const ProfileIssuesMobileHeader = observer(() => {
|
||||
// plane i18n
|
||||
const { t } = useTranslation();
|
||||
// router
|
||||
const { workspaceSlug, userId } = useParams();
|
||||
// store hook
|
||||
@@ -112,7 +121,7 @@ export const ProfileIssuesMobileHeader = observer(() => {
|
||||
placement="bottom-start"
|
||||
customButton={
|
||||
<div className="flex flex-center text-sm text-custom-text-200">
|
||||
Layout
|
||||
{t("common.layout")}
|
||||
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200 my-auto" strokeWidth={2} />
|
||||
</div>
|
||||
}
|
||||
@@ -129,19 +138,19 @@ export const ProfileIssuesMobileHeader = observer(() => {
|
||||
}}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<layout.icon className="h-3 w-3" />
|
||||
<div className="text-custom-text-300">{layout.title}</div>
|
||||
<IssueLayoutIcon layout={ISSUE_LAYOUTS[index].key} className="h-3 w-3" />
|
||||
<div className="text-custom-text-300">{t(layout.i18n_title)}</div>
|
||||
</CustomMenu.MenuItem>
|
||||
);
|
||||
})}
|
||||
</CustomMenu>
|
||||
<div className="flex flex-grow items-center justify-center border-l border-custom-border-200 text-sm text-custom-text-200">
|
||||
<FiltersDropdown
|
||||
title="Filters"
|
||||
title={t("common.filters")}
|
||||
placement="bottom-end"
|
||||
menuButton={
|
||||
<div className="flex flex-center text-sm text-custom-text-200">
|
||||
Filters
|
||||
{t("common.filters")}
|
||||
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" strokeWidth={2} />
|
||||
</div>
|
||||
}
|
||||
@@ -149,7 +158,7 @@ export const ProfileIssuesMobileHeader = observer(() => {
|
||||
>
|
||||
<FilterSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.profile_issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.profile_issues[activeLayout] : undefined
|
||||
}
|
||||
filters={issueFilters?.filters ?? {}}
|
||||
handleFiltersUpdate={handleFiltersUpdate}
|
||||
@@ -163,18 +172,18 @@ export const ProfileIssuesMobileHeader = observer(() => {
|
||||
</div>
|
||||
<div className="flex flex-grow items-center justify-center border-l border-custom-border-200 text-sm text-custom-text-200">
|
||||
<FiltersDropdown
|
||||
title="Display"
|
||||
title={t("common.display")}
|
||||
placement="bottom-end"
|
||||
menuButton={
|
||||
<div className="flex flex-center text-sm text-custom-text-200">
|
||||
Display
|
||||
{t("common.display")}
|
||||
<ChevronDown className="ml-2 h-4 w-4 text-custom-text-200" strokeWidth={2} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.profile_issues[activeLayout] : undefined
|
||||
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.profile_issues[activeLayout] : undefined
|
||||
}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
|
||||
@@ -2,12 +2,12 @@ import React from "react";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
|
||||
// components
|
||||
// constants
|
||||
import { Header, EHeaderVariant } from "@plane/ui";
|
||||
import { PROFILE_ADMINS_TAB, PROFILE_VIEWER_TAB } from "@/constants/profile";
|
||||
|
||||
type Props = {
|
||||
isAuthorized: boolean;
|
||||
@@ -33,7 +33,7 @@ export const ProfileNavbar: React.FC<Props> = (props) => {
|
||||
: "border-transparent"
|
||||
}`}
|
||||
>
|
||||
{t(tab.label)}
|
||||
{t(tab.i18n_label)}
|
||||
</span>
|
||||
</Link>
|
||||
))}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// types
|
||||
import { GROUP_CHOICES } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IUserStateDistribution, TStateGroups } from "@plane/types";
|
||||
// components
|
||||
import { ContentWrapper } from "@plane/ui";
|
||||
@@ -16,7 +18,6 @@ import {
|
||||
} from "@/components/profile";
|
||||
// constants
|
||||
import { USER_PROFILE_DATA } from "@/constants/fetch-keys";
|
||||
import { GROUP_CHOICES } from "@/constants/project";
|
||||
// services
|
||||
import { UserService } from "@/services/user.service";
|
||||
|
||||
@@ -26,6 +27,7 @@ const userService = new UserService();
|
||||
export default function ProfileOverviewPage() {
|
||||
const { workspaceSlug, userId } = useParams();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { data: userProfile } = useSWR(
|
||||
workspaceSlug && userId ? USER_PROFILE_DATA(workspaceSlug.toString(), userId.toString()) : null,
|
||||
workspaceSlug && userId ? () => userService.getUserProfileData(workspaceSlug.toString(), userId.toString()) : null
|
||||
@@ -40,7 +42,7 @@ export default function ProfileOverviewPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title="Your work" />
|
||||
<PageHead title={t("profile.page_label")} />
|
||||
<ContentWrapper className="space-y-7">
|
||||
<ProfileStats userProfile={userProfile} />
|
||||
<ProfileWorkload stateDistribution={stateDistribution} />
|
||||
|
||||
+3
-3
@@ -27,7 +27,7 @@ const PROJECT_ARCHIVES_BREADCRUMB_LIST: {
|
||||
};
|
||||
} = {
|
||||
issues: {
|
||||
label: "Issues",
|
||||
label: "Work items",
|
||||
href: "/issues",
|
||||
icon: LayersIcon,
|
||||
},
|
||||
@@ -65,7 +65,7 @@ export const ProjectArchivesHeader: FC<TProps> = observer((props: TProps) => {
|
||||
<Header>
|
||||
<Header.LeftItem>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<Breadcrumbs onBack={router.back} isLoading={loader}>
|
||||
<Breadcrumbs onBack={router.back} isLoading={loader === "init-loader"}>
|
||||
<ProjectBreadcrumb />
|
||||
<Breadcrumbs.BreadcrumbItem
|
||||
type="text"
|
||||
@@ -92,7 +92,7 @@ export const ProjectArchivesHeader: FC<TProps> = observer((props: TProps) => {
|
||||
{activeTab === "issues" && issueCount && issueCount > 0 ? (
|
||||
<Tooltip
|
||||
isMobile={isMobile}
|
||||
tooltipContent={`There are ${issueCount} ${issueCount > 1 ? "issues" : "issue"} in project's archived`}
|
||||
tooltipContent={`There are ${issueCount} ${issueCount > 1 ? "work items" : "work item"} in project's archived`}
|
||||
position="bottom"
|
||||
>
|
||||
<span className="cursor-default flex items-center text-center justify-center px-2.5 py-0.5 flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold rounded-xl">
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user