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

This commit is contained in:
pablohashescobar 2023-11-14 07:26:26 +00:00
commit aca0f5eadb
225 changed files with 2074 additions and 1490 deletions

View File

@ -0,0 +1,57 @@
import os, sys
import boto3
from botocore.exceptions import ClientError
sys.path.append("/code")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production")
import django
django.setup()
def create_bucket():
try:
from django.conf import settings
# Create a session using the credentials from Django settings
session = boto3.session.Session(
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
)
# Create an S3 client using the session
s3_client = session.client('s3', endpoint_url=settings.AWS_S3_ENDPOINT_URL)
bucket_name = settings.AWS_STORAGE_BUCKET_NAME
print("Checking bucket...")
# Check if the bucket exists
s3_client.head_bucket(Bucket=bucket_name)
# If head_bucket does not raise an exception, the bucket exists
print(f"Bucket '{bucket_name}' already exists.")
except ClientError as e:
error_code = int(e.response['Error']['Code'])
bucket_name = settings.AWS_STORAGE_BUCKET_NAME
if error_code == 404:
# Bucket does not exist, create it
print(f"Bucket '{bucket_name}' does not exist. Creating bucket...")
try:
s3_client.create_bucket(Bucket=bucket_name)
print(f"Bucket '{bucket_name}' created successfully.")
except ClientError as create_error:
print(f"Failed to create bucket: {create_error}")
elif error_code == 403:
# Access to the bucket is forbidden
print(f"Access to the bucket '{bucket_name}' is forbidden. Check permissions.")
else:
# Another ClientError occurred
print(f"Failed to check bucket: {e}")
except Exception as ex:
# Handle any other exception
print(f"An error occurred: {ex}")
if __name__ == "__main__":
create_bucket()

View File

@ -7,5 +7,7 @@ python manage.py migrate
python bin/instance_registration.py
# Load the configuration variable
python bin/instance_configuration.py
# Create the default bucket
python bin/bucket_script.py
exec gunicorn -w $GUNICORN_WORKERS -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile -

View File

@ -5,7 +5,7 @@ from django.utils import timezone
from rest_framework import serializers
# Module imports
from .base import BaseSerializer
from .base import BaseSerializer, DynamicBaseSerializer
from .user import UserLiteSerializer
from .state import StateSerializer, StateLiteSerializer
from .project import ProjectLiteSerializer
@ -548,7 +548,7 @@ class IssueSerializer(BaseSerializer):
]
class IssueLiteSerializer(BaseSerializer):
class IssueLiteSerializer(DynamicBaseSerializer):
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
state_detail = StateLiteSerializer(read_only=True, source="state")

View File

@ -4,7 +4,7 @@ from .authentication import urlpatterns as authentication_urls
from .config import urlpatterns as configuration_urls
from .cycle import urlpatterns as cycle_urls
from .estimate import urlpatterns as estimate_urls
from .gpt import urlpatterns as gpt_urls
from .external import urlpatterns as external_urls
from .importer import urlpatterns as importer_urls
from .inbox import urlpatterns as inbox_urls
from .integration import urlpatterns as integration_urls
@ -14,10 +14,8 @@ from .notification import urlpatterns as notification_urls
from .page import urlpatterns as page_urls
from .project import urlpatterns as project_urls
from .public_board import urlpatterns as public_board_urls
from .release_note import urlpatterns as release_note_urls
from .search import urlpatterns as search_urls
from .state import urlpatterns as state_urls
from .unsplash import urlpatterns as unsplash_urls
from .user import urlpatterns as user_urls
from .views import urlpatterns as view_urls
from .workspace import urlpatterns as workspace_urls
@ -30,7 +28,7 @@ urlpatterns = [
*configuration_urls,
*cycle_urls,
*estimate_urls,
*gpt_urls,
*external_urls,
*importer_urls,
*inbox_urls,
*integration_urls,
@ -40,10 +38,8 @@ urlpatterns = [
*page_urls,
*project_urls,
*public_board_urls,
*release_note_urls,
*search_urls,
*state_urls,
*unsplash_urls,
*user_urls,
*view_urls,
*workspace_urls,

View File

@ -0,0 +1,25 @@
from django.urls import path
from plane.api.views import UnsplashEndpoint
from plane.api.views import ReleaseNotesEndpoint
from plane.api.views import GPTIntegrationEndpoint
urlpatterns = [
path(
"unsplash/",
UnsplashEndpoint.as_view(),
name="unsplash",
),
path(
"release-notes/",
ReleaseNotesEndpoint.as_view(),
name="release-notes",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/ai-assistant/",
GPTIntegrationEndpoint.as_view(),
name="importer",
),
]

View File

@ -1,13 +0,0 @@
from django.urls import path
from plane.api.views import GPTIntegrationEndpoint
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/ai-assistant/",
GPTIntegrationEndpoint.as_view(),
name="importer",
),
]

View File

@ -3,6 +3,8 @@ from django.urls import path
from plane.api.views import (
IssueViewSet,
IssueListEndpoint,
IssueListGroupedEndpoint,
LabelViewSet,
BulkCreateIssueLabelsEndpoint,
BulkDeleteIssuesEndpoint,
@ -35,6 +37,16 @@ urlpatterns = [
),
name="project-issue",
),
path(
"v2/workspaces/<str:slug>/projects/<uuid:project_id>/issues/",
IssueListEndpoint.as_view(),
name="project-issue",
),
path(
"v3/workspaces/<str:slug>/projects/<uuid:project_id>/issues/",
IssueListGroupedEndpoint.as_view(),
name="project-issue",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:pk>/",
IssueViewSet.as_view(

View File

@ -1,13 +0,0 @@
from django.urls import path
from plane.api.views import ReleaseNotesEndpoint
urlpatterns = [
path(
"release-notes/",
ReleaseNotesEndpoint.as_view(),
name="release-notes",
),
]

View File

@ -1,13 +0,0 @@
from django.urls import path
from plane.api.views import UnsplashEndpoint
urlpatterns = [
path(
"unsplash/",
UnsplashEndpoint.as_view(),
name="unsplash",
),
]

View File

@ -54,7 +54,12 @@ from .workspace import (
LeaveWorkspaceEndpoint,
)
from .state import StateViewSet
from .view import GlobalViewViewSet, GlobalViewIssuesViewSet, IssueViewViewSet, IssueViewFavoriteViewSet
from .view import (
GlobalViewViewSet,
GlobalViewIssuesViewSet,
IssueViewViewSet,
IssueViewFavoriteViewSet,
)
from .cycle import (
CycleViewSet,
CycleIssueViewSet,
@ -65,6 +70,8 @@ from .cycle import (
from .asset import FileAssetEndpoint, UserAssetsEndpoint
from .issue import (
IssueViewSet,
IssueListEndpoint,
IssueListGroupedEndpoint,
WorkSpaceIssuesEndpoint,
IssueActivityEndpoint,
IssueCommentViewSet,
@ -162,7 +169,11 @@ from .analytic import (
DefaultAnalyticsEndpoint,
)
from .notification import NotificationViewSet, UnreadNotificationEndpoint, MarkAllReadNotificationViewSet
from .notification import (
NotificationViewSet,
UnreadNotificationEndpoint,
MarkAllReadNotificationViewSet,
)
from .exporter import ExportIssuesEndpoint

View File

@ -31,4 +31,7 @@ class ConfigurationEndpoint(BaseAPIView):
os.environ.get("ENABLE_EMAIL_PASSWORD", "0") == "1"
)
data["slack_client_id"] = os.environ.get("SLACK_CLIENT_ID", None)
data["posthog_api_key"] = os.environ.get("POSTHOG_API_KEY", None)
data["posthog_host"] = os.environ.get("POSTHOG_HOST", None)
data["has_unsplash_configured"] = bool(settings.UNSPLASH_ACCESS_KEY)
return Response(data, status=status.HTTP_200_OK)

View File

@ -176,9 +176,8 @@ class CycleViewSet(BaseViewSet):
def list(self, request, slug, project_id):
queryset = self.get_queryset()
cycle_view = request.GET.get("cycle_view", "all")
order_by = request.GET.get("order_by", "sort_order")
queryset = queryset.order_by(order_by)
queryset = queryset.order_by("-is_favorite","-created_at")
# Current Cycle
if cycle_view == "current":

View File

@ -89,4 +89,4 @@ class UnsplashEndpoint(BaseAPIView):
}
resp = requests.get(url=url, headers=headers)
return Response(resp.json(), status=status.HTTP_200_OK)
return Response(resp.json(), status=resp.status_code)

View File

@ -312,6 +312,104 @@ class IssueViewSet(BaseViewSet):
return Response(status=status.HTTP_204_NO_CONTENT)
class IssueListEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
def get(self, request, slug, project_id):
fields = [field for field in request.GET.get("fields", "").split(",") if field]
filters = issue_filters(request.query_params, "GET")
issue_queryset = (
Issue.objects.filter(workspace__slug=slug, project_id=project_id)
.select_related("project")
.select_related("workspace")
.select_related("state")
.select_related("parent")
.prefetch_related("assignees")
.prefetch_related("labels")
.prefetch_related(
Prefetch(
"issue_reactions",
queryset=IssueReaction.objects.select_related("actor"),
)
)
.filter(**filters)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(module_id=F("issue_module__module_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
attachment_count=IssueAttachment.objects.filter(issue=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.distinct()
)
serializer = IssueLiteSerializer(
issue_queryset, many=True, fields=fields if fields else None
)
return Response(serializer.data, status=status.HTTP_200_OK)
class IssueListGroupedEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
def get(self, request, slug, project_id):
filters = issue_filters(request.query_params, "GET")
fields = [field for field in request.GET.get("fields", "").split(",") if field]
issue_queryset = (
Issue.objects.filter(workspace__slug=slug, project_id=project_id)
.select_related("project")
.select_related("workspace")
.select_related("state")
.select_related("parent")
.prefetch_related("assignees")
.prefetch_related("labels")
.prefetch_related(
Prefetch(
"issue_reactions",
queryset=IssueReaction.objects.select_related("actor"),
)
)
.filter(**filters)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(module_id=F("issue_module__module_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
attachment_count=IssueAttachment.objects.filter(issue=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.distinct()
)
issues = IssueLiteSerializer(issue_queryset, many=True, fields=fields if fields else None).data
issue_dict = {str(issue["id"]): issue for issue in issues}
return Response(
issue_dict,
status=status.HTTP_200_OK,
)
class UserWorkSpaceIssues(BaseAPIView):
@method_decorator(gzip_page)
def get(self, request, slug):

View File

@ -55,7 +55,6 @@ class ModuleViewSet(BaseViewSet):
)
def get_queryset(self):
order_by = self.request.GET.get("order_by", "sort_order")
subquery = ModuleFavorite.objects.filter(
user=self.request.user,
@ -138,7 +137,7 @@ class ModuleViewSet(BaseViewSet):
),
)
)
.order_by(order_by, "name")
.order_by("-is_favorite","-created_at")
)
def create(self, request, slug, project_id):

View File

@ -1,8 +1,6 @@
# Python imports
import json
# Django imports
from django.utils import timezone
import uuid
# Module imports
from plane.db.models import (
@ -14,6 +12,7 @@ from plane.db.models import (
Issue,
Notification,
IssueComment,
IssueActivity
)
# Third Party imports
@ -21,12 +20,35 @@ from celery import shared_task
from bs4 import BeautifulSoup
# =========== Issue Description Html Parsing and Notification Functions ======================
def update_mentions_for_issue(issue, project, new_mentions, removed_mention):
aggregated_issue_mentions = []
for mention_id in new_mentions:
aggregated_issue_mentions.append(
IssueMention(
mention_id=mention_id,
issue=issue,
project=project,
workspace_id=project.workspace_id
)
)
IssueMention.objects.bulk_create(
aggregated_issue_mentions, batch_size=100)
IssueMention.objects.filter(
issue=issue, mention__in=removed_mention).delete()
def get_new_mentions(requested_instance, current_instance):
# requested_data is the newer instance of the current issue
# current_instance is the older instance of the current issue, saved in the database
# extract mentions from both the instance of data
mentions_older = extract_mentions(current_instance)
mentions_newer = extract_mentions(requested_instance)
# Getting Set Difference from mentions_newer
@ -64,25 +86,26 @@ def extract_mentions_as_subscribers(project_id, issue_id, mentions):
# If the particular mention has not already been subscribed to the issue, he must be sent the mentioned notification
if not IssueSubscriber.objects.filter(
issue_id=issue_id,
subscriber=mention_id,
project=project_id,
subscriber_id=mention_id,
project_id=project_id,
).exists() and not IssueAssignee.objects.filter(
project_id=project_id, issue_id=issue_id,
assignee_id=mention_id
).exists() and not Issue.objects.filter(
project_id=project_id, pk=issue_id, created_by_id=mention_id
).exists():
mentioned_user = User.objects.get(pk=mention_id)
project = Project.objects.get(pk=project_id)
issue = Issue.objects.get(pk=issue_id)
bulk_mention_subscribers.append(IssueSubscriber(
workspace=project.workspace,
project=project,
issue=issue,
subscriber=mentioned_user,
workspace_id=project.workspace_id,
project_id=project_id,
issue_id=issue_id,
subscriber_id=mention_id,
))
return bulk_mention_subscribers
# Parse Issue Description & extracts mentions
def extract_mentions(issue_instance):
try:
# issue_instance has to be a dictionary passed, containing the description_html and other set of activity data.
@ -99,6 +122,65 @@ def extract_mentions(issue_instance):
return list(set(mentions))
except Exception as e:
return []
# =========== Comment Parsing and Notification Functions ======================
def extract_comment_mentions(comment_value):
try:
mentions = []
soup = BeautifulSoup(comment_value, 'html.parser')
mentions_tags = soup.find_all(
'mention-component', attrs={'target': 'users'}
)
for mention_tag in mentions_tags:
mentions.append(mention_tag['id'])
return list(set(mentions))
except Exception as e:
return []
def get_new_comment_mentions(new_value, old_value):
mentions_newer = extract_comment_mentions(new_value)
if old_value is None:
return mentions_newer
mentions_older = extract_comment_mentions(old_value)
# Getting Set Difference from mentions_newer
new_mentions = [
mention for mention in mentions_newer if mention not in mentions_older]
return new_mentions
def createMentionNotification(project, notification_comment, issue, actor_id, mention_id, issue_id, activity):
return Notification(
workspace=project.workspace,
sender="in_app:issue_activities:mentioned",
triggered_by_id=actor_id,
receiver_id=mention_id,
entity_identifier=issue_id,
entity_name="issue",
project=project,
message=notification_comment,
data={
"issue": {
"id": str(issue_id),
"name": str(issue.name),
"identifier": str(issue.project.identifier),
"sequence_id": issue.sequence_id,
"state_name": issue.state.name,
"state_group": issue.state.group,
},
"issue_activity": {
"id": str(activity.get("id")),
"verb": str(activity.get("verb")),
"field": str(activity.get("field")),
"actor": str(activity.get("actor_id")),
"new_value": str(activity.get("new_value")),
"old_value": str(activity.get("old_value")),
}
},
)
@shared_task
@ -126,61 +208,97 @@ def notifications(type, issue_id, project_id, actor_id, subscriber, issue_activi
bulk_notifications = []
"""
Mention Tasks
1. Perform Diffing and Extract the mentions, that mention notification needs to be sent
2. From the latest set of mentions, extract the users which are not a subscribers & make them subscribers
"""
Mention Tasks
1. Perform Diffing and Extract the mentions, that mention notification needs to be sent
2. From the latest set of mentions, extract the users which are not a subscribers & make them subscribers
"""
# Get new mentions from the newer instance
new_mentions = get_new_mentions(
requested_instance=requested_data, current_instance=current_instance)
removed_mention = get_removed_mentions(
requested_instance=requested_data, current_instance=current_instance)
comment_mentions = []
all_comment_mentions = []
# Get New Subscribers from the mentions of the newer instance
requested_mentions = extract_mentions(
issue_instance=requested_data)
mention_subscribers = extract_mentions_as_subscribers(
project_id=project_id, issue_id=issue_id, mentions=requested_mentions)
issue_subscribers = list(
IssueSubscriber.objects.filter(
project_id=project_id, issue_id=issue_id)
.exclude(subscriber_id__in=list(new_mentions + [actor_id]))
.values_list("subscriber", flat=True)
)
for issue_activity in issue_activities_created:
issue_comment = issue_activity.get("issue_comment")
issue_comment_new_value = issue_activity.get("new_value")
issue_comment_old_value = issue_activity.get("old_value")
if issue_comment is not None:
# TODO: Maybe save the comment mentions, so that in future, we can filter out the issues based on comment mentions as well.
all_comment_mentions = all_comment_mentions + extract_comment_mentions(issue_comment_new_value)
new_comment_mentions = get_new_comment_mentions(old_value=issue_comment_old_value, new_value=issue_comment_new_value)
comment_mentions = comment_mentions + new_comment_mentions
comment_mention_subscribers = extract_mentions_as_subscribers( project_id=project_id, issue_id=issue_id, mentions=all_comment_mentions)
"""
We will not send subscription activity notification to the below mentioned user sets
- Those who have been newly mentioned in the issue description, we will send mention notification to them.
- When the activity is a comment_created and there exist a mention in the comment, then we have to send the "mention_in_comment" notification
- When the activity is a comment_updated and there exist a mention change, then also we have to send the "mention_in_comment" notification
"""
issue_assignees = list(
IssueAssignee.objects.filter(
project_id=project_id, issue_id=issue_id)
.exclude(assignee_id=actor_id)
.exclude(assignee_id__in=list(new_mentions + comment_mentions))
.values_list("assignee", flat=True)
)
issue_subscribers = issue_subscribers + issue_assignees
issue_subscribers = list(
IssueSubscriber.objects.filter(
project_id=project_id, issue_id=issue_id)
.exclude(subscriber_id__in=list(new_mentions + comment_mentions + [actor_id]))
.values_list("subscriber", flat=True)
)
issue = Issue.objects.filter(pk=issue_id).first()
if (issue.created_by_id is not None and str(issue.created_by_id) != str(actor_id)):
issue_subscribers = issue_subscribers + [issue.created_by_id]
if subscriber:
# add the user to issue subscriber
try:
_ = IssueSubscriber.objects.get_or_create(
issue_id=issue_id, subscriber_id=actor_id
)
if str(issue.created_by_id) != str(actor_id) and uuid.UUID(actor_id) not in issue_assignees:
_ = IssueSubscriber.objects.get_or_create(
project_id=project_id, issue_id=issue_id, subscriber_id=actor_id
)
except Exception as e:
pass
project = Project.objects.get(pk=project_id)
for subscriber in list(set(issue_subscribers)):
issue_subscribers = list(set(issue_subscribers + issue_assignees) - {uuid.UUID(actor_id)})
for subscriber in issue_subscribers:
if subscriber in issue_subscribers:
sender = "in_app:issue_activities:subscribed"
if issue.created_by_id is not None and subscriber == issue.created_by_id:
sender = "in_app:issue_activities:created"
if subscriber in issue_assignees:
sender = "in_app:issue_activities:assigned"
for issue_activity in issue_activities_created:
issue_comment = issue_activity.get("issue_comment")
if issue_comment is not None:
issue_comment = IssueComment.objects.get(id=issue_comment, issue_id=issue_id, project_id=project_id, workspace_id=project.workspace_id)
issue_comment = IssueComment.objects.get(
id=issue_comment, issue_id=issue_id, project_id=project_id, workspace_id=project.workspace_id)
bulk_notifications.append(
Notification(
workspace=project.workspace,
sender="in_app:issue_activities",
sender=sender,
triggered_by_id=actor_id,
receiver_id=subscriber,
entity_identifier=issue_id,
@ -215,15 +333,42 @@ def notifications(type, issue_id, project_id, actor_id, subscriber, issue_activi
# Add Mentioned as Issue Subscribers
IssueSubscriber.objects.bulk_create(
mention_subscribers, batch_size=100)
mention_subscribers + comment_mention_subscribers, batch_size=100)
last_activity = (
IssueActivity.objects.filter(issue_id=issue_id)
.order_by("-created_at")
.first()
)
actor = User.objects.get(pk=actor_id)
for mention_id in comment_mentions:
if (mention_id != actor_id):
for issue_activity in issue_activities_created:
notification = createMentionNotification(
project=project,
issue=issue,
notification_comment=f"{actor.display_name} has mentioned you in a comment in issue {issue.name}",
actor_id=actor_id,
mention_id=mention_id,
issue_id=issue_id,
activity=issue_activity
)
bulk_notifications.append(notification)
for mention_id in new_mentions:
if (mention_id != actor_id):
for issue_activity in issue_activities_created:
if (
last_activity is not None
and last_activity.field == "description"
and actor_id == str(last_activity.actor_id)
):
bulk_notifications.append(
Notification(
workspace=project.workspace,
sender="in_app:issue_activities:mention",
Notification(
workspace=project.workspace,
sender="in_app:issue_activities:mentioned",
triggered_by_id=actor_id,
receiver_id=mention_id,
entity_identifier=issue_id,
@ -237,38 +382,37 @@ def notifications(type, issue_id, project_id, actor_id, subscriber, issue_activi
"identifier": str(issue.project.identifier),
"sequence_id": issue.sequence_id,
"state_name": issue.state.name,
"state_group": issue.state.group,
},
"issue_activity": {
"id": str(issue_activity.get("id")),
"verb": str(issue_activity.get("verb")),
"field": str(issue_activity.get("field")),
"actor": str(issue_activity.get("actor_id")),
"new_value": str(issue_activity.get("new_value")),
"old_value": str(issue_activity.get("old_value")),
},
},
"state_group": issue.state.group,
},
"issue_activity": {
"id": str(last_activity.id),
"verb": str(last_activity.verb),
"field": str(last_activity.field),
"actor": str(last_activity.actor_id),
"new_value": str(last_activity.new_value),
"old_value": str(last_activity.old_value),
},
},
)
)
else:
for issue_activity in issue_activities_created:
notification = createMentionNotification(
project=project,
issue=issue,
notification_comment=f"You have been mentioned in the issue {issue.name}",
actor_id=actor_id,
mention_id=mention_id,
issue_id=issue_id,
activity=issue_activity
)
)
# Create New Mentions Here
aggregated_issue_mentions = []
for mention_id in new_mentions:
mentioned_user = User.objects.get(pk=mention_id)
aggregated_issue_mentions.append(
IssueMention(
mention=mentioned_user,
issue=issue,
project=project,
workspace=project.workspace
)
)
IssueMention.objects.bulk_create(
aggregated_issue_mentions, batch_size=100)
IssueMention.objects.filter(
issue=issue, mention__in=removed_mention).delete()
bulk_notifications.append(notification)
# save new mentions for the particular issue and remove the mentions that has been deleted from the description
update_mentions_for_issue(issue=issue, project=project, new_mentions=new_mentions,
removed_mention=removed_mention)
# Bulk create notifications
Notification.objects.bulk_create(bulk_notifications, batch_size=100)

View File

@ -174,7 +174,7 @@ module.exports = {
DEFAULT: convertToRGB("--color-sidebar-border-200"),
},
},
backdrop: "#131313",
backdrop: "rgba(0, 0, 0, 0.25)",
},
},
keyframes: {

View File

@ -18,18 +18,21 @@ export const PriorityIcon: React.FC<IPriorityIcon> = ({
}) => {
if (!className || className === "") className = "h-3.5 w-3.5";
// Convert to lowercase for string comparison
const lowercasePriority = priority?.toLowerCase();
return (
<>
{priority === "urgent" ? (
<AlertCircle className={`${className}`} />
) : priority === "high" ? (
<SignalHigh className={`${className}`} />
) : priority === "medium" ? (
<SignalMedium className={`${className}`} />
) : priority === "low" ? (
<SignalLow className={`${className}`} />
{lowercasePriority === "urgent" ? (
<AlertCircle className={`text-red-500 ${className}`} />
) : lowercasePriority === "high" ? (
<SignalHigh className={`text-orange-500 ${className}`} />
) : lowercasePriority === "medium" ? (
<SignalMedium className={`text-yellow-500 ${className}`} />
) : lowercasePriority === "low" ? (
<SignalLow className={`text-green-500 ${className}`} />
) : (
<Ban className={`${className}`} />
<Ban className={`text-custom-text-200 ${className}`} />
)}
</>
);

View File

@ -112,7 +112,7 @@ export const AnalyticsGraph: React.FC<Props> = ({ analytics, barGraphData, param
<text
x={0}
y={datum.y}
textAnchor="end"
textAnchor={`${barGraphData.data.length > 7 ? "end" : "middle"}`}
fontSize={10}
fill="rgb(var(--color-text-200))"
className={`${barGraphData.data.length > 7 ? "-rotate-45" : ""}`}

View File

@ -69,7 +69,6 @@ export const AnalyticsTable: React.FC<Props> = ({ analytics, barGraphData, param
}`}
>
{params.x_axis === "priority" ? (
// TODO: incorrect priority icon being rendered
<PriorityIcon priority={item.name as TIssuePriorities} />
) : (
<span

View File

@ -54,7 +54,7 @@ export const SelectMonthModal: React.FC<Props> = ({ type, initialValues, isOpen,
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-[#131313] bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
@ -68,7 +68,7 @@ export const SelectMonthModal: React.FC<Props> = ({ type, initialValues, isOpen,
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-90 px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 px-4 pt-5 pb-4 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
@ -144,10 +144,10 @@ export const SelectMonthModal: React.FC<Props> = ({ type, initialValues, isOpen,
</div>
</div>
<div className="mt-5 flex justify-end gap-2">
<Button variant="neutral-primary" onClick={onClose}>
<Button variant="neutral-primary" size="sm" onClick={onClose}>
Cancel
</Button>
<Button variant="primary" type="submit" loading={isSubmitting}>
<Button variant="primary" size="sm" type="submit" loading={isSubmitting}>
{isSubmitting ? "Submitting..." : "Submit"}
</Button>
</div>

View File

@ -245,7 +245,7 @@ export const CommandModal: React.FC<Props> = observer((props) => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-30 overflow-y-auto p-4 sm:p-6 md:p-20">
@ -259,7 +259,7 @@ export const CommandModal: React.FC<Props> = observer((props) => {
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative flex items-center justify-center w-full ">
<div className="w-full max-w-2xl transform divide-y divide-custom-border-200 divide-opacity-10 rounded-xl border border-custom-border-200 bg-custom-background-100 shadow-2xl transition-all">
<div className="w-full max-w-2xl transform divide-y divide-custom-border-200 divide-opacity-10 rounded-lg bg-custom-background-100 shadow-custom-shadow-md transition-all">
<Command
filter={(value, search) => {
if (value.toLowerCase().includes(search.toLowerCase())) return 1;

View File

@ -26,15 +26,16 @@ const issueService = new IssueService();
export const ChangeIssueAssignee: FC<Props> = observer((props) => {
const { setIsPaletteOpen, issue, user } = props;
// router
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
const { project: projectStore } = useMobxStore();
const members = projectId ? projectStore.members?.[projectId.toString()] : undefined;
// store
const {
projectMember: { projectMembers },
} = useMobxStore();
const options =
members?.map(({ member }) => ({
projectMembers?.map(({ member }) => ({
value: member.id,
query: member.display_name,
content: (

View File

@ -1,5 +1,4 @@
import { FC, useEffect, useState, Dispatch, SetStateAction, Fragment } from "react";
// headless ui
import { FC, useEffect, useState, Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react";
// icons
import { Command, Search, X } from "lucide-react";
@ -68,7 +67,7 @@ export const ShortcutsModal: FC<Props> = (props) => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-[#131313] bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
@ -82,8 +81,8 @@ export const ShortcutsModal: FC<Props> = (props) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-80 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg">
<div className="bg-custom-background-80 p-5">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-lg">
<div className="bg-custom-background-100 p-5">
<div className="sm:flex sm:items-start">
<div className="flex w-full flex-col gap-y-4 text-center sm:text-left">
<Dialog.Title

View File

@ -37,7 +37,7 @@ export const ProductUpdatesModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 h-full w-full">
@ -51,7 +51,7 @@ export const ProductUpdatesModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative overflow-hidden rounded-lg bg-custom-background-100 border border-custom-border-100 shadow-custom-shadow-rg] min-w-[100%] sm:min-w-[50%] sm:max-w-[50%]">
<Dialog.Panel className="relative overflow-hidden rounded-lg bg-custom-background-100 shadow-custom-shadow-md min-w-[100%] sm:min-w-[50%] sm:max-w-[50%]">
<div className="flex flex-col p-4 max-h-[90vh] w-full">
<Dialog.Title as="h3" className="flex items-center justify-between text-lg font-semibold">
<span>Product Updates</span>

View File

@ -42,9 +42,12 @@ const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
return (
<Tooltip tooltipContent={activity.issue_detail ? activity.issue_detail.name : "This issue has been deleted"}>
<a
href={`/${workspaceSlug}/projects/${activity.project}/issues/${activity.issue}`}
target="_blank"
rel="noopener noreferrer"
aria-disabled={activity.issue === null}
href={`${
activity.issue_detail ? `/${workspaceSlug}/projects/${activity.project}/issues/${activity.issue}` : "#"
}`}
target={activity.issue === null ? "_self" : "_blank"}
rel={activity.issue === null ? "" : "noopener noreferrer"}
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
>
{activity.issue_detail ? `${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}` : "Issue"}

View File

@ -62,7 +62,7 @@ export const DateFilterModal: React.FC<Props> = ({ title, handleClose, isOpen, o
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 flex w-full justify-center overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center sm:p-0">
@ -75,7 +75,7 @@ export const DateFilterModal: React.FC<Props> = ({ title, handleClose, isOpen, o
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative flex transform rounded-lg border border-custom-border-200 bg-custom-background-100 px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<Dialog.Panel className="relative flex transform rounded-lg bg-custom-background-100 px-5 py-8 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form className="space-y-4" onSubmit={handleSubmit(handleFormSubmit)}>
<div className="flex w-full justify-between">
<Controller
@ -127,10 +127,10 @@ export const DateFilterModal: React.FC<Props> = ({ title, handleClose, isOpen, o
</h6>
)}
<div className="flex justify-end gap-4">
<Button variant="neutral-primary" onClick={handleClose}>
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="primary" type="submit" disabled={isInvalid}>
<Button variant="primary" size="sm" type="submit" disabled={isInvalid}>
Apply
</Button>
</div>

View File

@ -116,7 +116,7 @@ export const BulkDeleteIssuesModal: React.FC<Props> = (props) => {
return (
<Transition.Root show={isOpen} as={React.Fragment} afterLeave={() => setQuery("")} appear>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<div className="fixed inset-0 z-20 overflow-y-auto p-4 sm:p-6 md:p-20">
<div className="fixed inset-0 bg-custom-backdrop transition-opacity z-20 overflow-y-auto p-4 sm:p-6 md:p-20">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
@ -127,7 +127,7 @@ export const BulkDeleteIssuesModal: React.FC<Props> = (props) => {
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative flex items-center justify-center w-full ">
<div className="w-full max-w-2xl transform divide-y divide-custom-border-200 divide-opacity-10 rounded-xl border border-custom-border-200 bg-custom-background-100 shadow-2xl transition-all">
<div className="w-full max-w-2xl transform divide-y divide-custom-border-200 divide-opacity-10 rounded-lg bg-custom-background-100 shadow-custom-shadow-md transition-all">
<form>
<Combobox
onChange={(val: string) => {
@ -211,10 +211,10 @@ export const BulkDeleteIssuesModal: React.FC<Props> = (props) => {
{filteredIssues.length > 0 && (
<div className="flex items-center justify-end gap-2 p-3">
<Button variant="neutral-primary" onClick={handleClose}>
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="danger" onClick={handleSubmit(handleDelete)} loading={isSubmitting}>
<Button variant="danger" size="sm" onClick={handleSubmit(handleDelete)} loading={isSubmitting}>
{isSubmitting ? "Deleting..." : "Delete selected issues"}
</Button>
</div>

View File

@ -102,7 +102,7 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto p-4 sm:p-6 md:p-20">
@ -115,7 +115,7 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative mx-auto max-w-2xl transform rounded-xl border border-custom-border-200 bg-custom-background-100 shadow-2xl transition-all">
<Dialog.Panel className="relative mx-auto max-w-2xl transform rounded-lg bg-custom-background-100 shadow-custom-shadow-md transition-all">
<Combobox
as="div"
onChange={(val: ISearchIssueResponse) => {
@ -262,10 +262,10 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
</Combobox>
{selectedIssues.length > 0 && (
<div className="flex items-center justify-end gap-2 p-3">
<Button variant="neutral-primary" onClick={handleClose}>
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="primary" onClick={onSubmit} loading={isSubmitting}>
<Button variant="primary" size="sm" onClick={onSubmit} loading={isSubmitting}>
{isSubmitting ? "Adding..." : "Add selected issues"}
</Button>
</div>

View File

@ -203,10 +203,10 @@ export const GptAssistantModal: React.FC<Props> = ({
</Button>
)}
<div className="flex items-center gap-2">
<Button variant="neutral-primary" onClick={onClose}>
<Button variant="neutral-primary" size="sm" onClick={onClose}>
Close
</Button>
<Button variant="primary" onClick={handleSubmit(handleResponse)} loading={isSubmitting}>
<Button variant="primary" size="sm" onClick={handleSubmit(handleResponse)} loading={isSubmitting}>
{isSubmitting ? "Generating response..." : response === "" ? "Generate response" : "Generate again"}
</Button>
</div>

View File

@ -106,7 +106,7 @@ export const ImageUploadModal: React.FC<Props> = observer((props) => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-30 overflow-y-auto">
@ -120,7 +120,7 @@ export const ImageUploadModal: React.FC<Props> = observer((props) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 px-5 py-8 text-left shadow-xl transition-all sm:w-full sm:max-w-xl sm:p-6">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 px-5 py-8 text-left shadow-custom-shadow-md transition-all sm:w-full sm:max-w-xl sm:p-6">
<div className="space-y-5">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
Upload Image
@ -175,15 +175,15 @@ export const ImageUploadModal: React.FC<Props> = observer((props) => {
</p>
<div className="flex items-center justify-between">
<div className="flex items-center">
<Button variant="danger" onClick={handleDelete} disabled={!value}>
<Button variant="danger" size="sm" onClick={handleDelete} disabled={!value}>
{isRemoving ? "Removing..." : "Remove"}
</Button>
</div>
<div className="flex items-center gap-2">
<Button variant="neutral-primary" onClick={handleClose}>
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="primary" onClick={handleSubmit} disabled={!image} loading={isImageUploading}>
<Button variant="primary" size="sm" onClick={handleSubmit} disabled={!image} loading={isImageUploading}>
{isImageUploading ? "Uploading..." : "Upload & Save"}
</Button>
</div>

View File

@ -76,7 +76,7 @@ export const LinkModal: FC<Props> = (props) => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
@ -90,7 +90,7 @@ export const LinkModal: FC<Props> = (props) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 border border-custom-border-200 px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 px-5 py-8 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form onSubmit={handleSubmit(handleCreateUpdatePage)}>
<div>
<div className="space-y-5">
@ -149,10 +149,10 @@ export const LinkModal: FC<Props> = (props) => {
</div>
</div>
<div className="mt-5 flex justify-end gap-2">
<Button variant="neutral-primary" onClick={onClose}>
<Button variant="neutral-primary" size="sm" onClick={onClose}>
Cancel
</Button>
<Button variant="primary" type="submit" loading={isSubmitting}>
<Button variant="primary" size="sm" type="submit" loading={isSubmitting}>
{status
? isSubmitting
? "Updating Link..."

View File

@ -75,12 +75,12 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
const { setToastAlert } = useToast();
const { isLoading } = useSWR(
useSWR(
workspaceSlug && projectId ? `ACTIVE_CYCLE_ISSUE_${projectId}_CURRENT` : null,
workspaceSlug && projectId ? () => cycleStore.fetchCycles(workspaceSlug, projectId, "current") : null
);
const activeCycle = cycleStore.cycles?.[projectId] || null;
const activeCycle = cycleStore.cycles?.[projectId]?.active || null;
const cycle = activeCycle ? activeCycle[0] : null;
const issues = (cycleStore?.active_cycle_issues as any) || null;
@ -94,7 +94,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
// : null
// ) as { data: IIssue[] | undefined };
if (isLoading)
if (!cycle)
return (
<Loader>
<Loader.Item height="250px" />
@ -143,7 +143,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
e.preventDefault();
if (!workspaceSlug || !projectId) return;
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle).catch(() => {
setToastAlert({
type: "error",
title: "Error!",
@ -156,7 +156,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
e.preventDefault();
if (!workspaceSlug || !projectId) return;
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle).catch(() => {
setToastAlert({
type: "error",
title: "Error!",

View File

@ -87,7 +87,7 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
e.preventDefault();
if (!workspaceSlug || !projectId) return;
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle).catch(() => {
setToastAlert({
type: "error",
title: "Error!",
@ -100,7 +100,7 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
e.preventDefault();
if (!workspaceSlug || !projectId) return;
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle).catch(() => {
setToastAlert({
type: "error",
title: "Error!",
@ -152,34 +152,32 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = (props) => {
<Link href={`/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`}>
<a className="flex flex-col justify-between p-4 h-44 w-full min-w-[250px] text-sm rounded bg-custom-background-100 border border-custom-border-100 hover:shadow-md">
<div>
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-3">
<span className="flex-shrink-0">
<CycleGroupIcon cycleGroup={cycleStatus} className="h-3.5 w-3.5" />
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-3 truncate">
<span className="flex-shrink-0">
<CycleGroupIcon cycleGroup={cycleStatus} className="h-3.5 w-3.5" />
</span>
<Tooltip tooltipContent={cycle.name} position="top">
<span className="text-base font-medium truncate">{cycle.name}</span>
</Tooltip>
</div>
<div className="flex items-center gap-2">
{currentCycle && (
<span
className="flex items-center justify-center text-xs text-center h-6 w-20 rounded-sm"
style={{
color: currentCycle.color,
backgroundColor: `${currentCycle.color}20`,
}}
>
{currentCycle.value === "current"
? `${findHowManyDaysLeft(cycle.end_date ?? new Date())} ${currentCycle.label}`
: `${currentCycle.label}`}
</span>
<Tooltip tooltipContent={cycle.name} position="top">
<span className="text-base font-medium truncate">{cycle.name}</span>
</Tooltip>
</div>
<div className="flex items-center gap-2">
{currentCycle && (
<span
className="flex items-center justify-center text-xs text-center h-6 w-20 rounded-sm"
style={{
color: currentCycle.color,
backgroundColor: `${currentCycle.color}20`,
}}
>
{currentCycle.value === "current"
? `${findHowManyDaysLeft(cycle.end_date ?? new Date())} ${currentCycle.label}`
: `${currentCycle.label}`}
</span>
)}
<button onClick={openCycleOverview}>
<Info className="h-4 w-4 text-custom-text-400" />
</button>
</div>
)}
<button onClick={openCycleOverview}>
<Info className="h-4 w-4 text-custom-text-400" />
</button>
</div>
</div>

View File

@ -12,7 +12,7 @@ export interface ICyclesBoard {
filter: string;
workspaceSlug: string;
projectId: string;
peekCycle: string;
peekCycle: string | undefined;
}
export const CyclesBoard: FC<ICyclesBoard> = observer((props) => {

View File

@ -1 +0,0 @@
export const CycleGantt = () => <></>;

View File

@ -87,7 +87,7 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
e.preventDefault();
if (!workspaceSlug || !projectId) return;
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
cycleStore.addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), cycle).catch(() => {
setToastAlert({
type: "error",
title: "Error!",
@ -100,7 +100,7 @@ export const CyclesListItem: FC<TCyclesListItem> = (props) => {
e.preventDefault();
if (!workspaceSlug || !projectId) return;
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle.id).catch(() => {
cycleStore.removeCycleFromFavorites(workspaceSlug?.toString(), projectId.toString(), cycle).catch(() => {
setToastAlert({
type: "error",
title: "Error!",

View File

@ -15,7 +15,7 @@ export interface ICyclesView {
layout: TCycleLayout;
workspaceSlug: string;
projectId: string;
peekCycle: string;
peekCycle: string | undefined;
}
export const CyclesView: FC<ICyclesView> = observer((props) => {
@ -30,7 +30,14 @@ export const CyclesView: FC<ICyclesView> = observer((props) => {
workspaceSlug && projectId && filter ? () => cycleStore.fetchCycles(workspaceSlug, projectId, filter) : null
);
const cyclesList = cycleStore.cycles?.[projectId];
const cyclesList =
filter === "completed"
? cycleStore.projectCompletedCycles
: filter === "draft"
? cycleStore.projectDraftCycles
: filter === "upcoming"
? cycleStore.projectUpcomingCycles
: cycleStore.projectCycles;
return (
<>

View File

@ -70,7 +70,7 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
@ -84,7 +84,7 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-4">
<div className="flex-shrink-0 flex justify-center items-center rounded-full bg-red-500/20 w-12 h-12">
@ -105,7 +105,7 @@ export const CycleDeleteModal: React.FC<ICycleDelete> = observer((props) => {
Cancel
</Button>
<Button variant="danger" size="sm" onClick={formSubmit}>
<Button variant="danger" size="sm" tabIndex={1} onClick={formSubmit}>
{loader ? "Deleting..." : "Delete Cycle"}
</Button>
</div>

View File

@ -136,10 +136,10 @@ export const CycleForm: React.FC<Props> = (props) => {
</div>
</div>
<div className="flex items-center justify-end gap-2 pt-5 mt-5 border-t-[0.5px] border-custom-border-100 ">
<Button variant="neutral-primary" onClick={handleClose}>
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="primary" type="submit" loading={isSubmitting}>
<Button variant="primary" size="sm" type="submit" loading={isSubmitting}>
{data
? isSubmitting
? "Updating Cycle..."

View File

@ -10,7 +10,7 @@ import { CycleService } from "services/cycle.service";
import useUser from "hooks/use-user";
import useProjectDetails from "hooks/use-project-details";
// components
import { GanttChartRoot, IBlockUpdateData } from "components/gantt-chart";
import { GanttChartRoot, IBlockUpdateData, CycleGanttSidebar } from "components/gantt-chart";
import { CycleGanttBlock, CycleGanttSidebarBlock } from "components/cycles";
// types
import { ICycle } from "types";
@ -85,8 +85,8 @@ export const CyclesListGanttChartView: FC<Props> = ({ cycles, mutateCycles }) =>
loaderTitle="Cycles"
blocks={cycles ? blockFormat(cycles) : null}
blockUpdateHandler={(block, payload) => handleCycleUpdate(block, payload)}
sidebarToRender={(props) => <CycleGanttSidebar {...props} />}
blockToRender={(data: ICycle) => <CycleGanttBlock data={data} />}
sidebarBlockToRender={(data: ICycle) => <CycleGanttSidebarBlock data={data} />}
enableBlockLeftResize={false}
enableBlockRightResize={false}
enableBlockMove={false}

View File

@ -5,7 +5,6 @@ export * from "./gantt-chart";
export * from "./cycles-view";
export * from "./form";
export * from "./modal";
export * from "./select";
export * from "./sidebar";
export * from "./transfer-issues-modal";
export * from "./transfer-issues";
@ -13,7 +12,6 @@ export * from "./cycles-list";
export * from "./cycles-list-item";
export * from "./cycles-board";
export * from "./cycles-board-card";
export * from "./cycles-gantt";
export * from "./delete-modal";
export * from "./cycle-peek-overview";
export * from "./cycles-list-item";

View File

@ -50,7 +50,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
const updateCycle = async (cycleId: string, payload: Partial<ICycle>) =>
cycleStore
.updateCycle(workspaceSlug, projectId, cycleId, payload)
.patchCycle(workspaceSlug, projectId, cycleId, payload)
.then(() => {
setToastAlert({
type: "success",
@ -123,7 +123,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
@ -137,7 +137,7 @@ export const CycleCreateUpdateModal: React.FC<CycleModalProps> = (props) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-200 bg-custom-background-100 p-5 text-left shadow-xl transition-all sm:w-full sm:max-w-2xl">
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 p-5 text-left shadow-custom-shadow-md transition-all sm:w-full sm:max-w-2xl">
<CycleForm
handleFormSubmit={handleFormSubmit}
handleClose={handleClose}

View File

@ -1,122 +0,0 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { Listbox, Transition } from "@headlessui/react";
// icons
import { ContrastIcon } from "@plane/ui";
import { Plus } from "lucide-react";
// services
import { CycleService } from "services/cycle.service";
// components
import { CycleCreateUpdateModal } from "components/cycles";
// fetch-keys
import { CYCLES_LIST } from "constants/fetch-keys";
export type IssueCycleSelectProps = {
projectId: string;
value: any;
onChange: (value: any) => void;
multiple?: boolean;
};
const cycleService = new CycleService();
export const CycleSelect: React.FC<IssueCycleSelectProps> = ({ projectId, value, onChange, multiple = false }) => {
// states
const [isCycleModalActive, setCycleModalActive] = useState(false);
const router = useRouter();
const { workspaceSlug } = router.query;
const { data: cycles } = useSWR(
workspaceSlug && projectId ? CYCLES_LIST(projectId) : null,
workspaceSlug && projectId
? () => cycleService.getCyclesWithParams(workspaceSlug as string, projectId as string, "all")
: null
);
const options = cycles?.map((cycle) => ({ value: cycle.id, display: cycle.name }));
const openCycleModal = () => {
setCycleModalActive(true);
};
const closeCycleModal = () => {
setCycleModalActive(false);
};
return (
<>
{workspaceSlug && projectId && (
<CycleCreateUpdateModal
isOpen={isCycleModalActive}
handleClose={closeCycleModal}
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
/>
)}
<Listbox as="div" className="relative" value={value} onChange={onChange} multiple={multiple}>
{({ open }) => (
<>
<Listbox.Button
className={`flex cursor-pointer items-center gap-1 rounded-md border border-custom-border-200 px-2 py-1 text-xs shadow-sm duration-300 hover:bg-custom-background-90 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500`}
>
<ContrastIcon className="h-3 w-3 text-custom-text-200" />
<div className="flex items-center gap-2 truncate">
{cycles?.find((c) => c.id === value)?.name ?? "Cycles"}
</div>
</Listbox.Button>
<Transition
show={open}
as={React.Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options
className={`absolute mt-1 max-h-32 min-w-[8rem] overflow-y-auto whitespace-nowrap bg-custom-background-80 shadow-lg text-xs z-10 rounded-md py-1 ring-1 ring-black ring-opacity-5 focus:outline-none`}
>
<div className="py-1">
{options ? (
options.length > 0 ? (
options.map((option) => (
<Listbox.Option
key={option.value}
className={({ selected, active }) =>
`${
selected || (Array.isArray(value) ? value.includes(option.value) : value === option.value)
? "bg-indigo-50 font-medium"
: ""
} ${
active ? "bg-indigo-50" : ""
} relative cursor-pointer select-none p-2 text-custom-text-100`
}
value={option.value}
>
<span className={` flex items-center gap-2 truncate`}>{option.display}</span>
</Listbox.Option>
))
) : (
<p className="text-center text-sm text-custom-text-200">No options</p>
)
) : (
<p className="text-center text-sm text-custom-text-200">Loading...</p>
)}
<button
type="button"
className="relative w-full flex select-none items-center gap-x-2 p-2 text-gray-400 hover:bg-indigo-50 hover:text-custom-text-100"
onClick={openCycleModal}
>
<Plus className="h-4 w-4 text-gray-400" aria-hidden="true" />
<span>Create cycle</span>
</button>
</div>
</Listbox.Options>
</Transition>
</>
)}
</Listbox>
</>
);
};

View File

@ -502,7 +502,7 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
</div>
<div className="relative h-40 w-80">
<ProgressChart
distribution={cycleDetails.distribution.completion_chart}
distribution={cycleDetails.distribution?.completion_chart ?? {}}
startDate={cycleDetails.start_date ?? ""}
endDate={cycleDetails.end_date ?? ""}
totalIssues={cycleDetails.total_issues}
@ -512,7 +512,7 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
) : (
""
)}
{cycleDetails.total_issues > 0 && (
{cycleDetails.total_issues > 0 && cycleDetails.distribution && (
<div className="h-full w-full pt-5 border-t border-custom-border-200">
<SidebarProgressStats
distribution={cycleDetails.distribution}

View File

@ -82,7 +82,7 @@ export const TransferIssuesModal: React.FC<Props> = ({ isOpen, handleClose }) =>
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-[#131313] bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10">
@ -96,7 +96,7 @@ export const TransferIssuesModal: React.FC<Props> = ({ isOpen, handleClose }) =>
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-90 py-5 text-left shadow-xl transition-all sm:w-full sm:max-w-2xl">
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 py-5 text-left shadow-custom-shadow-md transition-all sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between px-5">
<div className="flex items-center gap-3">

View File

@ -203,7 +203,7 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
@ -217,7 +217,7 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-200 bg-custom-background-100 px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 px-5 py-8 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form onSubmit={handleSubmit(onSubmit)}>
<div className="space-y-3">
<div className="text-lg font-medium leading-6">{data ? "Update" : "Create"} Estimate</div>
@ -292,10 +292,10 @@ export const CreateUpdateEstimateModal: React.FC<Props> = observer((props) => {
</div>
</div>
<div className="mt-5 flex justify-end gap-2">
<Button variant="neutral-primary" onClick={handleClose}>
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="primary" type="submit" loading={isSubmitting}>
<Button variant="primary" size="sm" type="submit" loading={isSubmitting}>
{data
? isSubmitting
? "Updating Estimate..."

View File

@ -73,7 +73,7 @@ export const DeleteEstimateModal: React.FC<Props> = observer((props) => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
@ -87,7 +87,7 @@ export const DeleteEstimateModal: React.FC<Props> = observer((props) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-red-500/20 p-4">
@ -106,11 +106,13 @@ export const DeleteEstimateModal: React.FC<Props> = observer((props) => {
</p>
</span>
<div className="flex justify-end gap-2">
<Button variant="neutral-primary" onClick={onClose}>
<Button variant="neutral-primary" size="sm" onClick={onClose}>
Cancel
</Button>
<Button
variant="danger"
size="sm"
tabIndex={1}
onClick={() => {
setIsDeleteLoading(true);
handleEstimateDelete();

View File

@ -99,7 +99,7 @@ export const Exporter: React.FC<Props> = observer((props) => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
@ -113,7 +113,7 @@ export const Exporter: React.FC<Props> = observer((props) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-6 gap-y-4 p-6">
<div className="flex w-full items-center justify-start gap-6">
<span className="flex items-center justify-start">
@ -150,11 +150,12 @@ export const Exporter: React.FC<Props> = observer((props) => {
<div className="text-sm whitespace-nowrap">Export the data into separate files</div>
</div>
<div className="flex justify-end gap-2">
<Button variant="neutral-primary" onClick={handleClose}>
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button
variant="primary"
size="sm"
onClick={ExportCSVToMail}
disabled={exportLoading}
loading={exportLoading}

View File

@ -2,7 +2,7 @@ import { FC, useEffect, useState } from "react";
// icons
// components
import { GanttChartBlocks } from "components/gantt-chart";
import { GanttSidebar } from "../sidebar";
// import { GanttSidebar } from "../sidebar";
// import { HourChartView } from "./hours";
// import { DayChartView } from "./day";
// import { WeekChartView } from "./week";
@ -40,7 +40,7 @@ type ChartViewRootProps = {
blocks: IGanttBlock[] | null;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blockToRender: (data: any) => React.ReactNode;
sidebarBlockToRender: (block: any) => React.ReactNode;
sidebarToRender: (props: any) => React.ReactNode;
enableBlockLeftResize: boolean;
enableBlockRightResize: boolean;
enableBlockMove: boolean;
@ -54,7 +54,7 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
blocks = null,
loaderTitle,
blockUpdateHandler,
sidebarBlockToRender,
sidebarToRender,
blockToRender,
enableBlockLeftResize,
enableBlockRightResize,
@ -285,13 +285,8 @@ export const ChartViewRoot: FC<ChartViewRootProps> = ({
<h6>{title}</h6>
<h6>Duration</h6>
</div>
<GanttSidebar
title={title}
blockUpdateHandler={blockUpdateHandler}
blocks={chartBlocks}
sidebarBlockToRender={sidebarBlockToRender}
enableReorder={enableReorder}
/>
{sidebarToRender && sidebarToRender({ title, blockUpdateHandler, blocks, enableReorder })}
</div>
<div
className="relative flex h-full w-full flex-1 flex-col overflow-hidden overflow-x-auto horizontal-scroll-enable"

View File

@ -3,3 +3,4 @@ export * from "./helpers";
export * from "./hooks";
export * from "./root";
export * from "./types";
export * from "./sidebar";

View File

@ -13,7 +13,7 @@ type GanttChartRootProps = {
blocks: IGanttBlock[] | null;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blockToRender: (data: any) => React.ReactNode;
sidebarBlockToRender: (block: any) => React.ReactNode;
sidebarToRender: (props: any) => React.ReactNode;
enableBlockLeftResize?: boolean;
enableBlockRightResize?: boolean;
enableBlockMove?: boolean;
@ -27,7 +27,7 @@ export const GanttChartRoot: FC<GanttChartRootProps> = ({
blocks,
loaderTitle = "blocks",
blockUpdateHandler,
sidebarBlockToRender,
sidebarToRender,
blockToRender,
enableBlockLeftResize = true,
enableBlockRightResize = true,
@ -42,7 +42,7 @@ export const GanttChartRoot: FC<GanttChartRootProps> = ({
blocks={blocks}
loaderTitle={loaderTitle}
blockUpdateHandler={blockUpdateHandler}
sidebarBlockToRender={sidebarBlockToRender}
sidebarToRender={sidebarToRender}
blockToRender={blockToRender}
enableBlockLeftResize={enableBlockLeftResize}
enableBlockRightResize={enableBlockRightResize}

View File

@ -3,25 +3,26 @@ import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { MoreVertical } from "lucide-react";
// hooks
import { useChart } from "./hooks";
import { useChart } from "components/gantt-chart/hooks";
// ui
import { Loader } from "@plane/ui";
// components
import { CycleGanttSidebarBlock } from "components/cycles";
// helpers
import { findTotalDaysInRange } from "helpers/date-time.helper";
// types
import { IBlockUpdateData, IGanttBlock } from "./types";
import { IBlockUpdateData, IGanttBlock } from "components/gantt-chart/types";
type Props = {
title: string;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blocks: IGanttBlock[] | null;
SidebarBlockRender: React.FC<any>;
enableReorder: boolean;
};
export const GanttSidebar: React.FC<Props> = (props) => {
export const CycleGanttSidebar: React.FC<Props> = (props) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { title, blockUpdateHandler, blocks, SidebarBlockRender, enableReorder } = props;
const { title, blockUpdateHandler, blocks, enableReorder } = props;
const router = useRouter();
const { cycleId } = router.query;
@ -128,7 +129,7 @@ export const GanttSidebar: React.FC<Props> = (props) => {
)}
<div className="flex-grow truncate h-full flex items-center justify-between gap-2">
<div className="flex-grow truncate">
<SidebarBlockRender data={block.data} />
<CycleGanttSidebarBlock data={block.data} />
</div>
<div className="flex-shrink-0 text-sm text-custom-text-200">
{duration} day{duration > 1 ? "s" : ""}

View File

@ -0,0 +1,4 @@
export * from "./cycle-sidebar";
export * from "./module-sidebar";
export * from "./sidebar";
export * from "./project-view-sidebar";

View File

@ -3,25 +3,26 @@ import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { MoreVertical } from "lucide-react";
// hooks
import { useChart } from "./hooks";
import { useChart } from "components/gantt-chart/hooks";
// ui
import { Loader } from "@plane/ui";
// components
import { ModuleGanttSidebarBlock } from "components/modules";
// helpers
import { findTotalDaysInRange } from "helpers/date-time.helper";
// types
import { IBlockUpdateData, IGanttBlock } from "./types";
import { IBlockUpdateData, IGanttBlock } from "components/gantt-chart";
type Props = {
title: string;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blocks: IGanttBlock[] | null;
SidebarBlockRender: React.FC<any>;
enableReorder: boolean;
};
export const GanttSidebar: React.FC<Props> = (props) => {
export const ModuleGanttSidebar: React.FC<Props> = (props) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { title, blockUpdateHandler, blocks, SidebarBlockRender, enableReorder } = props;
const { title, blockUpdateHandler, blocks, enableReorder } = props;
const router = useRouter();
const { cycleId } = router.query;
@ -128,7 +129,7 @@ export const GanttSidebar: React.FC<Props> = (props) => {
)}
<div className="flex-grow truncate h-full flex items-center justify-between gap-2">
<div className="flex-grow truncate">
<SidebarBlockRender data={block.data} />
<ModuleGanttSidebarBlock data={block.data} />
</div>
<div className="flex-shrink-0 text-sm text-custom-text-200">
{duration} day{duration > 1 ? "s" : ""}

View File

@ -0,0 +1,160 @@
import { useRouter } from "next/router";
import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { MoreVertical } from "lucide-react";
// hooks
import { useChart } from "components/gantt-chart/hooks";
// ui
import { Loader } from "@plane/ui";
// components
import { IssueGanttSidebarBlock } from "components/issues";
// helpers
import { findTotalDaysInRange } from "helpers/date-time.helper";
// types
import { IBlockUpdateData, IGanttBlock } from "components/gantt-chart/types";
type Props = {
title: string;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blocks: IGanttBlock[] | null;
enableReorder: boolean;
enableQuickIssueCreate?: boolean;
};
export const ProjectViewGanttSidebar: React.FC<Props> = (props) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { title, blockUpdateHandler, blocks, enableReorder } = props;
const router = useRouter();
const { cycleId } = router.query;
const { activeBlock, dispatch } = useChart();
// update the active block on hover
const updateActiveBlock = (block: IGanttBlock | null) => {
dispatch({
type: "PARTIAL_UPDATE",
payload: {
activeBlock: block,
},
});
};
const handleOrderChange = (result: DropResult) => {
if (!blocks) return;
const { source, destination } = result;
// return if dropped outside the list
if (!destination) return;
// return if dropped on the same index
if (source.index === destination.index) return;
let updatedSortOrder = blocks[source.index].sort_order;
// update the sort order to the lowest if dropped at the top
if (destination.index === 0) updatedSortOrder = blocks[0].sort_order - 1000;
// update the sort order to the highest if dropped at the bottom
else if (destination.index === blocks.length - 1) updatedSortOrder = blocks[blocks.length - 1].sort_order + 1000;
// update the sort order to the average of the two adjacent blocks if dropped in between
else {
const destinationSortingOrder = blocks[destination.index].sort_order;
const relativeDestinationSortingOrder =
source.index < destination.index
? blocks[destination.index + 1].sort_order
: blocks[destination.index - 1].sort_order;
updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2;
}
// extract the element from the source index and insert it at the destination index without updating the entire array
const removedElement = blocks.splice(source.index, 1)[0];
blocks.splice(destination.index, 0, removedElement);
// call the block update handler with the updated sort order, new and old index
blockUpdateHandler(removedElement.data, {
sort_order: {
destinationIndex: destination.index,
newSortOrder: updatedSortOrder,
sourceIndex: source.index,
},
});
};
return (
<DragDropContext onDragEnd={handleOrderChange}>
<StrictModeDroppable droppableId="gantt-sidebar">
{(droppableProvided) => (
<div
id={`gantt-sidebar-${cycleId}`}
className="max-h-full overflow-y-auto pl-2.5 mt-3"
ref={droppableProvided.innerRef}
{...droppableProvided.droppableProps}
>
<>
{blocks ? (
blocks.map((block, index) => {
const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? "", true);
return (
<Draggable
key={`sidebar-block-${block.id}`}
draggableId={`sidebar-block-${block.id}`}
index={index}
isDragDisabled={!enableReorder}
>
{(provided, snapshot) => (
<div
className={`h-11 ${snapshot.isDragging ? "bg-custom-background-80 rounded" : ""}`}
onMouseEnter={() => updateActiveBlock(block)}
onMouseLeave={() => updateActiveBlock(null)}
ref={provided.innerRef}
{...provided.draggableProps}
>
<div
id={`sidebar-block-${block.id}`}
className={`group h-full w-full flex items-center gap-2 rounded-l px-2 pr-4 ${
activeBlock?.id === block.id ? "bg-custom-background-80" : ""
}`}
>
{enableReorder && (
<button
type="button"
className="rounded p-0.5 text-custom-sidebar-text-200 flex flex-shrink-0 opacity-0 group-hover:opacity-100"
{...provided.dragHandleProps}
>
<MoreVertical className="h-3.5 w-3.5" />
<MoreVertical className="h-3.5 w-3.5 -ml-5" />
</button>
)}
<div className="flex-grow truncate h-full flex items-center justify-between gap-2">
<div className="flex-grow truncate">
<IssueGanttSidebarBlock data={block.data} handleIssue={blockUpdateHandler} />
</div>
<div className="flex-shrink-0 text-sm text-custom-text-200">
{duration} day{duration > 1 ? "s" : ""}
</div>
</div>
</div>
</div>
)}
</Draggable>
);
})
) : (
<Loader className="pr-2 space-y-3">
<Loader.Item height="34px" />
<Loader.Item height="34px" />
<Loader.Item height="34px" />
<Loader.Item height="34px" />
</Loader>
)}
{droppableProvided.placeholder}
</>
</div>
)}
</StrictModeDroppable>
</DragDropContext>
);
};

View File

@ -3,28 +3,27 @@ import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { MoreVertical } from "lucide-react";
// hooks
import { useChart } from "./hooks";
import { useChart } from "components/gantt-chart/hooks";
// ui
import { Loader } from "@plane/ui";
// components
import { GanttInlineCreateIssueForm } from "components/issues";
import { GanttInlineCreateIssueForm, IssueGanttSidebarBlock } from "components/issues";
// helpers
import { findTotalDaysInRange } from "helpers/date-time.helper";
// types
import { IBlockUpdateData, IGanttBlock } from "./types";
import { IGanttBlock, IBlockUpdateData } from "components/gantt-chart/types";
type Props = {
title: string;
blockUpdateHandler: (block: any, payload: IBlockUpdateData) => void;
blocks: IGanttBlock[] | null;
sidebarBlockToRender: (block: any) => React.ReactNode;
enableReorder: boolean;
enableQuickIssueCreate?: boolean;
};
export const GanttSidebar: React.FC<Props> = (props) => {
export const IssueGanttSidebar: React.FC<Props> = (props) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { title, blockUpdateHandler, blocks, sidebarBlockToRender, enableReorder, enableQuickIssueCreate } = props;
const { title, blockUpdateHandler, blocks, enableReorder, enableQuickIssueCreate } = props;
const router = useRouter();
const { cycleId } = router.query;
@ -130,7 +129,9 @@ export const GanttSidebar: React.FC<Props> = (props) => {
</button>
)}
<div className="flex-grow truncate h-full flex items-center justify-between gap-2">
<div className="flex-grow truncate">{sidebarBlockToRender(block.data)}</div>
<div className="flex-grow truncate">
<IssueGanttSidebarBlock data={block.data} handleIssue={blockUpdateHandler} />
</div>
<div className="flex-shrink-0 text-sm text-custom-text-200">
{duration} day{duration > 1 ? "s" : ""}
</div>
@ -151,7 +152,7 @@ export const GanttSidebar: React.FC<Props> = (props) => {
)}
{droppableProvided.placeholder}
</>
<GanttInlineCreateIssueForm />
{enableQuickIssueCreate && <GanttInlineCreateIssueForm />}
</div>
)}
</StrictModeDroppable>

View File

@ -31,6 +31,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
cycle: cycleStore,
cycleIssueFilter: cycleIssueFilterStore,
project: projectStore,
projectMember: { projectMembers },
projectState: projectStateStore,
commandPalette: commandPaletteStore,
} = useMobxStore();
@ -102,7 +103,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
[issueFilterStore, projectId, workspaceSlug]
);
const cyclesList = projectId ? cycleStore.cycles[projectId.toString()] : undefined;
const cyclesList = cycleStore.projectCycles;
const cycleDetails = cycleId ? cycleStore.getCycleById(cycleId.toString()) : undefined;
return (
@ -112,7 +113,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
onClose={() => setAnalyticsModal(false)}
cycleDetails={cycleDetails ?? undefined}
/>
<div className="relative w-full flex items-center z-10 justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="relative w-full flex items-center z-10 h-[3.75rem] justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2">
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
@ -178,7 +179,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
members={projectMembers?.map((m) => m.member)}
states={projectStateStore.states?.[projectId?.toString() ?? ""] ?? undefined}
/>
</FiltersDropdown>

View File

@ -18,9 +18,7 @@ export const CyclesHeader: FC = observer(() => {
const { currentProjectDetails } = projectStore;
return (
<div
className={`relative z-10 flex w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
>
<div className="relative z-10 flex w-full flex-shrink-0 flex-row items-center justify-between h-[3.75rem] gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div>
<Breadcrumbs>
@ -51,6 +49,7 @@ export const CyclesHeader: FC = observer(() => {
<div className="flex items-center gap-3">
<Button
variant="primary"
size="sm"
prependIcon={<Plus />}
onClick={() => commandPaletteStore.toggleCreateCycleModal(true)}
>

View File

@ -99,7 +99,7 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
return (
<>
<CreateUpdateWorkspaceViewModal isOpen={createViewModal} onClose={() => setCreateViewModal(false)} />
<div className="relative w-full flex items-center z-10 justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="relative w-full flex items-center z-10 h-[3.75rem] justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div>
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
@ -163,7 +163,7 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
</FiltersDropdown>
</>
)}
<Button variant="primary" prependIcon={<PlusIcon />} onClick={() => setCreateViewModal(true)}>
<Button variant="primary" size="sm" prependIcon={<PlusIcon />} onClick={() => setCreateViewModal(true)}>
New View
</Button>
</div>

View File

@ -31,6 +31,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
module: moduleStore,
moduleFilter: moduleFilterStore,
project: projectStore,
projectMember: { projectMembers },
projectState: projectStateStore,
commandPalette: commandPaletteStore,
} = useMobxStore();
@ -111,7 +112,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
onClose={() => setAnalyticsModal(false)}
moduleDetails={moduleDetails ?? undefined}
/>
<div className="relative w-full flex items-center z-10 justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="relative w-full flex items-center z-10 h-[3.75rem] justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2">
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
@ -177,7 +178,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
members={projectMembers?.map((m) => m.member)}
states={projectStateStore.states?.[projectId?.toString() ?? ""] ?? undefined}
/>
</FiltersDropdown>

View File

@ -23,9 +23,7 @@ export const ModulesListHeader: React.FC = observer(() => {
const { storedValue: modulesView, setValue: setModulesView } = useLocalStorage("modules_view", "grid");
return (
<div
className={`relative z-10 flex w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
>
<div className="relative z-10 flex w-full flex-shrink-0 flex-row items-center justify-between h-[3.75rem] gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<div>
<Breadcrumbs>
@ -76,6 +74,7 @@ export const ModulesListHeader: React.FC = observer(() => {
</div>
<Button
variant="primary"
size="sm"
prependIcon={<Plus />}
onClick={() => commandPaletteStore.toggleCreateModuleModal(true)}
>

View File

@ -39,7 +39,7 @@ export const PageDetailsHeader: FC<IPagesHeaderProps> = observer((props) => {
);
return (
<div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="relative flex w-full flex-shrink-0 flex-row z-10 h-[3.75rem] items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div>
<Breadcrumbs>

View File

@ -22,7 +22,7 @@ export const PagesHeader: FC<IPagesHeaderProps> = observer((props) => {
const { currentProjectDetails } = projectStore;
return (
<div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="relative flex w-full flex-shrink-0 flex-row z-10 h-[3.75rem] items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div>
<Breadcrumbs>

View File

@ -2,9 +2,7 @@
import { Breadcrumbs } from "@plane/ui";
export const ProfilePreferencesHeader = () => (
<div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
>
<div className="relative flex w-full flex-shrink-0 flex-row z-10 h-[3.75rem] items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div>
<Breadcrumbs>

View File

@ -38,7 +38,7 @@ export const ProjectArchivedIssueDetailsHeader: FC = observer(() => {
);
return (
<div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="relative flex w-full flex-shrink-0 flex-row z-10 h-[3.75rem] items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div>
<Breadcrumbs>

View File

@ -22,6 +22,7 @@ export const ProjectArchivedIssuesHeader: FC = observer(() => {
const {
project: projectStore,
projectMember: { projectMembers },
archivedIssueFilters: archivedIssueFiltersStore,
projectState: projectStateStore,
} = useMobxStore();
@ -70,7 +71,7 @@ export const ProjectArchivedIssuesHeader: FC = observer(() => {
};
return (
<div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="relative flex w-full flex-shrink-0 flex-row z-10 h-14 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div className="block md:hidden">
<button
@ -119,7 +120,7 @@ export const ProjectArchivedIssuesHeader: FC = observer(() => {
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.archived_issues[activeLayout] : undefined
}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
members={projectMembers?.map((m) => m.member)}
states={projectStateStore.states?.[projectId?.toString() ?? ""] ?? undefined}
/>
</FiltersDropdown>

View File

@ -16,7 +16,7 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
const { currentProjectDetails } = projectStore;
return (
<div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="relative flex w-full flex-shrink-0 flex-row z-10 h-[3.75rem] items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div>
<Breadcrumbs>

View File

@ -21,7 +21,7 @@ export const ProjectInboxHeader: FC = observer(() => {
const { currentProjectDetails } = projectStore;
return (
<div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="relative flex w-full flex-shrink-0 flex-row z-10 h-[3.75rem] items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div>
<Breadcrumbs>

View File

@ -32,7 +32,7 @@ export const ProjectIssueDetailsHeader: FC = observer(() => {
);
return (
<div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="relative flex w-full flex-shrink-0 flex-row z-10 h-[3.75rem] items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div>
<Breadcrumbs>

View File

@ -26,6 +26,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
const {
issueFilter: issueFilterStore,
project: projectStore,
projectMember: { projectMembers },
projectState: projectStateStore,
inbox: inboxStore,
commandPalette: commandPaletteStore,
@ -104,7 +105,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
onClose={() => setAnalyticsModal(false)}
projectDetails={currentProjectDetails ?? undefined}
/>
<div className="relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="relative flex w-full flex-shrink-0 flex-row z-10 h-[3.75rem] items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div className="block md:hidden">
<button
@ -172,7 +173,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
members={projectMembers?.map((m) => m.member)}
states={projectStateStore.states?.[projectId?.toString() ?? ""] ?? undefined}
/>
</FiltersDropdown>

View File

@ -22,9 +22,7 @@ export const ProjectSettingHeader: FC<IProjectSettingHeader> = observer((props)
const { currentProjectDetails } = projectStore;
return (
<div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
>
<div className="relative flex w-full flex-shrink-0 flex-row z-10 h-[3.75rem] items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div>
<Breadcrumbs>

View File

@ -23,6 +23,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
issueFilter: issueFilterStore,
projectViewFilters: projectViewFiltersStore,
project: projectStore,
projectMember: { projectMembers },
projectState: projectStateStore,
projectViews: projectViewsStore,
} = useMobxStore();
@ -94,7 +95,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
const viewDetails = viewId ? projectViewsStore.viewDetails[viewId.toString()] : undefined;
return (
<div className="relative w-full flex items-center z-10 justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="relative w-full flex items-center z-10 h-[3.75rem] justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2">
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
@ -163,7 +164,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined
}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? undefined}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
members={projectMembers?.map((m) => m.member)}
states={projectStateStore.states?.[projectId?.toString() ?? ""] ?? undefined}
/>
</FiltersDropdown>

View File

@ -31,9 +31,7 @@ export const ProjectViewsHeader: React.FC = observer(() => {
projectId={projectId.toString()}
/>
)}
<div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
>
<div className="relative flex w-full flex-shrink-0 flex-row z-10 h-[3.75rem] items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div>
<Breadcrumbs>

View File

@ -16,9 +16,7 @@ export const ProjectsHeader = observer(() => {
const projectsList = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : [];
return (
<div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
>
<div className="relative flex w-full flex-shrink-0 flex-row z-10 h-[3.75rem] items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div>
<Breadcrumbs>
@ -43,7 +41,7 @@ export const ProjectsHeader = observer(() => {
</div>
)}
<Button prependIcon={<Plus />} size="md" onClick={() => commandPaletteStore.toggleCreateProjectModal(true)}>
<Button prependIcon={<Plus />} size="sm" onClick={() => commandPaletteStore.toggleCreateProjectModal(true)}>
Add Project
</Button>
</div>

View File

@ -17,9 +17,7 @@ export const UserProfileHeader: FC<IUserProfileHeader> = observer((props) => {
const { workspaceSlug } = router.query;
return (
<div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
>
<div className="relative flex w-full flex-shrink-0 flex-row z-10 h-[3.75rem] items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div>
<Breadcrumbs>

View File

@ -9,7 +9,7 @@ export const WorkspaceAnalyticsHeader = () => {
return (
<>
<div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
className={`relative flex w-full flex-shrink-0 flex-row z-10 h-[3.75rem] items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
>
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div className="block md:hidden">

View File

@ -7,6 +7,7 @@ import githubBlackImage from "/public/logos/github-black.png";
import githubWhiteImage from "/public/logos/github-white.png";
// components
import { ProductUpdatesModal } from "components/common";
import { Breadcrumbs } from "@plane/ui";
export const WorkspaceDashboardHeader = () => {
const [isProductUpdatesModalOpen, setIsProductUpdatesModalOpen] = useState(false);
@ -16,25 +17,30 @@ export const WorkspaceDashboardHeader = () => {
return (
<>
<ProductUpdatesModal isOpen={isProductUpdatesModalOpen} setIsOpen={setIsProductUpdatesModalOpen} />
<div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
>
<div className="flex items-center gap-2 pl-3">
<LayoutGrid size={14} strokeWidth={2} />
Dashboard
<div className="relative flex w-full flex-shrink-0 flex-row z-10 h-[3.75rem] items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 whitespace-nowrap overflow-ellipsis">
<div>
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
icon={<LayoutGrid className="h-4 w-4 text-custom-text-300" />}
label="Dashboard"
/>
</Breadcrumbs>
</div>
</div>
<div className="flex items-center gap-3 px-3">
<a
href="https://plane.so/changelog"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5 bg-custom-background-80 text-xs font-medium py-1.5 px-3 rounded"
className="flex items-center gap-1.5 bg-custom-background-80 text-xs font-medium py-1.5 px-3 rounded flex-shrink-0"
>
<Zap size={14} strokeWidth={2} fill="rgb(var(--color-text-100))" />
{"What's New?"}
</a>
<a
className="flex items-center gap-1.5 bg-custom-background-80 text-xs font-medium py-1.5 px-3 rounded"
className="flex items-center gap-1.5 bg-custom-background-80 text-xs font-medium py-1.5 px-3 rounded flex-shrink-0"
href="https://github.com/makeplane/plane"
target="_blank"
rel="noopener noreferrer"

View File

@ -20,9 +20,7 @@ export const WorkspaceSettingHeader: FC<IWorkspaceSettingHeader> = observer((pro
const { workspaceSlug } = router.query;
return (
<div
className={`relative flex w-full flex-shrink-0 flex-row z-10 items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4`}
>
<div className="relative flex w-full flex-shrink-0 flex-row z-10 h-[3.75rem] items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div>
<Breadcrumbs>

View File

@ -160,7 +160,7 @@ export const InboxActionsHeader = observer(() => {
Snooze
</Button>
</Popover.Button>
<Popover.Panel className="w-80 p-2 absolute right-0 z-10 mt-2 rounded-md border border-custom-border-200 bg-custom-background-80 shadow-lg">
<Popover.Panel className="w-80 p-2 absolute right-0 z-10 mt-2 rounded-md bg-custom-background-100 shadow-lg">
{({ close }) => (
<div className="w-full h-full flex flex-col gap-y-1">
<DatePicker

View File

@ -45,13 +45,13 @@ export const InboxIssueCard: React.FC<Props> = (props) => {
<div
className={`grid h-6 w-6 place-items-center rounded border items-center shadow-sm ${
issue.priority === "urgent"
? "border-red-500/20 bg-red-500/20 text-red-500"
? "border-red-500/20 bg-red-500/20"
: issue.priority === "high"
? "border-orange-500/20 bg-orange-500/20 text-orange-500"
? "border-orange-500/20 bg-orange-500/20"
: issue.priority === "medium"
? "border-yellow-500/20 bg-yellow-500/20 text-yellow-500"
? "border-yellow-500/20 bg-yellow-500/20"
: issue.priority === "low"
? "border-green-500/20 bg-green-500/20 text-green-500"
? "border-green-500/20 bg-green-500/20"
: "border-custom-border-200"
}`}
>

View File

@ -41,7 +41,7 @@ export const AcceptIssueModal: React.FC<Props> = ({ isOpen, onClose, data, onSub
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
@ -55,7 +55,7 @@ export const AcceptIssueModal: React.FC<Props> = ({ isOpen, onClose, data, onSub
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-green-500/20 p-4">
@ -75,10 +75,10 @@ export const AcceptIssueModal: React.FC<Props> = ({ isOpen, onClose, data, onSub
</p>
</span>
<div className="flex justify-end gap-2">
<Button variant="neutral-primary" onClick={handleClose}>
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="primary" onClick={handleAccept} loading={isAccepting}>
<Button variant="primary" size="sm" tabIndex={1} onClick={handleAccept} loading={isAccepting}>
{isAccepting ? "Accepting..." : "Accept Issue"}
</Button>
</div>

View File

@ -85,7 +85,7 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
@ -99,7 +99,7 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-200 bg-custom-background-100 p-5 text-left shadow-xl transition-all sm:w-full sm:max-w-2xl">
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 p-5 text-left shadow-custom-shadow-md transition-all sm:w-full sm:max-w-2xl">
<form onSubmit={handleSubmit(handleFormSubmit)}>
<div className="space-y-5">
<h3 className="text-xl font-semibold leading-6 text-custom-text-100">Create Inbox Issue</h3>
@ -175,10 +175,10 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
<ToggleSwitch value={createMore} onChange={() => {}} size="md" />
</div>
<div className="flex items-center gap-2">
<Button variant="neutral-primary" onClick={() => handleClose()}>
<Button variant="neutral-primary" size="sm" onClick={() => handleClose()}>
Discard
</Button>
<Button variant="primary" type="submit" loading={isSubmitting}>
<Button variant="primary" size="sm" type="submit" loading={isSubmitting}>
{isSubmitting ? "Adding Issue..." : "Add Issue"}
</Button>
</div>

View File

@ -41,7 +41,7 @@ export const DeclineIssueModal: React.FC<Props> = ({ isOpen, onClose, data, onSu
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
@ -55,7 +55,7 @@ export const DeclineIssueModal: React.FC<Props> = ({ isOpen, onClose, data, onSu
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-red-500/20 p-4">
@ -75,10 +75,10 @@ export const DeclineIssueModal: React.FC<Props> = ({ isOpen, onClose, data, onSu
</p>
</span>
<div className="flex justify-end gap-2">
<Button variant="neutral-primary" onClick={handleClose}>
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="danger" onClick={handleDecline} loading={isDeclining}>
<Button variant="danger" size="sm" tabIndex={1} onClick={handleDecline} loading={isDeclining}>
{isDeclining ? "Declining..." : "Decline Issue"}
</Button>
</div>

View File

@ -78,7 +78,7 @@ export const DeleteInboxIssueModal: React.FC<Props> = observer(({ isOpen, onClos
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
@ -92,7 +92,7 @@ export const DeleteInboxIssueModal: React.FC<Props> = observer(({ isOpen, onClos
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-red-500/20 p-4">
@ -112,10 +112,10 @@ export const DeleteInboxIssueModal: React.FC<Props> = observer(({ isOpen, onClos
</p>
</span>
<div className="flex justify-end gap-2">
<Button variant="neutral-primary" onClick={onClose}>
<Button variant="neutral-primary" size="sm" onClick={onClose}>
Cancel
</Button>
<Button variant="danger" onClick={handleDelete} loading={isDeleting}>
<Button variant="danger" size="sm" tabIndex={1} onClick={handleDelete} loading={isDeleting}>
{isDeleting ? "Deleting..." : "Delete Issue"}
</Button>
</div>

View File

@ -81,7 +81,7 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto p-4 sm:p-6 md:p-20">
@ -94,7 +94,7 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative mx-auto max-w-2xl transform rounded-xl border border-custom-border-200 bg-custom-background-100 shadow-2xl transition-all">
<Dialog.Panel className="relative mx-auto max-w-2xl transform rounded-lg bg-custom-background-100 shadow-custom-shadow-md transition-all">
<Combobox
value={selectedItem}
onChange={(value) => {
@ -166,10 +166,10 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
{filteredIssues.length > 0 && (
<div className="flex items-center justify-end gap-2 p-3">
<Button variant="neutral-primary" onClick={handleClose}>
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="primary" onClick={handleSubmit}>
<Button variant="primary" size="sm" onClick={handleSubmit}>
Mark as original
</Button>
</div>

View File

@ -78,7 +78,7 @@ export const DeleteImportModal: React.FC<Props> = ({ isOpen, handleClose, data,
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
@ -92,7 +92,7 @@ export const DeleteImportModal: React.FC<Props> = ({ isOpen, handleClose, data,
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-red-500/20 p-4">
@ -127,11 +127,13 @@ export const DeleteImportModal: React.FC<Props> = ({ isOpen, handleClose, data,
/>
</div>
<div className="flex justify-end gap-2">
<Button variant="neutral-primary" onClick={handleClose}>
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button
variant="danger"
size="sm"
tabIndex={1}
onClick={handleDeletion}
disabled={!confirmDeleteImport}
loading={deleteLoading}

View File

@ -163,7 +163,7 @@ export const GithubImporterRoot: React.FC<Props> = ({ user }) => {
return (
<form onSubmit={handleSubmit(createGithubImporterService)}>
<div className="space-y-2">
<div className="space-y-2 mt-4">
<Link href={`/${workspaceSlug}/settings/imports`}>
<div className="inline-flex cursor-pointer items-center gap-2 text-sm font-medium text-custom-text-200 hover:text-custom-text-100">
<ArrowLeft className="h-3 w-3" />
@ -191,9 +191,7 @@ export const GithubImporterRoot: React.FC<Props> = ({ user }) => {
}`}
>
<integration.icon
width="18px"
height="18px"
color={index <= activeIntegrationState() ? "#ffffff" : "#d1d5db"}
className={`w-5 h-5 ${index <= activeIntegrationState() ? "text-white" : "text-custom-text-400"}`}
/>
</div>
{index < integrationWorkflowData.length - 1 && (

View File

@ -56,6 +56,7 @@ export const JiraGetImportDetail: React.FC = observer(() => {
ref={ref}
placeholder="XXXXXXXX"
className="w-full"
autoComplete="off"
/>
)}
/>
@ -94,7 +95,7 @@ export const JiraGetImportDetail: React.FC = observer(() => {
<div className="grid grid-cols-1 gap-10 md:grid-cols-2">
<div className="col-span-1">
<h3 className="font-semibold">Jira Email Address</h3>
<p className="text-sm text-custom-text-200">Enter the Gmail account that you use in Jira account</p>
<p className="text-sm text-custom-text-200">Enter the Email account that you use in Jira account</p>
</div>
<div className="col-span-1">
<Controller

View File

@ -5,7 +5,7 @@ import { useRouter } from "next/router";
import { mutate } from "swr";
import { FormProvider, useForm } from "react-hook-form";
// icons
import { ArrowLeft, Check, List, Settings } from "lucide-react";
import { ArrowLeft, Check, List, Settings, Users2 } from "lucide-react";
// services
import { JiraImporterService } from "services/integrations";
// fetch keys
@ -98,7 +98,7 @@ export const JiraImporterRoot: React.FC<Props> = ({ user }) => {
};
return (
<div className="flex h-full flex-col space-y-2">
<div className="flex h-full flex-col space-y-2 mt-4">
<Link href={`/${workspaceSlug}/settings/imports`}>
<div className="inline-flex cursor-pointer items-center gap-2 text-sm font-medium text-custom-text-200 hover:text-custom-text-100">
<div>
@ -136,9 +136,7 @@ export const JiraImporterRoot: React.FC<Props> = ({ user }) => {
}`}
>
<integration.icon
width="18px"
height="18px"
color={index <= activeIntegrationState() ? "#ffffff" : "#d1d5db"}
className={`w-5 h-5 ${index <= activeIntegrationState() ? "text-white" : "text-custom-text-400"}`}
/>
</button>
{index < integrationWorkflowData.length - 1 && (

View File

@ -112,7 +112,7 @@ export const SingleIntegrationCard: React.FC<Props> = observer(({ integration })
<h3 className="flex items-center gap-2 text-sm font-medium">
{integration.title}
{workspaceIntegrations
? isInstalled && <CheckCircle className="h-3.5 w-3.5 text-white fill-green-500" />
? isInstalled && <CheckCircle className="h-3.5 w-3.5 text-green-500 fill-transparent" />
: null}
</h3>
<p className="text-sm text-custom-text-200 tracking-tight">

View File

@ -1,10 +1,7 @@
import { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import useSWR from "swr";
// ui
import { Tooltip } from "@plane/ui";
import { DeleteAttachmentModal } from "./delete-attachment-modal";
@ -13,7 +10,7 @@ import { getFileIcon } from "components/icons";
import { AlertCircle, X } from "lucide-react";
// services
import { IssueAttachmentService } from "services/issue";
import { ProjectService } from "services/project";
import { ProjectMemberService } from "services/project";
// fetch-key
import { ISSUE_ATTACHMENTS, PROJECT_MEMBERS } from "constants/fetch-keys";
// helper
@ -25,7 +22,7 @@ import { IIssueAttachment } from "types";
// services
const issueAttachmentService = new IssueAttachmentService();
const projectService = new ProjectService();
const projectMemberService = new ProjectMemberService();
export const IssueAttachments = () => {
const [deleteAttachment, setDeleteAttachment] = useState<IIssueAttachment | null>(null);
@ -44,7 +41,7 @@ export const IssueAttachments = () => {
const { data: people } = useSWR(
workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null,
workspaceSlug && projectId
? () => projectService.fetchProjectMembers(workspaceSlug as string, projectId as string)
? () => projectMemberService.fetchProjectMembers(workspaceSlug as string, projectId as string)
: null
);

View File

@ -74,7 +74,7 @@ export const DeleteAttachmentModal: React.FC<Props> = ({ isOpen, setIsOpen, data
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-75 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
@ -88,8 +88,8 @@ export const DeleteAttachmentModal: React.FC<Props> = ({ isOpen, setIsOpen, data
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-80 text-left shadow-xl transition-all sm:my-8 sm:w-[40rem]">
<div className="bg-custom-background-80 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-[40rem]">
<div className="px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<AlertTriangle className="h-6 w-6 text-red-600" aria-hidden="true" />
@ -108,12 +108,14 @@ export const DeleteAttachmentModal: React.FC<Props> = ({ isOpen, setIsOpen, data
</div>
</div>
</div>
<div className="flex justify-end gap-2 bg-custom-background-90 p-4 sm:px-6">
<Button variant="neutral-primary" onClick={handleClose}>
<div className="flex justify-end gap-2 p-4 sm:px-6">
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button
variant="danger"
size="sm"
tabIndex={1}
onClick={() => {
handleDeletion(data.id);
handleClose();

View File

@ -140,7 +140,7 @@ export const CommentCard: React.FC<Props> = ({
</form>
<div className={`relative ${isEditing ? "hidden" : ""}`}>
{showAccessSpecifier && (
<div className="absolute top-1 right-1.5 z-[1] text-custom-text-300">
<div className="absolute top-2.5 right-2.5 z-[1] text-custom-text-300">
{comment.access === "INTERNAL" ? <Lock className="h-3 w-3" /> : <Globe2 className="h-3 w-3" />}
</div>
)}

View File

@ -40,7 +40,7 @@ export const ConfirmIssueDiscard: React.FC<Props> = (props) => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
@ -54,7 +54,7 @@ export const ConfirmIssueDiscard: React.FC<Props> = (props) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-[40rem]">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-[40rem]">
<div className="px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mt-3 text-center sm:mt-0 sm:text-left">
@ -69,15 +69,15 @@ export const ConfirmIssueDiscard: React.FC<Props> = (props) => {
</div>
<div className="flex justify-between gap-2 p-4 sm:px-6">
<div>
<Button variant="neutral-primary" onClick={onDiscard}>
<Button variant="neutral-primary" size="sm" onClick={onDiscard}>
Discard
</Button>
</div>
<div className="flex items-center gap-2">
<Button variant="neutral-primary" onClick={onClose}>
<Button variant="neutral-primary" size="sm" onClick={onClose}>
Cancel
</Button>
<Button variant="primary" onClick={handleDeletion} loading={isLoading}>
<Button variant="primary" size="sm" onClick={handleDeletion} loading={isLoading}>
{isLoading ? "Saving..." : "Save Draft"}
</Button>
</div>

View File

@ -78,7 +78,7 @@ export const DeleteArchivedIssueModal: React.FC<Props> = observer((props) => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
@ -92,7 +92,7 @@ export const DeleteArchivedIssueModal: React.FC<Props> = observer((props) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-red-500/20 p-4">
@ -113,10 +113,16 @@ export const DeleteArchivedIssueModal: React.FC<Props> = observer((props) => {
</p>
</span>
<div className="flex justify-end gap-2">
<Button variant="neutral-primary" onClick={onClose}>
<Button variant="neutral-primary" size="sm" onClick={onClose}>
Cancel
</Button>
<Button variant="danger" onClick={handleIssueDelete} loading={isDeleteLoading}>
<Button
variant="danger"
size="sm"
tabIndex={1}
onClick={handleIssueDelete}
loading={isDeleteLoading}
>
{isDeleteLoading ? "Deleting..." : "Delete Issue"}
</Button>
</div>

View File

@ -88,7 +88,7 @@ export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
@ -102,7 +102,7 @@ export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-red-500/20 p-4">
@ -123,10 +123,10 @@ export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
</p>
</span>
<div className="flex justify-end gap-2">
<Button variant="neutral-primary" onClick={onClose}>
<Button variant="neutral-primary" size="sm" onClick={onClose}>
Cancel
</Button>
<Button variant="danger" onClick={handleDeletion} loading={isDeleteLoading}>
<Button variant="danger" size="sm" tabIndex={1} onClick={handleDeletion} loading={isDeleteLoading}>
{isDeleteLoading ? "Deleting..." : "Delete Issue"}
</Button>
</div>

View File

@ -58,7 +58,7 @@ export const DeleteIssueModal: React.FC<Props> = observer((props) => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
@ -72,7 +72,7 @@ export const DeleteIssueModal: React.FC<Props> = observer((props) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 text-left shadow-custom-shadow-md transition-all sm:my-8 sm:w-full sm:max-w-2xl">
<div className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-red-500/20 p-4">
@ -93,10 +93,16 @@ export const DeleteIssueModal: React.FC<Props> = observer((props) => {
</p>
</span>
<div className="flex justify-end gap-2">
<Button variant="neutral-primary" onClick={onClose}>
<Button variant="neutral-primary" size="sm" onClick={onClose}>
Cancel
</Button>
<Button variant="danger" onClick={handleIssueDelete} loading={isDeleteLoading}>
<Button
variant="danger"
size="sm"
tabIndex={1}
onClick={handleIssueDelete}
loading={isDeleteLoading}
>
{isDeleteLoading ? "Deleting..." : "Delete Issue"}
</Button>
</div>

View File

@ -600,11 +600,12 @@ export const DraftIssueForm: FC<IssueFormProps> = (props) => {
<ToggleSwitch value={createMore} onChange={() => {}} size="md" />
</div>
<div className="flex items-center gap-2">
<Button variant="neutral-primary" onClick={handleDiscard}>
<Button variant="neutral-primary" size="sm" onClick={handleDiscard}>
Discard
</Button>
<Button
variant="neutral-primary"
size="sm"
loading={isSubmitting}
onClick={handleSubmit((formData) =>
handleCreateUpdateIssue(formData, data?.id ? "updateDraft" : "createDraft")
@ -615,6 +616,7 @@ export const DraftIssueForm: FC<IssueFormProps> = (props) => {
<Button
loading={isSubmitting}
variant="primary"
size="sm"
onClick={handleSubmit((formData) =>
handleCreateUpdateIssue(formData, data ? "convertToNewIssue" : "createNewIssue")
)}

View File

@ -317,7 +317,7 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
<div className="fixed inset-0 bg-custom-backdrop transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
@ -331,7 +331,7 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-200 bg-custom-background-100 p-5 text-left shadow-xl transition-all sm:w-full sm:max-w-2xl">
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 p-5 text-left shadow-custom-shadow-md transition-all sm:w-full sm:max-w-2xl">
<DraftIssueForm
isOpen={isOpen}
handleFormSubmit={handleFormSubmit}

Some files were not shown because too many files have changed in this diff Show More