forked from github/plane
fix: merge conflict
This commit is contained in:
commit
a434b1008d
205
.github/workflows/build-branch.yml
vendored
Normal file
205
.github/workflows/build-branch.yml
vendored
Normal file
@ -0,0 +1,205 @@
|
||||
|
||||
name: Docker Branch Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
logLevel:
|
||||
description: 'Log level'
|
||||
required: true
|
||||
default: 'warning'
|
||||
tags:
|
||||
description: 'Dev/QA Builds'
|
||||
|
||||
env:
|
||||
gh_branch: ${{ github.ref_name }}
|
||||
img_tag: latest
|
||||
|
||||
jobs:
|
||||
branch_build_and_push:
|
||||
name: Build-Push Web/Space/API/Proxy Docker Image
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3.3.0
|
||||
|
||||
- uses: ASzc/change-string-case-action@v2
|
||||
id: gh_branch_upper_lower
|
||||
with:
|
||||
string: ${{ env.gh_branch }}
|
||||
|
||||
- uses: mad9000/actions-find-and-replace-string@2
|
||||
id: gh_branch_replace_slash
|
||||
with:
|
||||
source: ${{ steps.gh_branch_upper_lower.outputs.lowercase }}
|
||||
find: '/'
|
||||
replace: '-'
|
||||
|
||||
- uses: mad9000/actions-find-and-replace-string@2
|
||||
id: gh_branch_replace_dot
|
||||
with:
|
||||
source: ${{ steps.gh_branch_replace_slash.outputs.value }}
|
||||
find: '.'
|
||||
replace: ''
|
||||
|
||||
- uses: mad9000/actions-find-and-replace-string@2
|
||||
id: gh_branch_clean
|
||||
with:
|
||||
source: ${{ steps.gh_branch_replace_dot.outputs.value }}
|
||||
find: '_'
|
||||
replace: ''
|
||||
- name: Uploading Proxy Source
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: proxy-src-code
|
||||
path: ./nginx
|
||||
- name: Uploading Backend Source
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: backend-src-code
|
||||
path: ./apiserver
|
||||
- name: Uploading Web Source
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: web-src-code
|
||||
path: |
|
||||
./
|
||||
!./apiserver
|
||||
!./nginx
|
||||
!./deploy
|
||||
!./space
|
||||
|
||||
- name: Uploading Space Source
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: space-src-code
|
||||
path: |
|
||||
./
|
||||
!./apiserver
|
||||
!./nginx
|
||||
!./deploy
|
||||
!./web
|
||||
outputs:
|
||||
gh_branch_name: ${{ steps.gh_branch_clean.outputs.value }}
|
||||
|
||||
branch_build_push_frontend:
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [ branch_build_and_push ]
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.5.0
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Downloading Web Source Code
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: web-src-code
|
||||
|
||||
- name: Build and Push Frontend to Docker Container Registry
|
||||
uses: docker/build-push-action@v4.0.0
|
||||
with:
|
||||
context: .
|
||||
file: ./web/Dockerfile.web
|
||||
platforms: linux/amd64
|
||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-private:${{ needs.branch_build_and_push.outputs.gh_branch_name }}
|
||||
push: true
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKET_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
branch_build_push_space:
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [ branch_build_and_push ]
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.5.0
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Downloading Space Source Code
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: space-src-code
|
||||
|
||||
- name: Build and Push Space to Docker Hub
|
||||
uses: docker/build-push-action@v4.0.0
|
||||
with:
|
||||
context: .
|
||||
file: ./space/Dockerfile.space
|
||||
platforms: linux/amd64
|
||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/plane-space-private:${{ needs.branch_build_and_push.outputs.gh_branch_name }}
|
||||
push: true
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKET_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
branch_build_push_backend:
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [ branch_build_and_push ]
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.5.0
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Downloading Backend Source Code
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: backend-src-code
|
||||
|
||||
- name: Build and Push Backend to Docker Hub
|
||||
uses: docker/build-push-action@v4.0.0
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.api
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-private:${{ needs.branch_build_and_push.outputs.gh_branch_name }}
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKET_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
branch_build_push_proxy:
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [ branch_build_and_push ]
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.5.0
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Downloading Proxy Source Code
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: proxy-src-code
|
||||
|
||||
- name: Build and Push Plane-Proxy to Docker Hub
|
||||
uses: docker/build-push-action@v4.0.0
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64
|
||||
tags: ${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-private:${{ needs.branch_build_and_push.outputs.gh_branch_name }}
|
||||
push: true
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKET_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -75,7 +75,7 @@ pnpm-lock.yaml
|
||||
pnpm-workspace.yaml
|
||||
|
||||
.npmrc
|
||||
.secrets
|
||||
tmp/
|
||||
|
||||
## packages
|
||||
dist
|
||||
|
@ -1,7 +1,7 @@
|
||||
from .analytic import urlpatterns as analytic_urls
|
||||
from .asset import urlpatterns as asset_urls
|
||||
from .authentication import urlpatterns as authentication_urls
|
||||
from .configuration import urlpatterns as configuration_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
|
||||
|
@ -30,4 +30,5 @@ class ConfigurationEndpoint(BaseAPIView):
|
||||
data["email_password_login"] = (
|
||||
os.environ.get("ENABLE_EMAIL_PASSWORD", "0") == "1"
|
||||
)
|
||||
data["slack"] = os.environ.get("SLACK_CLIENT_ID", None)
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Python improts
|
||||
import uuid
|
||||
|
||||
import requests
|
||||
# Django imports
|
||||
from django.contrib.auth.hashers import make_password
|
||||
|
||||
@ -25,7 +25,7 @@ from plane.utils.integrations.github import (
|
||||
delete_github_installation,
|
||||
)
|
||||
from plane.api.permissions import WorkSpaceAdminPermission
|
||||
|
||||
from plane.utils.integrations.slack import slack_oauth
|
||||
|
||||
class IntegrationViewSet(BaseViewSet):
|
||||
serializer_class = IntegrationSerializer
|
||||
@ -98,12 +98,19 @@ class WorkspaceIntegrationViewSet(BaseViewSet):
|
||||
config = {"installation_id": installation_id}
|
||||
|
||||
if provider == "slack":
|
||||
metadata = request.data.get("metadata", {})
|
||||
code = request.data.get("code", False)
|
||||
|
||||
if not code:
|
||||
return Response({"error": "Code is required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
slack_response = slack_oauth(code=code)
|
||||
|
||||
metadata = slack_response
|
||||
access_token = metadata.get("access_token", False)
|
||||
team_id = metadata.get("team", {}).get("id", False)
|
||||
if not metadata or not access_token or not team_id:
|
||||
return Response(
|
||||
{"error": "Access token and team id is required"},
|
||||
{"error": "Slack could not be installed. Please try again later"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
config = {"team_id": team_id, "access_token": access_token}
|
||||
|
@ -11,6 +11,7 @@ from plane.api.views import BaseViewSet, BaseAPIView
|
||||
from plane.db.models import SlackProjectSync, WorkspaceIntegration, ProjectMember
|
||||
from plane.api.serializers import SlackProjectSyncSerializer
|
||||
from plane.api.permissions import ProjectBasePermission, ProjectEntityPermission
|
||||
from plane.utils.integrations.slack import slack_oauth
|
||||
|
||||
|
||||
class SlackProjectSyncViewSet(BaseViewSet):
|
||||
@ -32,25 +33,46 @@ class SlackProjectSyncViewSet(BaseViewSet):
|
||||
)
|
||||
|
||||
def create(self, request, slug, project_id, workspace_integration_id):
|
||||
serializer = SlackProjectSyncSerializer(data=request.data)
|
||||
try:
|
||||
code = request.data.get("code", False)
|
||||
|
||||
workspace_integration = WorkspaceIntegration.objects.get(
|
||||
workspace__slug=slug, pk=workspace_integration_id
|
||||
)
|
||||
if not code:
|
||||
return Response(
|
||||
{"error": "Code is required"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save(
|
||||
project_id=project_id,
|
||||
workspace_integration_id=workspace_integration_id,
|
||||
slack_response = slack_oauth(code=code)
|
||||
|
||||
workspace_integration = WorkspaceIntegration.objects.get(
|
||||
workspace__slug=slug, pk=workspace_integration_id
|
||||
)
|
||||
|
||||
workspace_integration = WorkspaceIntegration.objects.get(
|
||||
pk=workspace_integration_id, workspace__slug=slug
|
||||
)
|
||||
|
||||
slack_project_sync = SlackProjectSync.objects.create(
|
||||
access_token=slack_response.get("access_token"),
|
||||
scopes=slack_response.get("scope"),
|
||||
bot_user_id=slack_response.get("bot_user_id"),
|
||||
webhook_url=slack_response.get("incoming_webhook", {}).get("url"),
|
||||
data=slack_response,
|
||||
team_id=slack_response.get("team", {}).get("id"),
|
||||
team_name=slack_response.get("team", {}).get("name"),
|
||||
workspace_integration=workspace_integration,
|
||||
)
|
||||
_ = ProjectMember.objects.get_or_create(
|
||||
member=workspace_integration.actor, role=20, project_id=project_id
|
||||
)
|
||||
|
||||
serializer = SlackProjectSyncSerializer(slack_project_sync)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
except IntegrityError as e:
|
||||
if "already exists" in str(e):
|
||||
return Response(
|
||||
{"error": "Slack is already installed for the project"},
|
||||
status=status.HTTP_410_GONE,
|
||||
)
|
||||
capture_exception(e)
|
||||
return Response(
|
||||
{"error": "Slack could not be installed. Please try again later"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
20
apiserver/plane/utils/integrations/slack.py
Normal file
20
apiserver/plane/utils/integrations/slack.py
Normal file
@ -0,0 +1,20 @@
|
||||
import os
|
||||
import requests
|
||||
|
||||
def slack_oauth(code):
|
||||
SLACK_OAUTH_URL = os.environ.get("SLACK_OAUTH_URL", False)
|
||||
SLACK_CLIENT_ID = os.environ.get("SLACK_CLIENT_ID", False)
|
||||
SLACK_CLIENT_SECRET = os.environ.get("SLACK_CLIENT_SECRET", False)
|
||||
|
||||
# Oauth Slack
|
||||
if SLACK_OAUTH_URL and SLACK_CLIENT_ID and SLACK_CLIENT_SECRET:
|
||||
response = requests.get(
|
||||
SLACK_OAUTH_URL,
|
||||
params={
|
||||
"code": code,
|
||||
"client_id": SLACK_CLIENT_ID,
|
||||
"client_secret": SLACK_CLIENT_SECRET,
|
||||
},
|
||||
)
|
||||
return response.json()
|
||||
return {}
|
@ -2,6 +2,7 @@
|
||||
"name": "@plane/editor-core",
|
||||
"version": "0.0.1",
|
||||
"description": "Core Editor that powers Plane",
|
||||
"private": true,
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.mts",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "@plane/lite-text-editor",
|
||||
"version": "0.0.1",
|
||||
"description": "Package that powers Plane's Comment Editor",
|
||||
"private": true,
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.mts",
|
||||
@ -29,9 +30,6 @@
|
||||
"dependencies": {
|
||||
"@plane/editor-core": "*",
|
||||
"@tiptap/extension-list-item": "^2.1.11",
|
||||
"@types/node": "18.15.3",
|
||||
"@types/react": "^18.2.5",
|
||||
"@types/react-dom": "18.0.11",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^1.2.1",
|
||||
"eslint": "8.36.0",
|
||||
@ -46,6 +44,9 @@
|
||||
"use-debounce": "^9.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.15.3",
|
||||
"@types/react": "^18.2.35",
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"eslint": "^7.32.0",
|
||||
"postcss": "^8.4.29",
|
||||
"tailwind-config-custom": "*",
|
||||
|
@ -1,4 +1,3 @@
|
||||
"use client";
|
||||
import * as React from "react";
|
||||
import {
|
||||
EditorContainer,
|
||||
@ -32,7 +31,7 @@ interface ILiteTextEditor {
|
||||
editorContentCustomClassNames?: string;
|
||||
onChange?: (json: any, html: string) => void;
|
||||
setIsSubmitting?: (
|
||||
isSubmitting: "submitting" | "submitted" | "saved",
|
||||
isSubmitting: "submitting" | "submitted" | "saved"
|
||||
) => void;
|
||||
setShouldShowAlert?: (showAlert: boolean) => void;
|
||||
forwardedRef?: any;
|
||||
@ -127,7 +126,7 @@ const LiteTextEditor = (props: LiteTextEditorProps) => {
|
||||
};
|
||||
|
||||
const LiteTextEditorWithRef = React.forwardRef<EditorHandle, ILiteTextEditor>(
|
||||
(props, ref) => <LiteTextEditor {...props} forwardedRef={ref} />,
|
||||
(props, ref) => <LiteTextEditor {...props} forwardedRef={ref} />
|
||||
);
|
||||
|
||||
LiteTextEditorWithRef.displayName = "LiteTextEditorWithRef";
|
||||
|
@ -1,6 +1,10 @@
|
||||
"use client"
|
||||
import { EditorContainer, EditorContentWrapper, getEditorClassNames, useReadOnlyEditor } from '@plane/editor-core';
|
||||
import * as React from 'react';
|
||||
import * as React from "react";
|
||||
import {
|
||||
EditorContainer,
|
||||
EditorContentWrapper,
|
||||
getEditorClassNames,
|
||||
useReadOnlyEditor,
|
||||
} from "@plane/editor-core";
|
||||
|
||||
interface ICoreReadOnlyEditor {
|
||||
value: string;
|
||||
@ -8,7 +12,7 @@ interface ICoreReadOnlyEditor {
|
||||
noBorder?: boolean;
|
||||
borderOnFocus?: boolean;
|
||||
customClassName?: string;
|
||||
mentionHighlights: string[]
|
||||
mentionHighlights: string[];
|
||||
}
|
||||
|
||||
interface EditorCoreProps extends ICoreReadOnlyEditor {
|
||||
@ -27,31 +31,39 @@ const LiteReadOnlyEditor = ({
|
||||
customClassName,
|
||||
value,
|
||||
forwardedRef,
|
||||
mentionHighlights
|
||||
mentionHighlights,
|
||||
}: EditorCoreProps) => {
|
||||
const editor = useReadOnlyEditor({
|
||||
value,
|
||||
forwardedRef,
|
||||
mentionHighlights
|
||||
mentionHighlights,
|
||||
});
|
||||
|
||||
const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName });
|
||||
const editorClassNames = getEditorClassNames({
|
||||
noBorder,
|
||||
borderOnFocus,
|
||||
customClassName,
|
||||
});
|
||||
|
||||
if (!editor) return null;
|
||||
|
||||
return (
|
||||
<EditorContainer editor={editor} editorClassNames={editorClassNames}>
|
||||
<div className="flex flex-col">
|
||||
<EditorContentWrapper editor={editor} editorContentCustomClassNames={editorContentCustomClassNames} />
|
||||
<EditorContentWrapper
|
||||
editor={editor}
|
||||
editorContentCustomClassNames={editorContentCustomClassNames}
|
||||
/>
|
||||
</div>
|
||||
</EditorContainer >
|
||||
</EditorContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const LiteReadOnlyEditorWithRef = React.forwardRef<EditorHandle, ICoreReadOnlyEditor>((props, ref) => (
|
||||
<LiteReadOnlyEditor {...props} forwardedRef={ref} />
|
||||
));
|
||||
const LiteReadOnlyEditorWithRef = React.forwardRef<
|
||||
EditorHandle,
|
||||
ICoreReadOnlyEditor
|
||||
>((props, ref) => <LiteReadOnlyEditor {...props} forwardedRef={ref} />);
|
||||
|
||||
LiteReadOnlyEditorWithRef.displayName = "LiteReadOnlyEditorWithRef";
|
||||
|
||||
export { LiteReadOnlyEditor , LiteReadOnlyEditorWithRef };
|
||||
export { LiteReadOnlyEditor, LiteReadOnlyEditorWithRef };
|
||||
|
@ -1,5 +1,4 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import * as React from "react";
|
||||
// next-themes
|
||||
import { useTheme } from "next-themes";
|
||||
// tooltip2
|
||||
@ -69,8 +68,16 @@ export const Tooltip: React.FC<Props> = ({
|
||||
</div>
|
||||
}
|
||||
position={position}
|
||||
renderTarget={({ isOpen: isTooltipOpen, ref: eleReference, ...tooltipProps }) =>
|
||||
React.cloneElement(children, { ref: eleReference, ...tooltipProps, ...children.props })
|
||||
renderTarget={({
|
||||
isOpen: isTooltipOpen,
|
||||
ref: eleReference,
|
||||
...tooltipProps
|
||||
}) =>
|
||||
React.cloneElement(children, {
|
||||
ref: eleReference,
|
||||
...tooltipProps,
|
||||
...children.props,
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
@ -2,6 +2,7 @@
|
||||
"name": "@plane/rich-text-editor",
|
||||
"version": "0.0.1",
|
||||
"description": "Rich Text Editor that powers Plane",
|
||||
"private": true,
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.mts",
|
||||
@ -21,19 +22,19 @@
|
||||
"check-types": "tsc --noEmit"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.1.11",
|
||||
"next": "12.3.2",
|
||||
"next-themes": "^0.2.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"@tiptap/core": "^2.1.11"
|
||||
"react-dom": "18.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@plane/editor-core": "*",
|
||||
"@tiptap/extension-code-block-lowlight": "^2.1.11",
|
||||
"@tiptap/extension-horizontal-rule": "^2.1.11",
|
||||
"@tiptap/extension-placeholder": "^2.1.11",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"@tiptap/suggestion": "^2.1.7",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^1.2.1",
|
||||
"highlight.js": "^11.8.0",
|
||||
"lowlight": "^3.0.0",
|
||||
@ -41,8 +42,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.15.3",
|
||||
"@types/react": "^18.2.5",
|
||||
"@types/react-dom": "18.0.11",
|
||||
"@types/react": "^18.2.35",
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"eslint": "^7.32.0",
|
||||
"postcss": "^8.4.29",
|
||||
"react": "^18.2.0",
|
||||
|
@ -8,6 +8,7 @@ interface IRichTextReadOnlyEditor {
|
||||
noBorder?: boolean;
|
||||
borderOnFocus?: boolean;
|
||||
customClassName?: string;
|
||||
mentionHighlights?: string[];
|
||||
}
|
||||
|
||||
interface RichTextReadOnlyEditorProps extends IRichTextReadOnlyEditor {
|
||||
@ -26,10 +27,12 @@ const RichReadOnlyEditor = ({
|
||||
customClassName,
|
||||
value,
|
||||
forwardedRef,
|
||||
mentionHighlights,
|
||||
}: RichTextReadOnlyEditorProps) => {
|
||||
const editor = useReadOnlyEditor({
|
||||
value,
|
||||
forwardedRef,
|
||||
mentionHighlights,
|
||||
});
|
||||
|
||||
const editorClassNames = getEditorClassNames({ noBorder, borderOnFocus, customClassName });
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "eslint-config-custom",
|
||||
"private": true,
|
||||
"version": "0.13.2",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
|
@ -3,6 +3,7 @@
|
||||
"version": "0.13.2",
|
||||
"description": "common tailwind configuration across monorepo",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"autoprefixer": "^10.4.14",
|
||||
|
@ -1,5 +1,7 @@
|
||||
{
|
||||
"name": "@plane/ui",
|
||||
"description": "UI components shared across multiple apps internally",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
|
@ -35,8 +35,11 @@ export const AvatarGroup: React.FC<Props> = (props) => {
|
||||
// calculate total length of avatars inside the group
|
||||
const totalAvatars = React.Children.toArray(children).length;
|
||||
|
||||
// if avatars are equal to max + 1, then we need to show the last avatar as well, if avatars are more than max + 1, then we need to show the count of the remaining avatars
|
||||
const maxAvatarsToRender = totalAvatars <= max + 1 ? max + 1 : max;
|
||||
|
||||
// slice the children to the maximum number of avatars
|
||||
const avatars = React.Children.toArray(children).slice(0, max);
|
||||
const avatars = React.Children.toArray(children).slice(0, maxAvatarsToRender);
|
||||
|
||||
// assign the necessary props from the AvatarGroup component to the Avatar components
|
||||
const avatarsWithUpdatedProps = avatars.map((avatar) => {
|
||||
@ -54,11 +57,14 @@ export const AvatarGroup: React.FC<Props> = (props) => {
|
||||
return (
|
||||
<div className={`flex ${sizeInfo.spacing}`}>
|
||||
{avatarsWithUpdatedProps.map((avatar, index) => (
|
||||
<div key={index} className="ring-1 ring-custom-border-200 rounded-full">
|
||||
<div
|
||||
key={index}
|
||||
className="ring-1 ring-custom-background-100 rounded-full"
|
||||
>
|
||||
{avatar}
|
||||
</div>
|
||||
))}
|
||||
{max < totalAvatars && (
|
||||
{maxAvatarsToRender < totalAvatars && (
|
||||
<Tooltip
|
||||
tooltipContent={`${totalAvatars} total`}
|
||||
disabled={!showTooltip}
|
||||
@ -66,7 +72,7 @@ export const AvatarGroup: React.FC<Props> = (props) => {
|
||||
<div
|
||||
className={`${
|
||||
!isAValidNumber(size) ? sizeInfo.avatarSize : ""
|
||||
} ring-1 ring-custom-border-200 bg-custom-primary-500 text-white rounded-full grid place-items-center text-[9px]`}
|
||||
} ring-1 ring-custom-background-100 bg-custom-primary-10 text-custom-primary-100 rounded-full grid place-items-center text-[9px]`}
|
||||
style={
|
||||
isAValidNumber(size)
|
||||
? {
|
||||
|
@ -1 +0,0 @@
|
||||
export * from "./state-group";
|
@ -1,23 +0,0 @@
|
||||
import React from "react";
|
||||
// types
|
||||
import type { Props } from "../types";
|
||||
// constants
|
||||
import { issueGroupColors } from "constants/data";
|
||||
|
||||
export const BacklogStateIcon: React.FC<Props> = ({
|
||||
width = "14",
|
||||
height = "14",
|
||||
className,
|
||||
color = issueGroupColors["backlog"],
|
||||
}) => (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
className={className}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="10" cy="10" r="9" stroke={color} strokeLinecap="round" strokeDasharray="4 4" />
|
||||
</svg>
|
||||
);
|
@ -1,74 +0,0 @@
|
||||
import React from "react";
|
||||
// types
|
||||
import type { Props } from "../types";
|
||||
// constants
|
||||
import { issueGroupColors } from "constants/data";
|
||||
|
||||
export const CancelledStateIcon: React.FC<Props> = ({
|
||||
width = "14",
|
||||
height = "14",
|
||||
className,
|
||||
color = issueGroupColors["cancelled"],
|
||||
}) => (
|
||||
<svg width={width} height={height} className={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 84.36 84.36">
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M20.45,7.69a39.74,39.74,0,0,1,43.43.54"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M76.67,20.45a39.76,39.76,0,0,1-.53,43.43"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M63.92,76.67a39.78,39.78,0,0,1-43.44-.53"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M7.69,63.92a39.75,39.75,0,0,1,.54-43.44"
|
||||
/>
|
||||
<circle className="cls-2" fill={color} cx="42.18" cy="42.18" r="31.04" />
|
||||
<path
|
||||
className="cls-3"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke="#ffffff"
|
||||
strokeLinecap="square"
|
||||
strokeMiterlimit={10}
|
||||
d="M32.64,32.44q9.54,9.75,19.09,19.48"
|
||||
/>
|
||||
<path
|
||||
className="cls-3"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke="#ffffff"
|
||||
strokeLinecap="square"
|
||||
strokeMiterlimit={10}
|
||||
d="M32.64,51.92,51.73,32.44"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
@ -1,65 +0,0 @@
|
||||
import React from "react";
|
||||
// types
|
||||
import type { Props } from "../types";
|
||||
// constants
|
||||
import { issueGroupColors } from "constants/data";
|
||||
|
||||
export const CompletedStateIcon: React.FC<Props> = ({
|
||||
width = "14",
|
||||
height = "14",
|
||||
className,
|
||||
color = issueGroupColors["completed"],
|
||||
}) => (
|
||||
<svg width={width} height={height} className={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 84.36 84.36">
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M20.45,7.69a39.74,39.74,0,0,1,43.43.54"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M76.67,20.45a39.76,39.76,0,0,1-.53,43.43"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M63.92,76.67a39.78,39.78,0,0,1-43.44-.53"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M7.69,63.92a39.75,39.75,0,0,1,.54-43.44"
|
||||
/>
|
||||
<circle className="cls-2" fill={color} cx="42.18" cy="42.18" r="31.04" />
|
||||
<path
|
||||
className="cls-3"
|
||||
fill="none"
|
||||
strokeWidth={3}
|
||||
stroke="#ffffff"
|
||||
strokeLinecap="square"
|
||||
strokeMiterlimit={10}
|
||||
d="M30.45,43.75l6.61,6.61L53.92,34"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
export * from "./backlog-state-icon";
|
||||
export * from "./cancelled-state-icon";
|
||||
export * from "./completed-state-icon";
|
||||
export * from "./started-state-icon";
|
||||
export * from "./state-group-icon";
|
||||
export * from "./unstarted-state-icon";
|
@ -1,73 +0,0 @@
|
||||
import React from "react";
|
||||
// types
|
||||
import type { Props } from "../types";
|
||||
// constants
|
||||
import { issueGroupColors } from "constants/data";
|
||||
|
||||
export const StartedStateIcon: React.FC<Props> = ({
|
||||
width = "14",
|
||||
height = "14",
|
||||
className,
|
||||
color = issueGroupColors["started"],
|
||||
}) => (
|
||||
<svg width={width} height={height} className={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 83.36 83.36">
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M20,7.19a39.74,39.74,0,0,1,43.43.54"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M76.17,20a39.76,39.76,0,0,1-.53,43.43"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M63.42,76.17A39.78,39.78,0,0,1,20,75.64"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M7.19,63.42A39.75,39.75,0,0,1,7.73,20"
|
||||
/>
|
||||
<path
|
||||
className="cls-2"
|
||||
fill={color}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M42.32,41.21q9.57-14.45,19.13-28.9a35.8,35.8,0,0,0-39.09,0Z"
|
||||
/>
|
||||
<path
|
||||
className="cls-2"
|
||||
fill={color}
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M42.32,41.7,61.45,70.6a35.75,35.75,0,0,1-39.09,0Z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
@ -1,29 +0,0 @@
|
||||
// icons
|
||||
import {
|
||||
BacklogStateIcon,
|
||||
CancelledStateIcon,
|
||||
CompletedStateIcon,
|
||||
StartedStateIcon,
|
||||
UnstartedStateIcon,
|
||||
} from "components/icons";
|
||||
import { TIssueGroupKey } from "types/issue";
|
||||
|
||||
type Props = {
|
||||
stateGroup: TIssueGroupKey;
|
||||
color: string;
|
||||
className?: string;
|
||||
height?: string;
|
||||
width?: string;
|
||||
};
|
||||
|
||||
export const StateGroupIcon: React.FC<Props> = ({ stateGroup, className, color, height = "12px", width = "12px" }) => {
|
||||
if (stateGroup === "backlog")
|
||||
return <BacklogStateIcon className={className} color={color} height={height} width={width} />;
|
||||
else if (stateGroup === "cancelled")
|
||||
return <CancelledStateIcon className={className} color={color} height={height} width={width} />;
|
||||
else if (stateGroup === "completed")
|
||||
return <CompletedStateIcon className={className} color={color} height={height} width={width} />;
|
||||
else if (stateGroup === "started")
|
||||
return <StartedStateIcon className={className} color={color} height={height} width={width} />;
|
||||
else return <UnstartedStateIcon className={className} color={color} height={height} width={width} />;
|
||||
};
|
@ -1,55 +0,0 @@
|
||||
import React from "react";
|
||||
// types
|
||||
import type { Props } from "../types";
|
||||
// constants
|
||||
import { issueGroupColors } from "constants/data";
|
||||
|
||||
export const UnstartedStateIcon: React.FC<Props> = ({
|
||||
width = "14",
|
||||
height = "14",
|
||||
className,
|
||||
color = issueGroupColors["unstarted"],
|
||||
}) => (
|
||||
<svg width={width} height={height} className={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 84.36 84.36">
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M20.45,7.69a39.74,39.74,0,0,1,43.43.54"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M76.67,20.45a39.76,39.76,0,0,1-.53,43.43"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M63.92,76.67a39.78,39.78,0,0,1-43.44-.53"
|
||||
/>
|
||||
<path
|
||||
className="cls-1"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M7.69,63.92a39.75,39.75,0,0,1,.54-43.44"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
6
space/components/icons/types.d.ts
vendored
6
space/components/icons/types.d.ts
vendored
@ -1,6 +0,0 @@
|
||||
export type Props = {
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
color?: string;
|
||||
className?: string;
|
||||
};
|
@ -1,3 +1,5 @@
|
||||
// ui
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
// constants
|
||||
import { issueGroupFilter } from "constants/data";
|
||||
|
||||
@ -8,7 +10,7 @@ export const IssueBlockState = ({ state }: any) => {
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-1 w-full rounded shadow-sm border-[0.5px] border-custom-border-300 duration-300 focus:outline-none px-2.5 py-1 text-xs">
|
||||
<div className="flex items-center w-full gap-1.5 text-custom-text-200">
|
||||
<stateGroup.icon />
|
||||
<StateGroupIcon stateGroup={state.group} color={state.color} />
|
||||
<div className="text-xs">{state?.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,7 +7,6 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { IssueBlockPriority } from "components/issues/board-views/block-priority";
|
||||
import { IssueBlockState } from "components/issues/board-views/block-state";
|
||||
import { IssueBlockLabels } from "components/issues/board-views/block-labels";
|
||||
import { IssueBlockDueDate } from "components/issues/board-views/block-due-date";
|
||||
// interfaces
|
||||
import { IIssue } from "types/issue";
|
||||
@ -37,7 +36,7 @@ export const IssueListBlock = observer(({ issue }: { issue: IIssue }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="py-3 px-4 h-[118px] flex flex-col gap-1.5 bg-custom-background-100 rounded shadow-custom-shadow-sm border-[0.5px] border-custom-border-200">
|
||||
<div className="py-2 px-3 flex flex-col gap-1.5 bg-custom-background-100 rounded shadow-custom-shadow-2xs border-[0.5px] border-custom-border-200 space-y-2 text-sm">
|
||||
{/* id */}
|
||||
<div className="text-xs text-custom-text-300 break-words">
|
||||
{projectStore?.project?.identifier}-{issue?.sequence_id}
|
||||
|
@ -4,8 +4,8 @@ import { observer } from "mobx-react-lite";
|
||||
import { IIssueState } from "types/issue";
|
||||
// constants
|
||||
import { issueGroupFilter } from "constants/data";
|
||||
// icons
|
||||
import { StateGroupIcon } from "components/icons";
|
||||
// ui
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
// mobx hook
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
@ -18,11 +18,11 @@ export const IssueListHeader = observer(({ state }: { state: IIssueState }) => {
|
||||
if (stateGroup === null) return <></>;
|
||||
|
||||
return (
|
||||
<div className="pb-2 px-2 flex items-center">
|
||||
<div className="w-4 h-4 flex justify-center items-center flex-shrink-0">
|
||||
<StateGroupIcon stateGroup={state.group} color={state.color} />
|
||||
<div className="pb-2 px-2 flex items-center gap-2">
|
||||
<div className="w-3.5 h-3.5 flex justify-center items-center flex-shrink-0">
|
||||
<StateGroupIcon stateGroup={state.group} color={state.color} height="14" width="14" />
|
||||
</div>
|
||||
<div className="font-semibold text-custom-text-200 capitalize ml-2 mr-3 truncate">{state?.name}</div>
|
||||
<div className="font-semibold text-custom-text-200 capitalize mr-1 truncate">{state?.name}</div>
|
||||
<span className="text-custom-text-300 rounded-full flex-shrink-0">
|
||||
{store.issue.getCountOfIssuesByState(state.id)}
|
||||
</span>
|
||||
|
@ -38,10 +38,10 @@ export const IssueListBlock: FC<{ issue: IIssue }> = observer((props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center px-6 py-3.5 relative gap-10 bg-custom-background-100">
|
||||
<div className="relative flex items-center gap-5 w-full flex-grow overflow-hidden">
|
||||
<div className="flex items-center p-3 relative gap-10 bg-custom-background-100">
|
||||
<div className="relative flex items-center gap-3 w-full flex-grow overflow-hidden">
|
||||
{/* id */}
|
||||
<div className="flex-shrink-0 text-sm text-custom-text-300">
|
||||
<div className="flex-shrink-0 text-xs text-custom-text-300 font-medium">
|
||||
{projectStore?.project?.identifier}-{issue?.sequence_id}
|
||||
</div>
|
||||
{/* name */}
|
||||
|
@ -2,8 +2,8 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
// interfaces
|
||||
import { IIssueState } from "types/issue";
|
||||
// icons
|
||||
import { StateGroupIcon } from "components/icons";
|
||||
// ui
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
// constants
|
||||
import { issueGroupFilter } from "constants/data";
|
||||
// mobx hook
|
||||
@ -18,12 +18,12 @@ export const IssueListHeader = observer(({ state }: { state: IIssueState }) => {
|
||||
if (stateGroup === null) return <></>;
|
||||
|
||||
return (
|
||||
<div className="px-6 py-2 flex items-center">
|
||||
<div className="w-4 h-4 flex justify-center items-center">
|
||||
<StateGroupIcon stateGroup={state.group} color={state.color} />
|
||||
<div className="p-3 flex items-center gap-2">
|
||||
<div className="w-3.5 h-3.5 flex justify-center items-center">
|
||||
<StateGroupIcon stateGroup={state.group} color={state.color} height="14" width="14" />
|
||||
</div>
|
||||
<div className="font-semibold capitalize ml-2 mr-3">{state?.name}</div>
|
||||
<div className="text-custom-text-200">{store.issue.getCountOfIssuesByState(state.id)}</div>
|
||||
<div className="font-medium capitalize mr-1">{state?.name}</div>
|
||||
<div className="text-custom-text-200 text-sm font-medium">{store.issue.getCountOfIssuesByState(state.id)}</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -27,9 +27,7 @@ export const IssueListView = observer(() => {
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-6 py-3.5 text-sm text-custom-text-200 bg-custom-background-100">
|
||||
No Issues are available.
|
||||
</div>
|
||||
<div className="p-3 text-sm text-custom-text-400 bg-custom-background-100">No issues.</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
@ -7,7 +7,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { SecondaryButton } from "components/ui";
|
||||
import { Button } from "@plane/ui";
|
||||
// types
|
||||
import { Comment } from "types/issue";
|
||||
// components
|
||||
@ -29,7 +29,6 @@ export const AddComment: React.FC<Props> = observer((props) => {
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
setValue,
|
||||
watch,
|
||||
formState: { isSubmitting },
|
||||
reset,
|
||||
@ -85,27 +84,30 @@ export const AddComment: React.FC<Props> = observer((props) => {
|
||||
? watch("comment_html")
|
||||
: value
|
||||
}
|
||||
customClassName="p-3 min-h-[50px] shadow-sm"
|
||||
customClassName="p-2"
|
||||
editorContentCustomClassNames="min-h-[35px]"
|
||||
debouncedUpdatesEnabled={false}
|
||||
onChange={(comment_json: Object, comment_html: string) => {
|
||||
onChange(comment_html);
|
||||
}}
|
||||
submitButton={
|
||||
<Button
|
||||
disabled={isSubmitting || disabled}
|
||||
variant="primary"
|
||||
type="submit"
|
||||
className="!px-2.5 !py-1.5 !text-xs"
|
||||
onClick={(e) => {
|
||||
userStore.requiredLogin(() => {
|
||||
handleSubmit(onSubmit)(e);
|
||||
});
|
||||
}}
|
||||
>
|
||||
{isSubmitting ? "Adding..." : "Comment"}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<SecondaryButton
|
||||
onClick={(e) => {
|
||||
userStore.requiredLogin(() => {
|
||||
handleSubmit(onSubmit)(e);
|
||||
});
|
||||
}}
|
||||
type="submit"
|
||||
disabled={isSubmitting || disabled}
|
||||
className="mt-2"
|
||||
>
|
||||
{isSubmitting ? "Adding..." : "Comment"}
|
||||
</SecondaryButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -15,6 +15,7 @@ import { timeAgo } from "helpers/date-time.helper";
|
||||
import { Comment } from "types/issue";
|
||||
// services
|
||||
import fileService from "services/file.service";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
|
||||
type Props = {
|
||||
workspaceSlug: string;
|
||||
@ -28,6 +29,8 @@ export const CommentCard: React.FC<Props> = observer((props) => {
|
||||
// states
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
const mentionsConfig = useEditorSuggestions();
|
||||
|
||||
const editorRef = React.useRef<any>(null);
|
||||
|
||||
const showEditorRef = React.useRef<any>(null);
|
||||
@ -135,6 +138,7 @@ export const CommentCard: React.FC<Props> = observer((props) => {
|
||||
ref={showEditorRef}
|
||||
value={comment.comment_html}
|
||||
customClassName="text-xs border border-custom-border-200 bg-custom-background-100"
|
||||
mentionHighlights={mentionsConfig.mentionHighlights}
|
||||
/>
|
||||
<CommentReactions commentId={comment.id} projectId={comment.project} />
|
||||
</div>
|
||||
|
@ -2,27 +2,33 @@ import { IssueReactions } from "components/issues/peek-overview";
|
||||
import { RichReadOnlyEditor } from "@plane/rich-text-editor";
|
||||
// types
|
||||
import { IIssue } from "types/issue";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
|
||||
type Props = {
|
||||
issueDetails: IIssue;
|
||||
};
|
||||
|
||||
export const PeekOverviewIssueDetails: React.FC<Props> = ({ issueDetails }) => (
|
||||
<div className="space-y-2">
|
||||
<h6 className="font-medium text-custom-text-200">
|
||||
{issueDetails.project_detail.identifier}-{issueDetails.sequence_id}
|
||||
</h6>
|
||||
<h4 className="break-words text-2xl font-semibold">{issueDetails.name}</h4>
|
||||
{issueDetails.description_html !== "" && issueDetails.description_html !== "<p></p>" && (
|
||||
<RichReadOnlyEditor
|
||||
value={!issueDetails.description_html ||
|
||||
issueDetails.description_html === "" ||
|
||||
(typeof issueDetails.description_html === "object" &&
|
||||
Object.keys(issueDetails.description_html).length === 0)
|
||||
? "<p></p>"
|
||||
: issueDetails.description_html}
|
||||
customClassName="p-3 min-h-[50px] shadow-sm" />
|
||||
)}
|
||||
<IssueReactions />
|
||||
</div>
|
||||
);
|
||||
export const PeekOverviewIssueDetails: React.FC<Props> = ({ issueDetails }) => {
|
||||
|
||||
const mentionConfig = useEditorSuggestions();
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h6 className="font-medium text-custom-text-200">
|
||||
{issueDetails.project_detail.identifier}-{issueDetails.sequence_id}
|
||||
</h6>
|
||||
<h4 className="break-words text-2xl font-semibold">{issueDetails.name}</h4>
|
||||
{issueDetails.description_html !== "" && issueDetails.description_html !== "<p></p>" && (
|
||||
<RichReadOnlyEditor
|
||||
value={!issueDetails.description_html ||
|
||||
issueDetails.description_html === "" ||
|
||||
(typeof issueDetails.description_html === "object" &&
|
||||
Object.keys(issueDetails.description_html).length === 0)
|
||||
? "<p></p>"
|
||||
: issueDetails.description_html}
|
||||
customClassName="p-3 min-h-[50px] shadow-sm" mentionHighlights={mentionConfig.mentionHighlights} />
|
||||
)}
|
||||
<IssueReactions />
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
13
space/hooks/use-editor-suggestions.tsx
Normal file
13
space/hooks/use-editor-suggestions.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
const useEditorSuggestions = () => {
|
||||
const { mentionsStore }: RootStore = useMobxStore();
|
||||
|
||||
return {
|
||||
// mentionSuggestions: mentionsStore.mentionSuggestions,
|
||||
mentionHighlights: mentionsStore.mentionHighlights,
|
||||
};
|
||||
};
|
||||
|
||||
export default useEditorSuggestions;
|
45
space/store/mentions.store.ts
Normal file
45
space/store/mentions.store.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { IMentionHighlight } from "@plane/lite-text-editor";
|
||||
import { RootStore } from "./root";
|
||||
import { computed, makeObservable } from "mobx";
|
||||
|
||||
export interface IMentionsStore {
|
||||
// mentionSuggestions: IMentionSuggestion[];
|
||||
mentionHighlights: IMentionHighlight[];
|
||||
}
|
||||
|
||||
export class MentionsStore implements IMentionsStore{
|
||||
|
||||
// root store
|
||||
rootStore;
|
||||
|
||||
constructor(_rootStore: RootStore ){
|
||||
|
||||
// rootStore
|
||||
this.rootStore = _rootStore;
|
||||
|
||||
makeObservable(this, {
|
||||
mentionHighlights: computed,
|
||||
// mentionSuggestions: computed
|
||||
})
|
||||
}
|
||||
|
||||
// get mentionSuggestions() {
|
||||
// const projectMembers = this.rootStore.project.project.
|
||||
|
||||
// const suggestions = projectMembers === null ? [] : projectMembers.map((member) => ({
|
||||
// id: member.member.id,
|
||||
// type: "User",
|
||||
// title: member.member.display_name,
|
||||
// subtitle: member.member.email ?? "",
|
||||
// avatar: member.member.avatar,
|
||||
// redirect_uri: `/${member.workspace.slug}/profile/${member.member.id}`,
|
||||
// }))
|
||||
|
||||
// return suggestions
|
||||
// }
|
||||
|
||||
get mentionHighlights() {
|
||||
const user = this.rootStore.user.currentUser;
|
||||
return user ? [user.id] : []
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import UserStore from "./user";
|
||||
import IssueStore, { IIssueStore } from "./issue";
|
||||
import ProjectStore, { IProjectStore } from "./project";
|
||||
import IssueDetailStore, { IIssueDetailStore } from "./issue_details";
|
||||
import { IMentionsStore, MentionsStore } from "./mentions.store";
|
||||
|
||||
enableStaticRendering(typeof window === "undefined");
|
||||
|
||||
@ -13,11 +14,13 @@ export class RootStore {
|
||||
issue: IIssueStore;
|
||||
issueDetails: IIssueDetailStore;
|
||||
project: IProjectStore;
|
||||
mentionsStore: IMentionsStore;
|
||||
|
||||
constructor() {
|
||||
this.user = new UserStore(this);
|
||||
this.issue = new IssueStore(this);
|
||||
this.project = new ProjectStore(this);
|
||||
this.issueDetails = new IssueDetailStore(this);
|
||||
this.mentionsStore = new MentionsStore(this);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
||||
// service
|
||||
import UserService from "services/user.service";
|
||||
import { ActorDetail } from "types/issue";
|
||||
// types
|
||||
import { IUser } from "types/user";
|
||||
|
||||
|
@ -199,9 +199,9 @@
|
||||
--color-sidebar-text-400: var(--color-text-400); /* sidebar placeholder text */
|
||||
|
||||
--color-sidebar-border-100: var(--color-border-100); /* subtle sidebar border= 1 */
|
||||
--color-sidebar-border-200: var(--color-border-100); /* subtle sidebar border- 2 */
|
||||
--color-sidebar-border-300: var(--color-border-100); /* strong sidebar border- 1 */
|
||||
--color-sidebar-border-400: var(--color-border-100); /* strong sidebar border- 2 */
|
||||
--color-sidebar-border-200: var(--color-border-200); /* subtle sidebar border- 2 */
|
||||
--color-sidebar-border-300: var(--color-border-300); /* strong sidebar border- 1 */
|
||||
--color-sidebar-border-400: var(--color-border-400); /* strong sidebar border- 2 */
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,8 @@
|
||||
"SLACK_CLIENT_SECRET",
|
||||
"JITSU_TRACKER_ACCESS_KEY",
|
||||
"JITSU_TRACKER_HOST",
|
||||
"UNSPLASH_ACCESS_KEY"
|
||||
"UNSPLASH_ACCESS_KEY",
|
||||
"NEXT_PUBLIC_SLACK_CLIENT_ID"
|
||||
],
|
||||
"pipeline": {
|
||||
"build": {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
// react beautiful dnd
|
||||
import { Droppable, DroppableProps } from "react-beautiful-dnd";
|
||||
import { Droppable, DroppableProps } from "@hello-pangea/dnd";
|
||||
|
||||
const StrictModeDroppable = ({ children, ...props }: DroppableProps) => {
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { DragDropContext, Draggable, DropResult } from "react-beautiful-dnd";
|
||||
import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
|
||||
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
|
||||
import { MoreVertical } from "lucide-react";
|
||||
// hooks
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { DragDropContext, Draggable, DropResult } from "react-beautiful-dnd";
|
||||
import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
|
||||
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
|
||||
import { MoreVertical } from "lucide-react";
|
||||
// hooks
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { DragDropContext, Draggable, DropResult } from "react-beautiful-dnd";
|
||||
import { DragDropContext, Draggable, DropResult } from "@hello-pangea/dnd";
|
||||
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
|
||||
import { MoreVertical } from "lucide-react";
|
||||
// hooks
|
||||
|
@ -50,9 +50,9 @@ export const AddComment: React.FC<Props> = ({ disabled = false, onSubmit, showAc
|
||||
const editorRef = React.useRef<any>(null);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const editorSuggestions = useEditorSuggestions(workspaceSlug as string | undefined, projectId as string | undefined);
|
||||
const editorSuggestions = useEditorSuggestions();
|
||||
|
||||
const {
|
||||
control,
|
||||
@ -88,7 +88,8 @@ export const AddComment: React.FC<Props> = ({ disabled = false, onSubmit, showAc
|
||||
deleteFile={fileService.deleteImage}
|
||||
ref={editorRef}
|
||||
value={!commentValue || commentValue === "" ? "<p></p>" : commentValue}
|
||||
customClassName="p-3 min-h-[100px] shadow-sm"
|
||||
customClassName="p-2 h-full"
|
||||
editorContentCustomClassNames="min-h-[35px]"
|
||||
debouncedUpdatesEnabled={false}
|
||||
onChange={(comment_json: Object, comment_html: string) => onCommentChange(comment_html)}
|
||||
commentAccessSpecifier={
|
||||
@ -98,15 +99,21 @@ export const AddComment: React.FC<Props> = ({ disabled = false, onSubmit, showAc
|
||||
}
|
||||
mentionSuggestions={editorSuggestions.mentionSuggestions}
|
||||
mentionHighlights={editorSuggestions.mentionHighlights}
|
||||
submitButton={
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
className="!px-2.5 !py-1.5 !text-xs"
|
||||
disabled={isSubmitting || disabled}
|
||||
>
|
||||
{isSubmitting ? "Adding..." : "Comment"}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button variant="neutral-primary" type="submit" disabled={isSubmitting || disabled}>
|
||||
{isSubmitting ? "Adding..." : "Comment"}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -40,7 +40,7 @@ export const CommentCard: React.FC<Props> = ({
|
||||
const editorRef = React.useRef<any>(null);
|
||||
const showEditorRef = React.useRef<any>(null);
|
||||
|
||||
const editorSuggestions = useEditorSuggestions(workspaceSlug, comment.project_detail.id)
|
||||
const editorSuggestions = useEditorSuggestions();
|
||||
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
|
@ -38,7 +38,7 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
|
||||
|
||||
const { setShowAlert } = useReloadConfirmations();
|
||||
|
||||
const editorSuggestion = useEditorSuggestions(workspaceSlug, issue.project_id)
|
||||
const editorSuggestion = useEditorSuggestions();
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
@ -164,8 +164,9 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={`absolute right-5 bottom-5 text-xs text-custom-text-200 border border-custom-border-400 rounded-xl w-[6.5rem] py-1 z-10 flex items-center justify-center ${isSubmitting === "saved" ? "fadeOut" : "fadeIn"
|
||||
}`}
|
||||
className={`absolute right-5 bottom-5 text-xs text-custom-text-200 border border-custom-border-400 rounded-xl w-[6.5rem] py-1 z-10 flex items-center justify-center ${
|
||||
isSubmitting === "saved" ? "fadeOut" : "fadeIn"
|
||||
}`}
|
||||
>
|
||||
{isSubmitting === "submitting" ? "Saving..." : "Saved"}
|
||||
</div>
|
||||
|
@ -119,7 +119,7 @@ export const DraftIssueForm: FC<IssueFormProps> = (props) => {
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const editorSuggestions = useEditorSuggestions(workspaceSlug as string | undefined, projectId);
|
||||
const editorSuggestions = useEditorSuggestions();
|
||||
|
||||
const {
|
||||
formState: { errors, isSubmitting, isDirty },
|
||||
|
@ -112,7 +112,7 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
||||
|
||||
const user = userStore.currentUser;
|
||||
|
||||
const editorSuggestion = useEditorSuggestions(workspaceSlug as string | undefined, projectId);
|
||||
const editorSuggestion = useEditorSuggestions();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
|
@ -1,38 +1,85 @@
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { EmptyState } from "components/common";
|
||||
import { ExistingIssuesListModal } from "components/core";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// assets
|
||||
import emptyIssue from "public/empty-state/issue.svg";
|
||||
import { Button } from "@plane/ui";
|
||||
// types
|
||||
import { ISearchIssueResponse } from "types";
|
||||
|
||||
type Props = {
|
||||
openIssuesListModal: () => void;
|
||||
workspaceSlug: string | undefined;
|
||||
projectId: string | undefined;
|
||||
cycleId: string | undefined;
|
||||
};
|
||||
|
||||
export const CycleEmptyState: React.FC<Props> = ({ openIssuesListModal }) => (
|
||||
<div className="h-full w-full grid place-items-center">
|
||||
<EmptyState
|
||||
title="Cycle issues will appear here"
|
||||
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
|
||||
image={emptyIssue}
|
||||
primaryButton={{
|
||||
text: "New issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "c",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
}}
|
||||
secondaryButton={
|
||||
<Button
|
||||
variant="neutral-primary"
|
||||
prependIcon={<PlusIcon className="h-3 w-3" strokeWidth={2} onClick={openIssuesListModal} />}
|
||||
>
|
||||
Add an existing issue
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
export const CycleEmptyState: React.FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId, cycleId } = props;
|
||||
// states
|
||||
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
|
||||
|
||||
const { cycleIssue: cycleIssueStore } = useMobxStore();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const handleAddIssuesToCycle = async (data: ISearchIssueResponse[]) => {
|
||||
if (!workspaceSlug || !projectId || !cycleId) return;
|
||||
|
||||
const issueIds = data.map((i) => i.id);
|
||||
|
||||
await cycleIssueStore
|
||||
.addIssueToCycle(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), issueIds)
|
||||
.catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Selected issues could not be added to the cycle. Please try again.",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ExistingIssuesListModal
|
||||
isOpen={cycleIssuesListModal}
|
||||
handleClose={() => setCycleIssuesListModal(false)}
|
||||
searchParams={{ cycle: true }}
|
||||
handleOnSubmit={handleAddIssuesToCycle}
|
||||
/>
|
||||
<div className="h-full w-full grid place-items-center">
|
||||
<EmptyState
|
||||
title="Cycle issues will appear here"
|
||||
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
|
||||
image={emptyIssue}
|
||||
primaryButton={{
|
||||
text: "New issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "c",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
}}
|
||||
secondaryButton={
|
||||
<Button
|
||||
variant="neutral-primary"
|
||||
prependIcon={<PlusIcon className="h-3 w-3" strokeWidth={2} />}
|
||||
onClick={() => setCycleIssuesListModal(true)}
|
||||
>
|
||||
Add an existing issue
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -4,36 +4,78 @@ import { EmptyState } from "components/common";
|
||||
import { Button } from "@plane/ui";
|
||||
// assets
|
||||
import emptyIssue from "public/empty-state/issue.svg";
|
||||
import { ExistingIssuesListModal } from "components/core";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { ISearchIssueResponse } from "types";
|
||||
import useToast from "hooks/use-toast";
|
||||
import { useState } from "react";
|
||||
|
||||
type Props = {
|
||||
openIssuesListModal: () => void;
|
||||
workspaceSlug: string | undefined;
|
||||
projectId: string | undefined;
|
||||
moduleId: string | undefined;
|
||||
};
|
||||
|
||||
export const ModuleEmptyState: React.FC<Props> = ({ openIssuesListModal }) => (
|
||||
<div className="h-full w-full grid place-items-center">
|
||||
<EmptyState
|
||||
title="Module issues will appear here"
|
||||
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
|
||||
image={emptyIssue}
|
||||
primaryButton={{
|
||||
text: "New issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "c",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
}}
|
||||
secondaryButton={
|
||||
<Button
|
||||
variant="neutral-primary"
|
||||
prependIcon={<PlusIcon className="h-3 w-3" strokeWidth={2} />}
|
||||
onClick={openIssuesListModal}
|
||||
>
|
||||
Add an existing issue
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
export const ModuleEmptyState: React.FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId, moduleId } = props;
|
||||
// states
|
||||
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
|
||||
|
||||
const { moduleIssue: moduleIssueStore } = useMobxStore();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const handleAddIssuesToModule = async (data: ISearchIssueResponse[]) => {
|
||||
if (!workspaceSlug || !projectId || !moduleId) return;
|
||||
|
||||
const issueIds = data.map((i) => i.id);
|
||||
|
||||
await moduleIssueStore
|
||||
.addIssueToModule(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), issueIds)
|
||||
.catch(() =>
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Selected issues could not be added to the module. Please try again.",
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ExistingIssuesListModal
|
||||
isOpen={moduleIssuesListModal}
|
||||
handleClose={() => setModuleIssuesListModal(false)}
|
||||
searchParams={{ module: true }}
|
||||
handleOnSubmit={handleAddIssuesToModule}
|
||||
/>
|
||||
<div className="h-full w-full grid place-items-center">
|
||||
<EmptyState
|
||||
title="Module issues will appear here"
|
||||
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
|
||||
image={emptyIssue}
|
||||
primaryButton={{
|
||||
text: "New issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
key: "c",
|
||||
});
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
}}
|
||||
secondaryButton={
|
||||
<Button
|
||||
variant="neutral-primary"
|
||||
prependIcon={<PlusIcon className="h-3 w-3" strokeWidth={2} />}
|
||||
onClick={() => setModuleIssuesListModal(true)}
|
||||
>
|
||||
Add an existing issue
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -42,7 +42,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Draggable draggableId={issue.id} index={index} isDragDisabled={isDragDisabled}>
|
||||
<Draggable draggableId={issue.id} index={index}>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
className="group/kanban-block relative p-1.5 hover:cursor-default"
|
||||
@ -61,7 +61,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`text-sm rounded p-2 px-3 shadow-custom-shadow-2xs space-y-[8px] border transition-all bg-custom-background-100 ${
|
||||
className={`text-sm rounded py-2 px-3 shadow-custom-shadow-2xs space-y-2 border-[0.5px] border-custom-border-200 transition-all bg-custom-background-100 ${
|
||||
isDragDisabled ? "" : "hover:cursor-grab"
|
||||
} ${snapshot.isDragging ? `border-custom-primary-100` : `border-transparent`}`}
|
||||
>
|
||||
|
@ -10,11 +10,13 @@ import { KanbanIssueBlocksList, BoardInlineCreateIssueForm } from "components/is
|
||||
import { IIssueDisplayProperties, IIssue } from "types";
|
||||
// constants
|
||||
import { getValueFromObject } from "constants/issue";
|
||||
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
||||
|
||||
export interface IGroupByKanBan {
|
||||
issues: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
order_by: string | null;
|
||||
sub_group_id: string;
|
||||
list: any;
|
||||
listKey: string;
|
||||
@ -31,6 +33,7 @@ export interface IGroupByKanBan {
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
isDragStarted?: boolean;
|
||||
}
|
||||
|
||||
const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
||||
@ -38,6 +41,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
||||
issues,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
order_by,
|
||||
sub_group_id = "null",
|
||||
list,
|
||||
listKey,
|
||||
@ -49,17 +53,20 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
enableQuickIssueCreate,
|
||||
isDragStarted,
|
||||
} = props;
|
||||
|
||||
const verticalAlignPosition = (_list: any) =>
|
||||
kanBanToggle?.groupByHeaderMinMax.includes(getValueFromObject(_list, listKey) as string);
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-full flex">
|
||||
<div className="relative w-full h-full flex gap-3">
|
||||
{list &&
|
||||
list.length > 0 &&
|
||||
list.map((_list: any) => (
|
||||
<div className={`flex-shrink-0 flex flex-col ${!verticalAlignPosition(_list) ? `w-[340px]` : ``}`}>
|
||||
<div
|
||||
className={`relative flex-shrink-0 flex flex-col ${!verticalAlignPosition(_list) ? `w-[340px]` : ``} group`}
|
||||
>
|
||||
{sub_group_by === null && (
|
||||
<div className="flex-shrink-0 w-full bg-custom-background-90 py-1 sticky top-0 z-[2]">
|
||||
<KanBanGroupByHeaderRoot
|
||||
@ -79,7 +86,10 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
||||
verticalAlignPosition(_list) ? `w-[0px] overflow-hidden` : `w-full transition-all`
|
||||
}`}
|
||||
>
|
||||
<Droppable droppableId={`${getValueFromObject(_list, listKey) as string}__${sub_group_id}`}>
|
||||
<Droppable
|
||||
droppableId={`${getValueFromObject(_list, listKey) as string}__${sub_group_id}`}
|
||||
isDropDisabled={isDragDisabled}
|
||||
>
|
||||
{(provided: any, snapshot: any) => (
|
||||
<div
|
||||
className={`w-full h-full relative transition-all ${
|
||||
@ -101,25 +111,39 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
||||
/>
|
||||
) : (
|
||||
isDragDisabled && (
|
||||
<div className="absolute top-0 left-0 w-full h-full flex items-center justify-center">
|
||||
<div className="absolute top-0 left-0 w-full h-full flex items-center justify-center text-sm">
|
||||
{/* <div className="text-custom-text-300 text-sm">Drop here</div> */}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</div>
|
||||
{enableQuickIssueCreate && (
|
||||
<BoardInlineCreateIssueForm
|
||||
groupId={getValueFromObject(_list, listKey) as string}
|
||||
subGroupId={sub_group_id}
|
||||
prePopulatedData={{
|
||||
...(group_by && { [group_by]: getValueFromObject(_list, listKey) }),
|
||||
...(sub_group_by && sub_group_id !== "null" && { [sub_group_by]: sub_group_id }),
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex-shrink-0 w-full bg-custom-background-90 py-1 sticky bottom-0 z-[0]">
|
||||
{enableQuickIssueCreate && (
|
||||
<BoardInlineCreateIssueForm
|
||||
groupId={getValueFromObject(_list, listKey) as string}
|
||||
subGroupId={sub_group_id}
|
||||
prePopulatedData={{
|
||||
...(group_by && { [group_by]: getValueFromObject(_list, listKey) }),
|
||||
...(sub_group_by && sub_group_id !== "null" && { [sub_group_by]: sub_group_id }),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isDragStarted && isDragDisabled && (
|
||||
<div className="invisible group-hover:visible transition-all text-sm absolute top-12 bottom-10 left-0 right-0 bg-custom-background-100/40 text-center">
|
||||
<div className="rounded inline-flex mt-80 h-8 px-3 justify-center items-center bg-custom-background-80 text-custom-text-100 font-medium">
|
||||
{`This board is ordered by "${replaceUnderscoreIfSnakeCase(
|
||||
order_by ? (order_by[0] === "-" ? order_by.slice(1) : order_by) : "created_at"
|
||||
)}"`}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
@ -131,8 +155,8 @@ export interface IKanBan {
|
||||
issues: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
order_by: string | null;
|
||||
sub_group_id?: string;
|
||||
handleDragDrop?: (result: any) => void | undefined;
|
||||
handleIssues: (
|
||||
sub_group_by: string | null,
|
||||
group_by: string | null,
|
||||
@ -151,6 +175,7 @@ export interface IKanBan {
|
||||
members: any;
|
||||
projects: any;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
isDragStarted?: boolean;
|
||||
}
|
||||
|
||||
export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
@ -158,6 +183,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
issues,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
order_by,
|
||||
sub_group_id = "null",
|
||||
handleIssues,
|
||||
quickActions,
|
||||
@ -172,6 +198,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
members,
|
||||
projects,
|
||||
enableQuickIssueCreate,
|
||||
isDragStarted,
|
||||
} = props;
|
||||
|
||||
const { issueKanBanView: issueKanBanViewStore } = useMobxStore();
|
||||
@ -182,6 +209,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={projects}
|
||||
@ -194,6 +222,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -201,6 +230,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={states}
|
||||
@ -213,6 +243,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -220,6 +251,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={stateGroups}
|
||||
@ -232,6 +264,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -239,6 +272,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={priorities}
|
||||
@ -251,6 +285,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -258,6 +293,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={labels ? [...labels, { id: "None", name: "None" }] : labels}
|
||||
@ -270,6 +306,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -277,6 +314,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={members ? [...members, { id: "None", display_name: "None" }] : members}
|
||||
@ -289,6 +327,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -296,6 +335,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={members}
|
||||
@ -308,6 +348,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -17,7 +17,7 @@ export interface IStateGroupHeader {
|
||||
}
|
||||
|
||||
export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) => (
|
||||
<div className="w-[14px] h-[14px] rounded-full">
|
||||
<div className="w-3.5 h-3.5 rounded-full">
|
||||
<StateGroupIcon stateGroup={stateGroup} color={color || null} width="14" height="14" />
|
||||
</div>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from "react";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { DragDropContext } from "@hello-pangea/dnd";
|
||||
@ -8,6 +8,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { KanBanSwimLanes } from "../swimlanes";
|
||||
import { KanBan } from "../default";
|
||||
import { CycleIssueQuickActions } from "components/issues";
|
||||
import { Spinner } from "@plane/ui";
|
||||
// helpers
|
||||
import { orderArrayBy } from "helpers/array.helper";
|
||||
// types
|
||||
@ -37,6 +38,8 @@ export const CycleKanBanLayout: React.FC = observer(() => {
|
||||
|
||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
||||
|
||||
const order_by: string | null = issueFilterStore?.userDisplayFilters?.order_by || null;
|
||||
|
||||
const userDisplayFilters = issueFilterStore?.userDisplayFilters || null;
|
||||
|
||||
const displayProperties = issueFilterStore?.userDisplayProperties || null;
|
||||
@ -45,7 +48,15 @@ export const CycleKanBanLayout: React.FC = observer(() => {
|
||||
? "swimlanes"
|
||||
: "default";
|
||||
|
||||
const [isDragStarted, setIsDragStarted] = useState<boolean>(false);
|
||||
|
||||
const onDragStart = () => {
|
||||
setIsDragStarted(true);
|
||||
};
|
||||
|
||||
const onDragEnd = (result: any) => {
|
||||
setIsDragStarted(false);
|
||||
|
||||
if (!result) return;
|
||||
|
||||
if (
|
||||
@ -99,60 +110,72 @@ export const CycleKanBanLayout: React.FC = observer(() => {
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
{currentKanBanView === "default" ? (
|
||||
<KanBan
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue) => (
|
||||
<CycleIssueQuickActions
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
||||
handleRemoveFromCycle={async () => handleIssues(sub_group_by, group_by, issue, "remove")}
|
||||
<>
|
||||
{cycleIssueStore.loader ? (
|
||||
<div className="w-full h-full flex justify-center items-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
{currentKanBanView === "default" ? (
|
||||
<KanBan
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue) => (
|
||||
<CycleIssueQuickActions
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
||||
handleRemoveFromCycle={async () => handleIssues(sub_group_by, group_by, issue, "remove")}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={cycleIssueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members?.map((m) => m.member) ?? null}
|
||||
projects={projects}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
) : (
|
||||
<KanBanSwimLanes
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue) => (
|
||||
<CycleIssueQuickActions
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
||||
handleRemoveFromCycle={async () => handleIssues(sub_group_by, group_by, issue, "remove")}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={cycleIssueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members?.map((m) => m.member) ?? null}
|
||||
projects={projects}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={cycleIssueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members?.map((m) => m.member) ?? null}
|
||||
projects={projects}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
/>
|
||||
) : (
|
||||
<KanBanSwimLanes
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue) => (
|
||||
<CycleIssueQuickActions
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
||||
handleRemoveFromCycle={async () => handleIssues(sub_group_by, group_by, issue, "remove")}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={cycleIssueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members?.map((m) => m.member) ?? null}
|
||||
projects={projects}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
/>
|
||||
)}
|
||||
</DragDropContext>
|
||||
</div>
|
||||
</DragDropContext>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from "react";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { DragDropContext } from "@hello-pangea/dnd";
|
||||
@ -8,6 +8,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { KanBanSwimLanes } from "../swimlanes";
|
||||
import { KanBan } from "../default";
|
||||
import { ModuleIssueQuickActions } from "components/issues";
|
||||
import { Spinner } from "@plane/ui";
|
||||
// helpers
|
||||
import { orderArrayBy } from "helpers/array.helper";
|
||||
// types
|
||||
@ -36,6 +37,8 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
|
||||
|
||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
||||
|
||||
const order_by: string | null = issueFilterStore?.userDisplayFilters?.order_by || null;
|
||||
|
||||
const userDisplayFilters = issueFilterStore?.userDisplayFilters || null;
|
||||
|
||||
const displayProperties = issueFilterStore?.userDisplayProperties || null;
|
||||
@ -44,7 +47,14 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
|
||||
? "swimlanes"
|
||||
: "default";
|
||||
|
||||
const [isDragStarted, setIsDragStarted] = useState<boolean>(false);
|
||||
|
||||
const onDragStart = () => {
|
||||
setIsDragStarted(true);
|
||||
};
|
||||
|
||||
const onDragEnd = (result: any) => {
|
||||
setIsDragStarted(false);
|
||||
if (!result) return;
|
||||
|
||||
if (
|
||||
@ -98,60 +108,72 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
{currentKanBanView === "default" ? (
|
||||
<KanBan
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue) => (
|
||||
<ModuleIssueQuickActions
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
||||
handleRemoveFromModule={async () => handleIssues(sub_group_by, group_by, issue, "remove")}
|
||||
<>
|
||||
{moduleIssueStore.loader ? (
|
||||
<div className="w-full h-full flex justify-center items-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
{currentKanBanView === "default" ? (
|
||||
<KanBan
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue) => (
|
||||
<ModuleIssueQuickActions
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
||||
handleRemoveFromModule={async () => handleIssues(sub_group_by, group_by, issue, "remove")}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={moduleIssueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members?.map((m) => m.member) ?? null}
|
||||
projects={projects}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
) : (
|
||||
<KanBanSwimLanes
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue) => (
|
||||
<ModuleIssueQuickActions
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
||||
handleRemoveFromModule={async () => handleIssues(sub_group_by, group_by, issue, "remove")}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={moduleIssueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members?.map((m) => m.member) ?? null}
|
||||
projects={projects}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={moduleIssueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members?.map((m) => m.member) ?? null}
|
||||
projects={projects}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
/>
|
||||
) : (
|
||||
<KanBanSwimLanes
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue) => (
|
||||
<ModuleIssueQuickActions
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
||||
handleRemoveFromModule={async () => handleIssues(sub_group_by, group_by, issue, "remove")}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={moduleIssueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members?.map((m) => m.member) ?? null}
|
||||
projects={projects}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
/>
|
||||
)}
|
||||
</DragDropContext>
|
||||
</div>
|
||||
</DragDropContext>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FC, useCallback } from "react";
|
||||
import { FC, useCallback, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { DragDropContext } from "@hello-pangea/dnd";
|
||||
@ -8,6 +8,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { KanBanSwimLanes } from "../swimlanes";
|
||||
import { KanBan } from "../default";
|
||||
import { ProjectIssueQuickActions } from "components/issues";
|
||||
import { Spinner } from "@plane/ui";
|
||||
// constants
|
||||
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
||||
// types
|
||||
@ -34,6 +35,8 @@ export const ProfileIssuesKanBanLayout: FC = observer(() => {
|
||||
|
||||
const group_by: string | null = profileIssueFiltersStore?.userDisplayFilters?.group_by || null;
|
||||
|
||||
const order_by: string | null = profileIssueFiltersStore?.userDisplayFilters?.order_by || null;
|
||||
|
||||
const userDisplayFilters = profileIssueFiltersStore?.userDisplayFilters || null;
|
||||
|
||||
const displayProperties = profileIssueFiltersStore?.userDisplayProperties || null;
|
||||
@ -42,7 +45,14 @@ export const ProfileIssuesKanBanLayout: FC = observer(() => {
|
||||
? "swimlanes"
|
||||
: "default";
|
||||
|
||||
const [isDragStarted, setIsDragStarted] = useState<boolean>(false);
|
||||
|
||||
const onDragStart = () => {
|
||||
setIsDragStarted(true);
|
||||
};
|
||||
|
||||
const onDragEnd = (result: any) => {
|
||||
setIsDragStarted(false);
|
||||
if (!result) return;
|
||||
|
||||
if (
|
||||
@ -83,58 +93,70 @@ export const ProfileIssuesKanBanLayout: FC = observer(() => {
|
||||
const projects = projectStore?.workspaceProjects || null;
|
||||
|
||||
return (
|
||||
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
{currentKanBanView === "default" ? (
|
||||
<KanBan
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue) => (
|
||||
<ProjectIssueQuickActions
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
||||
<>
|
||||
{profileIssuesStore.loader ? (
|
||||
<div className="w-full h-full flex justify-center items-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
{currentKanBanView === "default" ? (
|
||||
<KanBan
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue) => (
|
||||
<ProjectIssueQuickActions
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members?.map((m) => m.member) ?? null}
|
||||
projects={projects}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
) : (
|
||||
<KanBanSwimLanes
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue) => (
|
||||
<ProjectIssueQuickActions
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members?.map((m) => m.member) ?? null}
|
||||
projects={projects}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members?.map((m) => m.member) ?? null}
|
||||
projects={projects}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
/>
|
||||
) : (
|
||||
<KanBanSwimLanes
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue) => (
|
||||
<ProjectIssueQuickActions
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members?.map((m) => m.member) ?? null}
|
||||
projects={projects}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
/>
|
||||
)}
|
||||
</DragDropContext>
|
||||
</div>
|
||||
</DragDropContext>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useCallback } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { DragDropContext } from "@hello-pangea/dnd";
|
||||
import { observer } from "mobx-react-lite";
|
||||
@ -8,6 +8,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { KanBanSwimLanes } from "../swimlanes";
|
||||
import { KanBan } from "../default";
|
||||
import { ProjectIssueQuickActions } from "components/issues";
|
||||
import { Spinner } from "@plane/ui";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
// constants
|
||||
@ -34,6 +35,8 @@ export const KanBanLayout: React.FC = observer(() => {
|
||||
|
||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
||||
|
||||
const order_by: string | null = issueFilterStore?.userDisplayFilters?.order_by || null;
|
||||
|
||||
const userDisplayFilters = issueFilterStore?.userDisplayFilters || null;
|
||||
|
||||
const displayProperties = issueFilterStore?.userDisplayProperties || null;
|
||||
@ -42,12 +45,22 @@ export const KanBanLayout: React.FC = observer(() => {
|
||||
? "swimlanes"
|
||||
: "default";
|
||||
|
||||
const [isDragStarted, setIsDragStarted] = useState<boolean>(false);
|
||||
|
||||
const onDragStart = () => {
|
||||
setIsDragStarted(true);
|
||||
};
|
||||
|
||||
const onDragEnd = (result: any) => {
|
||||
setIsDragStarted(false);
|
||||
|
||||
if (!result) return;
|
||||
|
||||
if (
|
||||
result.destination &&
|
||||
result.source &&
|
||||
result.source.droppableId &&
|
||||
result.destination.droppableId &&
|
||||
result.destination.droppableId === result.source.droppableId &&
|
||||
result.destination.index === result.source.index
|
||||
)
|
||||
@ -87,59 +100,71 @@ export const KanBanLayout: React.FC = observer(() => {
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
{currentKanBanView === "default" ? (
|
||||
<KanBan
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue) => (
|
||||
<ProjectIssueQuickActions
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
||||
<>
|
||||
{issueStore.loader ? (
|
||||
<div className="w-full h-full flex justify-center items-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
|
||||
<DragDropContext onDragStart={onDragStart} onDragEnd={onDragEnd}>
|
||||
{currentKanBanView === "default" ? (
|
||||
<KanBan
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue) => (
|
||||
<ProjectIssueQuickActions
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members?.map((m) => m.member) ?? null}
|
||||
projects={projects}
|
||||
enableQuickIssueCreate
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
) : (
|
||||
<KanBanSwimLanes
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue) => (
|
||||
<ProjectIssueQuickActions
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members?.map((m) => m.member) ?? null}
|
||||
projects={projects}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members?.map((m) => m.member) ?? null}
|
||||
projects={projects}
|
||||
enableQuickIssueCreate
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
/>
|
||||
) : (
|
||||
<KanBanSwimLanes
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue) => (
|
||||
<ProjectIssueQuickActions
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, "delete")}
|
||||
handleUpdate={async (data) => handleIssues(sub_group_by, group_by, data, "update")}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={issueKanBanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members?.map((m) => m.member) ?? null}
|
||||
projects={projects}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
/>
|
||||
)}
|
||||
</DragDropContext>
|
||||
</div>
|
||||
</DragDropContext>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -58,6 +58,7 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
||||
};
|
||||
|
||||
interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
||||
order_by: string | null;
|
||||
showEmptyGroup: boolean;
|
||||
states: IState[] | null;
|
||||
stateGroups: any;
|
||||
@ -76,12 +77,14 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
||||
displayProperties: IIssueDisplayProperties;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
isDragStarted?: boolean;
|
||||
}
|
||||
const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||
const {
|
||||
issues,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
order_by,
|
||||
list,
|
||||
listKey,
|
||||
handleIssues,
|
||||
@ -96,6 +99,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||
labels,
|
||||
members,
|
||||
projects,
|
||||
isDragStarted,
|
||||
} = props;
|
||||
|
||||
const calculateIssueCount = (column_id: string) => {
|
||||
@ -133,6 +137,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||
issues={issues?.[getValueFromObject(_list, listKey) as string]}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_id={getValueFromObject(_list, listKey) as string}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
@ -147,6 +152,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||
members={members}
|
||||
projects={projects}
|
||||
enableQuickIssueCreate
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@ -160,6 +166,7 @@ export interface IKanBanSwimLanes {
|
||||
issues: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
order_by: string | null;
|
||||
handleIssues: (
|
||||
sub_group_by: string | null,
|
||||
group_by: string | null,
|
||||
@ -177,6 +184,7 @@ export interface IKanBanSwimLanes {
|
||||
labels: IIssueLabels[] | null;
|
||||
members: IUserLite[] | null;
|
||||
projects: IProject[] | null;
|
||||
isDragStarted?: boolean;
|
||||
}
|
||||
|
||||
export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
@ -184,6 +192,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
issues,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
order_by,
|
||||
handleIssues,
|
||||
quickActions,
|
||||
displayProperties,
|
||||
@ -196,6 +205,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
labels,
|
||||
members,
|
||||
projects,
|
||||
isDragStarted,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
@ -291,6 +301,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={projects}
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
@ -305,6 +316,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -313,6 +325,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={states}
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
@ -327,6 +340,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -335,6 +349,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={states}
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
@ -349,6 +364,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -357,6 +373,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={stateGroups}
|
||||
listKey={`key`}
|
||||
handleIssues={handleIssues}
|
||||
@ -371,6 +388,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -379,6 +397,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={priorities}
|
||||
listKey={`key`}
|
||||
handleIssues={handleIssues}
|
||||
@ -393,6 +412,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -401,6 +421,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={labels ? [...labels, { id: "None", name: "None" }] : labels}
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
@ -415,6 +436,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -423,6 +445,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={members ? [...members, { id: "None", display_name: "None" }] : members}
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
@ -437,6 +460,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -445,6 +469,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
issues={issues}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={members}
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
@ -459,6 +484,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -4,20 +4,20 @@ import { IssuePeekOverview } from "components/issues/issue-peek-overview";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import { IIssue, IIssueDisplayProperties } from "types";
|
||||
|
||||
interface IssueBlockProps {
|
||||
columnId: string;
|
||||
issue: IIssue;
|
||||
handleIssues: (group_by: string | null, issue: IIssue, action: "update" | "delete") => void;
|
||||
quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||
display_properties: any;
|
||||
displayProperties: IIssueDisplayProperties;
|
||||
isReadonly?: boolean;
|
||||
showEmptyGroup?: boolean;
|
||||
}
|
||||
|
||||
export const IssueBlock: React.FC<IssueBlockProps> = (props) => {
|
||||
const { columnId, issue, handleIssues, quickActions, display_properties, showEmptyGroup, isReadonly } = props;
|
||||
const { columnId, issue, handleIssues, quickActions, displayProperties, showEmptyGroup, isReadonly } = props;
|
||||
|
||||
const updateIssue = (group_by: string | null, issueToUpdate: IIssue) => {
|
||||
handleIssues(group_by, issueToUpdate, "update");
|
||||
@ -26,7 +26,7 @@ export const IssueBlock: React.FC<IssueBlockProps> = (props) => {
|
||||
return (
|
||||
<>
|
||||
<div className="text-sm p-3 relative bg-custom-background-100 flex items-center gap-3">
|
||||
{display_properties && display_properties?.key && (
|
||||
{displayProperties && displayProperties?.key && (
|
||||
<div className="flex-shrink-0 text-xs text-custom-text-300 font-medium">
|
||||
{issue?.project_detail?.identifier}-{issue.sequence_id}
|
||||
</div>
|
||||
@ -54,7 +54,7 @@ export const IssueBlock: React.FC<IssueBlockProps> = (props) => {
|
||||
issue={issue}
|
||||
isReadonly={isReadonly}
|
||||
handleIssues={updateIssue}
|
||||
display_properties={display_properties}
|
||||
displayProperties={displayProperties}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
/>
|
||||
{quickActions(!columnId && columnId === "null" ? null : columnId, issue)}
|
||||
|
@ -2,7 +2,7 @@ import { FC } from "react";
|
||||
// components
|
||||
import { IssueBlock } from "components/issues";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import { IIssue, IIssueDisplayProperties } from "types";
|
||||
|
||||
interface Props {
|
||||
columnId: string;
|
||||
@ -10,12 +10,12 @@ interface Props {
|
||||
isReadonly?: boolean;
|
||||
handleIssues: (group_by: string | null, issue: IIssue, action: "update" | "delete") => void;
|
||||
quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||
display_properties: any;
|
||||
displayProperties: IIssueDisplayProperties;
|
||||
showEmptyGroup?: boolean;
|
||||
}
|
||||
|
||||
export const IssueBlocksList: FC<Props> = (props) => {
|
||||
const { columnId, issues, handleIssues, quickActions, display_properties, showEmptyGroup, isReadonly } = props;
|
||||
const { columnId, issues, handleIssues, quickActions, displayProperties, showEmptyGroup, isReadonly } = props;
|
||||
|
||||
return (
|
||||
<div className="w-full h-full relative divide-y-[0.5px] divide-custom-border-200">
|
||||
@ -28,7 +28,7 @@ export const IssueBlocksList: FC<Props> = (props) => {
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
isReadonly={isReadonly}
|
||||
display_properties={display_properties}
|
||||
displayProperties={displayProperties}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
/>
|
||||
))
|
||||
|
@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
||||
import { ListGroupByHeaderRoot } from "./headers/group-by-root";
|
||||
import { IssueBlocksList, ListInlineCreateIssueForm } from "components/issues";
|
||||
// types
|
||||
import { IEstimatePoint, IIssue, IIssueLabels, IProject, IState, IUserLite } from "types";
|
||||
import { IEstimatePoint, IIssue, IIssueDisplayProperties, IIssueLabels, IProject, IState, IUserLite } from "types";
|
||||
// constants
|
||||
import { getValueFromObject } from "constants/issue";
|
||||
|
||||
@ -16,7 +16,7 @@ export interface IGroupByList {
|
||||
listKey: string;
|
||||
handleIssues: (group_by: string | null, issue: IIssue, action: "update" | "delete") => void;
|
||||
quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||
display_properties: any;
|
||||
displayProperties: IIssueDisplayProperties;
|
||||
is_list?: boolean;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
showEmptyGroup?: boolean;
|
||||
@ -31,7 +31,7 @@ const GroupByList: React.FC<IGroupByList> = observer((props) => {
|
||||
listKey,
|
||||
handleIssues,
|
||||
quickActions,
|
||||
display_properties,
|
||||
displayProperties,
|
||||
is_list = false,
|
||||
enableQuickIssueCreate,
|
||||
showEmptyGroup,
|
||||
@ -59,7 +59,7 @@ const GroupByList: React.FC<IGroupByList> = observer((props) => {
|
||||
issues={is_list ? issues : issues[getValueFromObject(_list, listKey) as string]}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
display_properties={display_properties}
|
||||
displayProperties={displayProperties}
|
||||
isReadonly={isReadonly}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
/>
|
||||
@ -86,7 +86,7 @@ export interface IList {
|
||||
handleDragDrop?: (result: any) => void | undefined;
|
||||
handleIssues: (group_by: string | null, issue: IIssue, action: "update" | "delete") => void;
|
||||
quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||
display_properties: any;
|
||||
displayProperties: IIssueDisplayProperties;
|
||||
states: IState[] | null;
|
||||
labels: IIssueLabels[] | null;
|
||||
members: IUserLite[] | null;
|
||||
@ -105,7 +105,7 @@ export const List: React.FC<IList> = observer((props) => {
|
||||
isReadonly,
|
||||
handleIssues,
|
||||
quickActions,
|
||||
display_properties,
|
||||
displayProperties,
|
||||
states,
|
||||
labels,
|
||||
members,
|
||||
@ -126,7 +126,7 @@ export const List: React.FC<IList> = observer((props) => {
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
display_properties={display_properties}
|
||||
displayProperties={displayProperties}
|
||||
is_list
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isReadonly={isReadonly}
|
||||
@ -142,7 +142,7 @@ export const List: React.FC<IList> = observer((props) => {
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
display_properties={display_properties}
|
||||
displayProperties={displayProperties}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isReadonly={isReadonly}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
@ -157,7 +157,7 @@ export const List: React.FC<IList> = observer((props) => {
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
display_properties={display_properties}
|
||||
displayProperties={displayProperties}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isReadonly={isReadonly}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
@ -172,7 +172,7 @@ export const List: React.FC<IList> = observer((props) => {
|
||||
listKey={`key`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
display_properties={display_properties}
|
||||
displayProperties={displayProperties}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isReadonly={isReadonly}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
@ -187,7 +187,7 @@ export const List: React.FC<IList> = observer((props) => {
|
||||
listKey={`key`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
display_properties={display_properties}
|
||||
displayProperties={displayProperties}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isReadonly={isReadonly}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
@ -202,7 +202,7 @@ export const List: React.FC<IList> = observer((props) => {
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
display_properties={display_properties}
|
||||
displayProperties={displayProperties}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isReadonly={isReadonly}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
@ -217,7 +217,7 @@ export const List: React.FC<IList> = observer((props) => {
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
display_properties={display_properties}
|
||||
displayProperties={displayProperties}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isReadonly={isReadonly}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
@ -232,7 +232,7 @@ export const List: React.FC<IList> = observer((props) => {
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
display_properties={display_properties}
|
||||
displayProperties={displayProperties}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isReadonly={isReadonly}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
|
@ -91,12 +91,12 @@ export const HeaderGroupByCard = observer(({ icon, title, count, issuePayload }:
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`flex-shrink-0 relative flex gap-2 p-1.5 ${
|
||||
verticalAlignPosition ? `flex-col items-center w-[44px]` : `flex-row items-center w-full`
|
||||
className={`flex-shrink-0 relative flex gap-2 py-1.5 ${
|
||||
verticalAlignPosition ? `flex-col items-center w-11` : `flex-row items-center w-full`
|
||||
}`}
|
||||
>
|
||||
<div className="flex-shrink-0 w-[20px] h-[20px] rounded-sm overflow-hidden flex justify-center items-center">
|
||||
{icon ? icon : <CircleDashed width={14} strokeWidth={2} />}
|
||||
<div className="flex-shrink-0 w-5 h-5 rounded-sm overflow-hidden flex justify-center items-center">
|
||||
{icon ? icon : <CircleDashed className="h-3.5 w-3.5" strokeWidth={2} />}
|
||||
</div>
|
||||
|
||||
<div className={`flex items-center gap-1 ${verticalAlignPosition ? `flex-col` : `flex-row w-full`}`}>
|
||||
@ -114,8 +114,8 @@ export const HeaderGroupByCard = observer(({ icon, title, count, issuePayload }:
|
||||
<CustomMenu
|
||||
width="auto"
|
||||
customButton={
|
||||
<span className="flex-shrink-0 w-[20px] h-[20px] rounded-sm overflow-hidden flex justify-center items-center hover:bg-custom-background-80 cursor-pointer transition-all">
|
||||
<Plus width={14} strokeWidth={2} />
|
||||
<span className="flex-shrink-0 w-5 h-5 rounded-sm overflow-hidden flex justify-center items-center hover:bg-custom-background-80 cursor-pointer transition-all">
|
||||
<Plus className="h-3.5 w-3.5" strokeWidth={2} />
|
||||
</span>
|
||||
}
|
||||
>
|
||||
@ -128,7 +128,7 @@ export const HeaderGroupByCard = observer(({ icon, title, count, issuePayload }:
|
||||
</CustomMenu>
|
||||
) : (
|
||||
<div
|
||||
className="flex-shrink-0 w-[20px] h-[20px] rounded-sm overflow-hidden flex justify-center items-center hover:bg-custom-background-80 cursor-pointer transition-all"
|
||||
className="flex-shrink-0 w-5 h-5 rounded-sm overflow-hidden flex justify-center items-center hover:bg-custom-background-80 cursor-pointer transition-all"
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
<Plus width={14} strokeWidth={2} />
|
||||
|
@ -11,19 +11,19 @@ import { IssuePropertyDate } from "../properties/date";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// types
|
||||
import { IIssue, IState, TIssuePriorities } from "types";
|
||||
import { IIssue, IIssueDisplayProperties, IState, TIssuePriorities } from "types";
|
||||
|
||||
export interface IKanBanProperties {
|
||||
columnId: string;
|
||||
issue: IIssue;
|
||||
handleIssues: (group_by: string | null, issue: IIssue) => void;
|
||||
display_properties: any;
|
||||
displayProperties: IIssueDisplayProperties;
|
||||
isReadonly?: boolean;
|
||||
showEmptyGroup?: boolean;
|
||||
}
|
||||
|
||||
export const KanBanProperties: FC<IKanBanProperties> = observer((props) => {
|
||||
const { columnId: group_id, issue, handleIssues, display_properties, isReadonly, showEmptyGroup } = props;
|
||||
const { columnId: group_id, issue, handleIssues, displayProperties, isReadonly, showEmptyGroup } = props;
|
||||
|
||||
const handleState = (state: IState) => {
|
||||
handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, state: state.id });
|
||||
@ -57,7 +57,7 @@ export const KanBanProperties: FC<IKanBanProperties> = observer((props) => {
|
||||
<div className="relative flex gap-2 overflow-x-auto whitespace-nowrap">
|
||||
{/* basic properties */}
|
||||
{/* state */}
|
||||
{display_properties && display_properties?.state && (
|
||||
{displayProperties && displayProperties?.state && (
|
||||
<IssuePropertyState
|
||||
projectId={issue?.project_detail?.id || null}
|
||||
value={issue?.state_detail || null}
|
||||
@ -68,7 +68,7 @@ export const KanBanProperties: FC<IKanBanProperties> = observer((props) => {
|
||||
)}
|
||||
|
||||
{/* priority */}
|
||||
{display_properties && display_properties?.priority && (
|
||||
{displayProperties && displayProperties?.priority && (
|
||||
<IssuePropertyPriority
|
||||
value={issue?.priority || null}
|
||||
onChange={handlePriority}
|
||||
@ -78,7 +78,7 @@ export const KanBanProperties: FC<IKanBanProperties> = observer((props) => {
|
||||
)}
|
||||
|
||||
{/* label */}
|
||||
{display_properties && display_properties?.labels && (showEmptyGroup || issue?.labels.length > 0) && (
|
||||
{displayProperties && displayProperties?.labels && (showEmptyGroup || issue?.labels.length > 0) && (
|
||||
<IssuePropertyLabels
|
||||
projectId={issue?.project_detail?.id || null}
|
||||
value={issue?.labels || null}
|
||||
@ -89,7 +89,7 @@ export const KanBanProperties: FC<IKanBanProperties> = observer((props) => {
|
||||
)}
|
||||
|
||||
{/* assignee */}
|
||||
{display_properties && display_properties?.assignee && (showEmptyGroup || issue?.assignees?.length > 0) && (
|
||||
{displayProperties && displayProperties?.assignee && (showEmptyGroup || issue?.assignees?.length > 0) && (
|
||||
<IssuePropertyAssignee
|
||||
projectId={issue?.project_detail?.id || null}
|
||||
value={issue?.assignees || null}
|
||||
@ -101,7 +101,7 @@ export const KanBanProperties: FC<IKanBanProperties> = observer((props) => {
|
||||
)}
|
||||
|
||||
{/* start date */}
|
||||
{display_properties && display_properties?.start_date && (showEmptyGroup || issue?.start_date) && (
|
||||
{displayProperties && displayProperties?.start_date && (showEmptyGroup || issue?.start_date) && (
|
||||
<IssuePropertyDate
|
||||
value={issue?.start_date || null}
|
||||
onChange={(date: string) => handleStartDate(date)}
|
||||
@ -111,7 +111,7 @@ export const KanBanProperties: FC<IKanBanProperties> = observer((props) => {
|
||||
)}
|
||||
|
||||
{/* target/due date */}
|
||||
{display_properties && display_properties?.due_date && (showEmptyGroup || issue?.target_date) && (
|
||||
{displayProperties && displayProperties?.due_date && (showEmptyGroup || issue?.target_date) && (
|
||||
<IssuePropertyDate
|
||||
value={issue?.target_date || null}
|
||||
onChange={(date: string) => handleTargetDate(date)}
|
||||
@ -121,7 +121,7 @@ export const KanBanProperties: FC<IKanBanProperties> = observer((props) => {
|
||||
)}
|
||||
|
||||
{/* estimates */}
|
||||
{display_properties && display_properties?.estimate && (
|
||||
{displayProperties && displayProperties?.estimate && (
|
||||
<IssuePropertyEstimates
|
||||
projectId={issue?.project_detail?.id || null}
|
||||
value={issue?.estimate_point || null}
|
||||
@ -133,7 +133,7 @@ export const KanBanProperties: FC<IKanBanProperties> = observer((props) => {
|
||||
|
||||
{/* extra render properties */}
|
||||
{/* sub-issues */}
|
||||
{display_properties && display_properties?.sub_issue_count && (
|
||||
{displayProperties && displayProperties?.sub_issue_count && (
|
||||
<Tooltip tooltipHeading="Sub-issues" tooltipContent={`${issue.sub_issues_count}`}>
|
||||
<div className="flex-shrink-0 border-[0.5px] border-custom-border-300 overflow-hidden rounded flex justify-center items-center gap-2 px-2.5 py-1 h-5">
|
||||
<Layers className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
|
||||
@ -143,7 +143,7 @@ export const KanBanProperties: FC<IKanBanProperties> = observer((props) => {
|
||||
)}
|
||||
|
||||
{/* attachments */}
|
||||
{display_properties && display_properties?.attachment_count && (
|
||||
{displayProperties && displayProperties?.attachment_count && (
|
||||
<Tooltip tooltipHeading="Attachments" tooltipContent={`${issue.attachment_count}`}>
|
||||
<div className="flex-shrink-0 border-[0.5px] border-custom-border-300 overflow-hidden rounded flex justify-center items-center gap-2 px-2.5 py-1 h-5">
|
||||
<Paperclip className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
|
||||
@ -153,7 +153,7 @@ export const KanBanProperties: FC<IKanBanProperties> = observer((props) => {
|
||||
)}
|
||||
|
||||
{/* link */}
|
||||
{display_properties && display_properties?.link && (
|
||||
{displayProperties && displayProperties?.link && (
|
||||
<Tooltip tooltipHeading="Links" tooltipContent={`${issue.link_count}`}>
|
||||
<div className="flex-shrink-0 border-[0.5px] border-custom-border-300 overflow-hidden rounded flex justify-center items-center gap-2 px-2.5 py-1 h-5">
|
||||
<Link className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
|
||||
|
@ -25,7 +25,7 @@ export const ArchivedIssueListLayout: FC = observer(() => {
|
||||
|
||||
// derived values
|
||||
const issues = archivedIssueStore.getIssues;
|
||||
const display_properties = archivedIssueFiltersStore?.userDisplayProperties || null;
|
||||
const displayProperties = archivedIssueFiltersStore?.userDisplayProperties || null;
|
||||
const group_by: string | null = archivedIssueFiltersStore?.userDisplayFilters?.group_by || null;
|
||||
|
||||
const handleIssues = (group_by: string | null, issue: IIssue, action: "delete" | "update") => {
|
||||
@ -59,7 +59,7 @@ export const ArchivedIssueListLayout: FC = observer(() => {
|
||||
quickActions={(group_by, issue) => (
|
||||
<ArchivedIssueQuickActions issue={issue} handleDelete={async () => handleIssues(group_by, issue, "delete")} />
|
||||
)}
|
||||
display_properties={display_properties}
|
||||
displayProperties={displayProperties}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
|
@ -31,7 +31,7 @@ export const CycleListLayout: React.FC = observer(() => {
|
||||
|
||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
||||
|
||||
const display_properties = issueFilterStore?.userDisplayProperties || null;
|
||||
const displayProperties = issueFilterStore?.userDisplayProperties || null;
|
||||
|
||||
const handleIssues = useCallback(
|
||||
(group_by: string | null, issue: IIssue, action: "update" | "delete" | "remove") => {
|
||||
@ -80,7 +80,7 @@ export const CycleListLayout: React.FC = observer(() => {
|
||||
handleRemoveFromCycle={async () => handleIssues(group_by, issue, "remove")}
|
||||
/>
|
||||
)}
|
||||
display_properties={display_properties}
|
||||
displayProperties={displayProperties}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
|
@ -31,7 +31,7 @@ export const ModuleListLayout: React.FC = observer(() => {
|
||||
|
||||
const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null;
|
||||
|
||||
const display_properties = issueFilterStore?.userDisplayProperties || null;
|
||||
const displayProperties = issueFilterStore?.userDisplayProperties || null;
|
||||
|
||||
const handleIssues = useCallback(
|
||||
(group_by: string | null, issue: IIssue, action: "update" | "delete" | "remove") => {
|
||||
@ -80,7 +80,7 @@ export const ModuleListLayout: React.FC = observer(() => {
|
||||
handleRemoveFromModule={async () => handleIssues(group_by, issue, "remove")}
|
||||
/>
|
||||
)}
|
||||
display_properties={display_properties}
|
||||
displayProperties={displayProperties}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
|
@ -29,7 +29,7 @@ export const ProfileIssuesListLayout: FC = observer(() => {
|
||||
|
||||
const group_by: string | null = profileIssueFiltersStore?.userDisplayFilters?.group_by || null;
|
||||
|
||||
const display_properties = profileIssueFiltersStore?.userDisplayProperties || null;
|
||||
const displayProperties = profileIssueFiltersStore?.userDisplayProperties || null;
|
||||
|
||||
const handleIssues = useCallback(
|
||||
(group_by: string | null, issue: IIssue, action: "update" | "delete") => {
|
||||
@ -64,7 +64,7 @@ export const ProfileIssuesListLayout: FC = observer(() => {
|
||||
handleUpdate={async (data) => handleIssues(group_by, data, "update")}
|
||||
/>
|
||||
)}
|
||||
display_properties={display_properties}
|
||||
displayProperties={displayProperties}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
|
@ -6,6 +6,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { List } from "../default";
|
||||
import { ProjectIssueQuickActions } from "components/issues";
|
||||
import { Spinner } from "@plane/ui";
|
||||
// helpers
|
||||
import { orderArrayBy } from "helpers/array.helper";
|
||||
// types
|
||||
@ -29,7 +30,7 @@ export const ListLayout: FC = observer(() => {
|
||||
|
||||
const userDisplayFilters = issueFilterStore?.userDisplayFilters || null;
|
||||
const group_by: string | null = userDisplayFilters?.group_by || null;
|
||||
const display_properties = issueFilterStore?.userDisplayProperties || null;
|
||||
const displayProperties = issueFilterStore?.userDisplayProperties || null;
|
||||
|
||||
const handleIssues = useCallback(
|
||||
(group_by: string | null, issue: IIssue, action: "update" | "delete") => {
|
||||
@ -56,29 +57,37 @@ export const ListLayout: FC = observer(() => {
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-full bg-custom-background-90">
|
||||
<List
|
||||
issues={issues}
|
||||
group_by={group_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(group_by, issue) => (
|
||||
<ProjectIssueQuickActions
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(group_by, issue, "delete")}
|
||||
handleUpdate={async (data) => handleIssues(group_by, data, "update")}
|
||||
<>
|
||||
{issueStore.loader ? (
|
||||
<div className="w-full h-full flex justify-center items-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative w-full h-full bg-custom-background-90">
|
||||
<List
|
||||
issues={issues}
|
||||
group_by={group_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(group_by, issue) => (
|
||||
<ProjectIssueQuickActions
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(group_by, issue, "delete")}
|
||||
handleUpdate={async (data) => handleIssues(group_by, data, "update")}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members?.map((m) => m.member) ?? null}
|
||||
projects={projects}
|
||||
enableQuickIssueCreate
|
||||
estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null}
|
||||
showEmptyGroup={userDisplayFilters.show_empty_groups}
|
||||
/>
|
||||
)}
|
||||
display_properties={display_properties}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members?.map((m) => m.member) ?? null}
|
||||
projects={projects}
|
||||
enableQuickIssueCreate
|
||||
estimates={estimates?.points ? orderArrayBy(estimates.points, "key") : null}
|
||||
showEmptyGroup={userDisplayFilters.show_empty_groups}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -1,16 +1,13 @@
|
||||
import { Fragment, useState } from "react";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// hooks
|
||||
import { usePopper } from "react-popper";
|
||||
import useEstimateOption from "hooks/use-estimate-option";
|
||||
// ui
|
||||
import { Check, ChevronDown, Search, Triangle } from "lucide-react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Combobox } from "@headlessui/react";
|
||||
import { Check, ChevronDown, Search, Triangle } from "lucide-react";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// types
|
||||
import { Placement } from "@popperjs/core";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
export interface IIssuePropertyEstimates {
|
||||
view?: "profile" | "workspace" | "project";
|
||||
@ -27,7 +24,6 @@ export interface IIssuePropertyEstimates {
|
||||
|
||||
export const IssuePropertyEstimates: React.FC<IIssuePropertyEstimates> = observer((props) => {
|
||||
const {
|
||||
view,
|
||||
projectId,
|
||||
value,
|
||||
onChange,
|
||||
@ -44,8 +40,6 @@ export const IssuePropertyEstimates: React.FC<IIssuePropertyEstimates> = observe
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const { isEstimateActive, estimatePoints } = useEstimateOption();
|
||||
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: placement ?? "bottom-start",
|
||||
modifiers: [
|
||||
@ -58,6 +52,14 @@ export const IssuePropertyEstimates: React.FC<IIssuePropertyEstimates> = observe
|
||||
],
|
||||
});
|
||||
|
||||
const { project: projectStore } = useMobxStore();
|
||||
|
||||
const projectDetails = projectId ? projectStore.project_details[projectId] : null;
|
||||
const isEstimateEnabled = projectDetails?.estimate !== null;
|
||||
const estimates = projectId ? projectStore.estimates?.[projectId] : null;
|
||||
const estimatePoints =
|
||||
projectDetails && isEstimateEnabled ? estimates?.find((e) => e.id === projectDetails.estimate)?.points : null;
|
||||
|
||||
const options: { value: number | null; query: string; content: any }[] | undefined = (estimatePoints ?? []).map(
|
||||
(estimate) => ({
|
||||
value: estimate.key,
|
||||
@ -94,6 +96,8 @@ export const IssuePropertyEstimates: React.FC<IIssuePropertyEstimates> = observe
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
if (!isEstimateEnabled) return null;
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
as="div"
|
||||
|
@ -20,11 +20,7 @@ import { Spinner } from "@plane/ui";
|
||||
// helpers
|
||||
import { getDateRangeStatus } from "helpers/date-time.helper";
|
||||
|
||||
type Props = {
|
||||
openIssuesListModal: () => void;
|
||||
};
|
||||
|
||||
export const CycleLayoutRoot: React.FC<Props> = observer(({ openIssuesListModal }) => {
|
||||
export const CycleLayoutRoot: React.FC = observer(() => {
|
||||
const [transferIssuesModal, setTransferIssuesModal] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
@ -73,7 +69,11 @@ export const CycleLayoutRoot: React.FC<Props> = observer(({ openIssuesListModal
|
||||
{cycleStatus === "completed" && <TransferIssues handleClick={() => setTransferIssuesModal(true)} />}
|
||||
<CycleAppliedFiltersRoot />
|
||||
{(activeLayout === "list" || activeLayout === "spreadsheet") && issueCount === 0 ? (
|
||||
<CycleEmptyState openIssuesListModal={openIssuesListModal} />
|
||||
<CycleEmptyState
|
||||
workspaceSlug={workspaceSlug?.toString()}
|
||||
projectId={projectId?.toString()}
|
||||
cycleId={cycleId?.toString()}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full overflow-auto">
|
||||
{activeLayout === "list" ? (
|
||||
|
@ -18,11 +18,7 @@ import {
|
||||
// ui
|
||||
import { Spinner } from "@plane/ui";
|
||||
|
||||
type Props = {
|
||||
openIssuesListModal: () => void;
|
||||
};
|
||||
|
||||
export const ModuleLayoutRoot: React.FC<Props> = observer(({ openIssuesListModal }) => {
|
||||
export const ModuleLayoutRoot: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, moduleId } = router.query as {
|
||||
workspaceSlug: string;
|
||||
@ -66,7 +62,11 @@ export const ModuleLayoutRoot: React.FC<Props> = observer(({ openIssuesListModal
|
||||
<div className="relative w-full h-full flex flex-col overflow-hidden">
|
||||
<ModuleAppliedFiltersRoot />
|
||||
{(activeLayout === "list" || activeLayout === "spreadsheet") && issueCount === 0 ? (
|
||||
<ModuleEmptyState openIssuesListModal={openIssuesListModal} />
|
||||
<ModuleEmptyState
|
||||
workspaceSlug={workspaceSlug?.toString()}
|
||||
projectId={projectId?.toString()}
|
||||
moduleId={moduleId?.toString()}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-full w-full overflow-auto">
|
||||
{activeLayout === "list" ? (
|
||||
|
@ -22,13 +22,19 @@ export const ProjectLayoutRoot: React.FC = observer(() => {
|
||||
|
||||
const { issue: issueStore, issueFilter: issueFilterStore } = useMobxStore();
|
||||
|
||||
useSWR(workspaceSlug && projectId ? `PROJECT_FILTERS_AND_ISSUES_${projectId.toString()}` : null, async () => {
|
||||
if (workspaceSlug && projectId) {
|
||||
await issueFilterStore.fetchUserProjectFilters(workspaceSlug.toString(), projectId.toString());
|
||||
|
||||
await issueStore.fetchIssues(workspaceSlug.toString(), projectId.toString());
|
||||
const { isLoading } = useSWR(
|
||||
workspaceSlug && projectId ? `PROJECT_FILTERS_AND_ISSUES_${projectId.toString()}` : null,
|
||||
async () => {
|
||||
if (workspaceSlug && projectId) {
|
||||
await issueFilterStore.fetchUserProjectFilters(workspaceSlug.toString(), projectId.toString());
|
||||
await issueStore.fetchIssues(workspaceSlug.toString(), projectId.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
console.log("--");
|
||||
console.log("isLoading -- -->", isLoading);
|
||||
console.log("--");
|
||||
|
||||
const activeLayout = issueFilterStore.userDisplayFilters.layout;
|
||||
|
||||
|
@ -49,7 +49,7 @@ export const IssueCommentCard: React.FC<IIssueCommentCard> = (props) => {
|
||||
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
const editorSuggestions = useEditorSuggestions(workspaceSlug, projectId);
|
||||
const editorSuggestions = useEditorSuggestions();
|
||||
|
||||
const {
|
||||
formState: { isSubmitting },
|
||||
|
@ -51,9 +51,9 @@ export const IssueCommentEditor: React.FC<IIssueCommentEditor> = (props) => {
|
||||
const editorRef = React.useRef<any>(null);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const editorSuggestions = useEditorSuggestions(workspaceSlug as string | undefined, projectId as string | undefined);
|
||||
const editorSuggestions = useEditorSuggestions();
|
||||
|
||||
const {
|
||||
control,
|
||||
@ -90,6 +90,7 @@ export const IssueCommentEditor: React.FC<IIssueCommentEditor> = (props) => {
|
||||
ref={editorRef}
|
||||
value={!commentValue || commentValue === "" ? "<p></p>" : commentValue}
|
||||
customClassName="p-2 h-full"
|
||||
editorContentCustomClassNames="min-h-[35px]"
|
||||
debouncedUpdatesEnabled={false}
|
||||
mentionSuggestions={editorSuggestions.mentionSuggestions}
|
||||
mentionHighlights={editorSuggestions.mentionHighlights}
|
||||
|
@ -32,13 +32,13 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
|
||||
// store
|
||||
const { user: userStore } = useMobxStore();
|
||||
const { currentProjectRole } = userStore;
|
||||
const isAllowed = [5, 10].includes(currentProjectRole || 0);
|
||||
const isAllowed = [15, 20].includes(currentProjectRole || 0);
|
||||
// states
|
||||
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
|
||||
const [characterLimit, setCharacterLimit] = useState(false);
|
||||
// hooks
|
||||
const { setShowAlert } = useReloadConfirmations();
|
||||
const editorSuggestions = useEditorSuggestions(workspaceSlug, issue.project_detail.id);
|
||||
const editorSuggestions = useEditorSuggestions();
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
|
@ -74,13 +74,13 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
||||
};
|
||||
const addIssueToCycle = async (cycleId: string) => {
|
||||
if (!workspaceSlug || !issue || !cycleId) return;
|
||||
cycleIssueStore.addIssueToCycle(workspaceSlug.toString(), issue.project_detail.id, cycleId, issue.id);
|
||||
cycleIssueStore.addIssueToCycle(workspaceSlug.toString(), issue.project_detail.id, cycleId, [issue.id]);
|
||||
};
|
||||
|
||||
const addIssueToModule = async (moduleId: string) => {
|
||||
if (!workspaceSlug || !issue || !moduleId) return;
|
||||
|
||||
moduleIssueStore.addIssueToModule(workspaceSlug.toString(), issue.project_detail.id, moduleId, issue.id);
|
||||
moduleIssueStore.addIssueToModule(workspaceSlug.toString(), issue.project_detail.id, moduleId, [issue.id]);
|
||||
};
|
||||
const handleLabels = (formData: Partial<IIssue>) => {
|
||||
issueUpdate({ ...issue, ...formData });
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FC, ReactNode } from "react";
|
||||
import { FC, Fragment, ReactNode } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR from "swr";
|
||||
import { observer } from "mobx-react-lite";
|
||||
@ -112,6 +112,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
||||
else await issueStore.deleteIssue(workspaceSlug, projectId, issue!);
|
||||
const { query } = router;
|
||||
if (query.peekIssueId) {
|
||||
issueDetailStore.setPeekId(null);
|
||||
delete query.peekIssueId;
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
@ -123,30 +124,32 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
||||
const userRole = userStore.currentProjectRole ?? 5;
|
||||
|
||||
return (
|
||||
<IssueView
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
issue={issue}
|
||||
isLoading={isLoading}
|
||||
isArchived={isArchived}
|
||||
handleCopyText={handleCopyText}
|
||||
redirectToIssueDetail={redirectToIssueDetail}
|
||||
issueUpdate={issueUpdate}
|
||||
issueReactionCreate={issueReactionCreate}
|
||||
issueReactionRemove={issueReactionRemove}
|
||||
issueCommentCreate={issueCommentCreate}
|
||||
issueCommentUpdate={issueCommentUpdate}
|
||||
issueCommentRemove={issueCommentRemove}
|
||||
issueCommentReactionCreate={issueCommentReactionCreate}
|
||||
issueCommentReactionRemove={issueCommentReactionRemove}
|
||||
issueSubscriptionCreate={issueSubscriptionCreate}
|
||||
issueSubscriptionRemove={issueSubscriptionRemove}
|
||||
handleDeleteIssue={handleDeleteIssue}
|
||||
disableUserActions={[5, 10].includes(userRole)}
|
||||
showCommentAccessSpecifier={projectStore.currentProjectDetails?.is_deployed}
|
||||
>
|
||||
{children}
|
||||
</IssueView>
|
||||
<Fragment>
|
||||
<IssueView
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
issue={issue}
|
||||
isLoading={isLoading}
|
||||
isArchived={isArchived}
|
||||
handleCopyText={handleCopyText}
|
||||
redirectToIssueDetail={redirectToIssueDetail}
|
||||
issueUpdate={issueUpdate}
|
||||
issueReactionCreate={issueReactionCreate}
|
||||
issueReactionRemove={issueReactionRemove}
|
||||
issueCommentCreate={issueCommentCreate}
|
||||
issueCommentUpdate={issueCommentUpdate}
|
||||
issueCommentRemove={issueCommentRemove}
|
||||
issueCommentReactionCreate={issueCommentReactionCreate}
|
||||
issueCommentReactionRemove={issueCommentReactionRemove}
|
||||
issueSubscriptionCreate={issueSubscriptionCreate}
|
||||
issueSubscriptionRemove={issueSubscriptionRemove}
|
||||
handleDeleteIssue={handleDeleteIssue}
|
||||
disableUserActions={[5, 10].includes(userRole)}
|
||||
showCommentAccessSpecifier={projectStore.currentProjectDetails?.is_deployed}
|
||||
>
|
||||
{children}
|
||||
</IssueView>
|
||||
</Fragment>
|
||||
);
|
||||
});
|
||||
|
@ -97,6 +97,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||
|
||||
const updateRoutePeekId = () => {
|
||||
if (issueId != peekIssueId) {
|
||||
issueDetailStore.setPeekId(issueId);
|
||||
const { query } = router;
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
@ -107,6 +108,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||
const removeRoutePeekId = () => {
|
||||
const { query } = router;
|
||||
if (query.peekIssueId) {
|
||||
issueDetailStore.setPeekId(null);
|
||||
delete query.peekIssueId;
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
@ -248,7 +250,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||
issue && (
|
||||
<>
|
||||
{["side-peek", "modal"].includes(peekMode) ? (
|
||||
<div className="flex flex-col gap-3 px-10 py-6">
|
||||
<div className="flex flex-col gap-3 py-6 px-8">
|
||||
<PeekOverviewIssueDetails
|
||||
workspaceSlug={workspaceSlug}
|
||||
issue={issue}
|
||||
@ -292,8 +294,6 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||
issueReactionRemove={issueReactionRemove}
|
||||
/>
|
||||
|
||||
<div className="border-t border-custom-border-400" />
|
||||
|
||||
<IssueComment
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
|
@ -168,13 +168,13 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
||||
const addIssueToCycle = async (issueId: string, cycleId: string) => {
|
||||
if (!workspaceSlug || !activeProject) return;
|
||||
|
||||
cycleIssueStore.addIssueToCycle(workspaceSlug.toString(), activeProject, cycleId, issueId);
|
||||
cycleIssueStore.addIssueToCycle(workspaceSlug.toString(), activeProject, cycleId, [issueId]);
|
||||
};
|
||||
|
||||
const addIssueToModule = async (issueId: string, moduleId: string) => {
|
||||
if (!workspaceSlug || !activeProject) return;
|
||||
|
||||
moduleIssueStore.addIssueToModule(workspaceSlug.toString(), activeProject, moduleId, issueId);
|
||||
moduleIssueStore.addIssueToModule(workspaceSlug.toString(), activeProject, moduleId, [issueId]);
|
||||
};
|
||||
|
||||
const createIssue = async (payload: Partial<IIssue>) => {
|
||||
@ -320,7 +320,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
||||
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-3xl">
|
||||
<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 mx-4 sm:max-w-4xl">
|
||||
<IssueForm
|
||||
handleFormSubmit={handleFormSubmit}
|
||||
initialData={data ?? prePopulateData}
|
||||
|
@ -16,7 +16,7 @@ import { getNumberCount } from "helpers/string.helper";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
export const NotificationPopover = observer(() => {
|
||||
const store: any = useMobxStore();
|
||||
const { theme: themeStore } = useMobxStore();
|
||||
|
||||
const {
|
||||
notifications,
|
||||
@ -45,6 +45,8 @@ export const NotificationPopover = observer(() => {
|
||||
markAllNotificationsAsRead,
|
||||
} = useUserNotification();
|
||||
|
||||
const isSidebarCollapsed = themeStore.sidebarCollapsed;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SnoozeNotificationModal
|
||||
@ -62,23 +64,18 @@ export const NotificationPopover = observer(() => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip
|
||||
tooltipContent="Notifications"
|
||||
position="right"
|
||||
className="ml-2"
|
||||
disabled={!store?.theme?.sidebarCollapsed}
|
||||
>
|
||||
<Tooltip tooltipContent="Notifications" position="right" className="ml-2" disabled={!isSidebarCollapsed}>
|
||||
<Popover.Button
|
||||
className={`relative group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${
|
||||
isActive
|
||||
? "bg-custom-primary-100/10 text-custom-primary-100"
|
||||
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80"
|
||||
} ${store?.theme?.sidebarCollapsed ? "justify-center" : ""}`}
|
||||
} ${isSidebarCollapsed ? "justify-center" : ""}`}
|
||||
>
|
||||
<Bell className="h-4 w-4" />
|
||||
{store?.theme?.sidebarCollapsed ? null : <span>Notifications</span>}
|
||||
{isSidebarCollapsed ? null : <span>Notifications</span>}
|
||||
{totalNotificationCount && totalNotificationCount > 0 ? (
|
||||
store?.theme?.sidebarCollapsed ? (
|
||||
isSidebarCollapsed ? (
|
||||
<span className="absolute right-3.5 top-2 h-2 w-2 bg-custom-primary-300 rounded-full" />
|
||||
) : (
|
||||
<span className="ml-auto bg-custom-primary-300 rounded-full text-xs text-white px-1.5">
|
||||
|
@ -1,16 +1,15 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import Image from "next/image";
|
||||
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { TourSidebar } from "components/onboarding";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// icons
|
||||
import { X } from "lucide-react";
|
||||
// images
|
||||
// assets
|
||||
import PlaneWhiteLogo from "public/plane-logos/white-horizontal.svg";
|
||||
import IssuesTour from "public/onboarding/issues.webp";
|
||||
import CyclesTour from "public/onboarding/cycles.webp";
|
||||
@ -75,10 +74,13 @@ const TOUR_STEPS: {
|
||||
},
|
||||
];
|
||||
|
||||
export const TourRoot: React.FC<Props> = ({ onComplete }) => {
|
||||
export const TourRoot: React.FC<Props> = observer((props) => {
|
||||
const { onComplete } = props;
|
||||
// states
|
||||
const [step, setStep] = useState<TTourSteps>("welcome");
|
||||
|
||||
const { user } = useUser();
|
||||
const { user: userStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const user = userStore.currentUser;
|
||||
|
||||
const currentStepIndex = TOUR_STEPS.findIndex((tourStep) => tourStep.key === step);
|
||||
const currentStep = TOUR_STEPS[currentStepIndex];
|
||||
@ -91,7 +93,7 @@ export const TourRoot: React.FC<Props> = ({ onComplete }) => {
|
||||
<div className="h-3/5 bg-custom-primary-100 grid place-items-center">
|
||||
<Image src={PlaneWhiteLogo} alt="Plane White Logo" />
|
||||
</div>
|
||||
<div className="h-2/5 overflow-y-auto p-6">
|
||||
<div className="h-2/5 flex flex-col overflow-y-auto p-6">
|
||||
<h3 className="font-semibold sm:text-xl">
|
||||
Welcome to Plane, {user?.first_name} {user?.last_name}
|
||||
</h3>
|
||||
@ -99,17 +101,19 @@ export const TourRoot: React.FC<Props> = ({ onComplete }) => {
|
||||
We{"'"}re glad that you decided to try out Plane. You can now manage your projects with ease. Get
|
||||
started by creating a project.
|
||||
</p>
|
||||
<div className="flex items-center gap-6 mt-8">
|
||||
<Button variant="primary" onClick={() => setStep("issues")}>
|
||||
Take a Product Tour
|
||||
</Button>
|
||||
<button
|
||||
type="button"
|
||||
className="outline-custom-text-100 bg-transparent text-custom-primary-100 text-xs font-medium"
|
||||
onClick={onComplete}
|
||||
>
|
||||
No thanks, I will explore it myself
|
||||
</button>
|
||||
<div className="flex items-end h-full">
|
||||
<div className="flex items-center gap-6 mt-8">
|
||||
<Button variant="primary" onClick={() => setStep("issues")}>
|
||||
Take a Product Tour
|
||||
</Button>
|
||||
<button
|
||||
type="button"
|
||||
className="outline-custom-text-100 bg-transparent text-custom-primary-100 text-xs font-medium"
|
||||
onClick={onComplete}
|
||||
>
|
||||
No thanks, I will explore it myself
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -148,8 +152,14 @@ export const TourRoot: React.FC<Props> = ({ onComplete }) => {
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{TOUR_STEPS.findIndex((tourStep) => tourStep.key === step) === TOUR_STEPS.length - 1 && (
|
||||
<Button variant="primary" onClick={onComplete}>
|
||||
{currentStepIndex === TOUR_STEPS.length - 1 && (
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
onComplete();
|
||||
commandPaletteStore.toggleCreateProjectModal(true);
|
||||
}}
|
||||
>
|
||||
Create my first project
|
||||
</Button>
|
||||
)}
|
||||
@ -160,4 +170,4 @@ export const TourRoot: React.FC<Props> = ({ onComplete }) => {
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1 +1,2 @@
|
||||
export * from "./signin";
|
||||
export * from "./workspace-dashboard";
|
||||
|
@ -56,7 +56,7 @@ export const CreateUpdateBlockInline: FC<Props> = ({
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, pageId } = router.query;
|
||||
|
||||
const editorSuggestion = useEditorSuggestions(workspaceSlug as string | undefined, projectId as string | undefined)
|
||||
const editorSuggestion = useEditorSuggestions();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
||||
import Link from "next/link";
|
||||
import { mutate } from "swr";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Draggable } from "react-beautiful-dnd";
|
||||
import { Draggable } from "@hello-pangea/dnd";
|
||||
// services
|
||||
import { PageService } from "services/page.service";
|
||||
import { IssueService } from "services/issue/issue.service";
|
||||
@ -64,7 +64,7 @@ export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails, showBl
|
||||
},
|
||||
});
|
||||
|
||||
const editorSuggestion = useEditorSuggestions(workspaceSlug as string | undefined, projectId as string | undefined)
|
||||
const editorSuggestion = useEditorSuggestions();
|
||||
|
||||
const updatePageBlock = async (formData: Partial<IPageBlock>) => {
|
||||
if (!workspaceSlug || !projectId || !pageId) return;
|
||||
|
@ -211,7 +211,7 @@ export const ProjectCard: React.FC<ProjectCardProps> = observer((props) => {
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
variant="link-primary"
|
||||
className="!p-0"
|
||||
className="!p-0 font-semibold"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
@ -261,6 +261,7 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
||||
name="name"
|
||||
type="text"
|
||||
value={value}
|
||||
tabIndex={1}
|
||||
onChange={handleNameChange(onChange)}
|
||||
hasError={Boolean(errors.name)}
|
||||
placeholder="Project Title"
|
||||
@ -293,6 +294,7 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
||||
name="identifier"
|
||||
type="text"
|
||||
value={value}
|
||||
tabIndex={2}
|
||||
onChange={handleIdentifierChange(onChange)}
|
||||
hasError={Boolean(errors.identifier)}
|
||||
placeholder="Identifier"
|
||||
@ -311,6 +313,7 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
||||
id="description"
|
||||
name="description"
|
||||
value={value}
|
||||
tabIndex={3}
|
||||
placeholder="Description..."
|
||||
onChange={onChange}
|
||||
className="text-sm !h-24"
|
||||
@ -322,7 +325,7 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="flex-shrink-0" tabIndex={4}>
|
||||
<Controller
|
||||
name="network"
|
||||
control={control}
|
||||
@ -359,7 +362,7 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-shrink-0">
|
||||
<div className="flex-shrink-0" tabIndex={5}>
|
||||
<Controller
|
||||
name="project_lead_member"
|
||||
control={control}
|
||||
@ -377,10 +380,10 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2 pt-5">
|
||||
<Button variant="neutral-primary" onClick={handleClose}>
|
||||
<Button variant="neutral-primary" onClick={handleClose} tabIndex={6}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" type="submit" size="sm" loading={isSubmitting}>
|
||||
<Button variant="primary" type="submit" size="sm" loading={isSubmitting} tabIndex={7}>
|
||||
{isSubmitting ? "Creating..." : "Create Project"}
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { DraggableProvided, DraggableStateSnapshot } from "react-beautiful-dnd";
|
||||
import { DraggableProvided, DraggableStateSnapshot } from "@hello-pangea/dnd";
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// icons
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useState, FC, useRef, useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { DragDropContext, Draggable, DropResult, Droppable } from "react-beautiful-dnd";
|
||||
import { DragDropContext, Draggable, DropResult, Droppable } from "@hello-pangea/dnd";
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
|
2991
web/components/ui/icon-name-type.d.ts
vendored
2991
web/components/ui/icon-name-type.d.ts
vendored
File diff suppressed because it is too large
Load Diff
@ -3,4 +3,3 @@ export * from "./form";
|
||||
export * from "./modal";
|
||||
export * from "./view-list-item";
|
||||
export * from "./views-list";
|
||||
export * from "./workspace-dashboard";
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { IMentionHighlight, IMentionSuggestion } from "@plane/rich-text-editor";
|
||||
import useUser from "./use-user";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
const useEditorSuggestions = (_workspaceSlug: string | undefined, _projectId: string | undefined) => {
|
||||
const useEditorSuggestions = () => {
|
||||
const { mentionsStore }: RootStore = useMobxStore();
|
||||
|
||||
return {
|
||||
|
@ -11,14 +11,10 @@
|
||||
"export": "next export"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blueprintjs/core": "^4.16.3",
|
||||
"@blueprintjs/popover2": "^1.13.3",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@headlessui/react": "^1.7.3",
|
||||
"@hello-pangea/dnd": "^16.3.0",
|
||||
"@jitsu/nextjs": "^3.1.5",
|
||||
"@mui/material": "^5.14.1",
|
||||
"@nivo/bar": "0.80.0",
|
||||
"@nivo/calendar": "0.80.0",
|
||||
"@nivo/core": "0.80.0",
|
||||
@ -34,13 +30,10 @@
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
"@types/react-datepicker": "^4.8.0",
|
||||
"axios": "^1.1.3",
|
||||
"clsx": "^2.0.0",
|
||||
"cmdk": "^0.2.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"highlight.js": "^11.8.0",
|
||||
"js-cookie": "^3.0.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lowlight": "^2.9.0",
|
||||
"lucide-react": "^0.274.0",
|
||||
"mobx": "^6.10.0",
|
||||
"mobx-react-lite": "^4.0.3",
|
||||
@ -49,29 +42,24 @@
|
||||
"next-themes": "^0.2.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"react": "18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-color": "^2.19.3",
|
||||
"react-datepicker": "^4.8.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-hook-form": "^7.38.0",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-moveable": "^0.54.1",
|
||||
"react-popper": "^2.3.0",
|
||||
"sharp": "^0.32.1",
|
||||
"swr": "^2.1.3",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tlds": "^1.238.0",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/js-cookie": "^3.0.2",
|
||||
"@types/node": "18.0.6",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/react": "18.0.15",
|
||||
"@types/react-beautiful-dnd": "^13.1.2",
|
||||
"@types/react": "^18.2.35",
|
||||
"@types/react-color": "^3.0.6",
|
||||
"@types/react-dom": "18.0.6",
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.2",
|
||||
"@typescript-eslint/parser": "^5.48.2",
|
||||
|
@ -2,7 +2,7 @@ import { ReactElement } from "react";
|
||||
// layouts
|
||||
import { AppLayout } from "layouts/app-layout";
|
||||
// components
|
||||
import { WorkspaceDashboardView } from "components/views";
|
||||
import { WorkspaceDashboardView } from "components/page-views";
|
||||
import { WorkspaceDashboardHeader } from "components/headers/workspace-dashboard";
|
||||
// types
|
||||
import { NextPageWithLayout } from "types/app";
|
||||
|
@ -1,19 +1,14 @@
|
||||
import { useState, ReactElement } from "react";
|
||||
import { ReactElement } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR from "swr";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// services
|
||||
import { IssueService } from "services/issue";
|
||||
// hooks
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
import useUser from "hooks/use-user";
|
||||
import useToast from "hooks/use-toast";
|
||||
// layouts
|
||||
import { AppLayout } from "layouts/app-layout";
|
||||
// components
|
||||
import { CycleIssuesHeader } from "components/headers";
|
||||
import { ExistingIssuesListModal } from "components/core";
|
||||
import { CycleDetailsSidebar } from "components/cycles";
|
||||
import { CycleLayoutRoot } from "components/issues/issue-layouts";
|
||||
// ui
|
||||
@ -21,23 +16,14 @@ import { EmptyState } from "components/common";
|
||||
// assets
|
||||
import emptyCycle from "public/empty-state/cycle.svg";
|
||||
// types
|
||||
import { ISearchIssueResponse } from "types";
|
||||
import { NextPageWithLayout } from "types/app";
|
||||
|
||||
const issueService = new IssueService();
|
||||
|
||||
const CycleDetailPage: NextPageWithLayout = () => {
|
||||
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId } = router.query;
|
||||
|
||||
const { cycle: cycleStore } = useMobxStore();
|
||||
|
||||
const { user } = useUser();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { setValue, storedValue } = useLocalStorage("cycle_sidebar_collapsed", "false");
|
||||
const isSidebarCollapsed = storedValue ? (storedValue === "true" ? true : false) : false;
|
||||
|
||||
@ -52,38 +38,8 @@ const CycleDetailPage: NextPageWithLayout = () => {
|
||||
setValue(`${!isSidebarCollapsed}`);
|
||||
};
|
||||
|
||||
// TODO: add this function to bulk add issues to cycle
|
||||
const handleAddIssuesToCycle = async (data: ISearchIssueResponse[]) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
const payload = {
|
||||
issues: data.map((i) => i.id),
|
||||
};
|
||||
|
||||
await issueService
|
||||
.addIssueToCycle(workspaceSlug as string, projectId as string, cycleId as string, payload, user)
|
||||
.catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Selected issues could not be added to the cycle. Please try again.",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const openIssuesListModal = () => {
|
||||
setCycleIssuesListModal(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* TODO: Update logic to bulk add issues to a cycle */}
|
||||
<ExistingIssuesListModal
|
||||
isOpen={cycleIssuesListModal}
|
||||
handleClose={() => setCycleIssuesListModal(false)}
|
||||
searchParams={{ cycle: true }}
|
||||
handleOnSubmit={handleAddIssuesToCycle}
|
||||
/>
|
||||
{error ? (
|
||||
<EmptyState
|
||||
image={emptyCycle}
|
||||
@ -98,7 +54,7 @@ const CycleDetailPage: NextPageWithLayout = () => {
|
||||
<>
|
||||
<div className="flex h-full w-full">
|
||||
<div className="h-full w-full overflow-hidden">
|
||||
<CycleLayoutRoot openIssuesListModal={openIssuesListModal} />
|
||||
<CycleLayoutRoot />
|
||||
</div>
|
||||
{cycleId && !isSidebarCollapsed && (
|
||||
<div
|
||||
|
@ -1,18 +1,13 @@
|
||||
import { useState, ReactElement } from "react";
|
||||
import { ReactElement } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR from "swr";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// services
|
||||
import { ModuleService } from "services/module.service";
|
||||
// hooks
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useUser from "hooks/use-user";
|
||||
// layouts
|
||||
import { AppLayout } from "layouts/app-layout";
|
||||
// components
|
||||
import { ExistingIssuesListModal } from "components/core";
|
||||
import { ModuleDetailsSidebar } from "components/modules";
|
||||
import { ModuleLayoutRoot } from "components/issues";
|
||||
import { ModuleIssuesHeader } from "components/headers";
|
||||
@ -22,21 +17,13 @@ import { EmptyState } from "components/common";
|
||||
import emptyModule from "public/empty-state/module.svg";
|
||||
// types
|
||||
import { NextPageWithLayout } from "types/app";
|
||||
import { ISearchIssueResponse } from "types";
|
||||
|
||||
const moduleService = new ModuleService();
|
||||
|
||||
const ModuleIssuesPage: NextPageWithLayout = () => {
|
||||
// states
|
||||
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, moduleId } = router.query;
|
||||
// store
|
||||
const { module: moduleStore } = useMobxStore();
|
||||
// hooks
|
||||
const { user } = useUser();
|
||||
const { setToastAlert } = useToast();
|
||||
// local storage
|
||||
const { setValue, storedValue } = useLocalStorage("module_sidebar_collapsed", "false");
|
||||
const isSidebarCollapsed = storedValue ? (storedValue === "true" ? true : false) : false;
|
||||
@ -48,42 +35,12 @@ const ModuleIssuesPage: NextPageWithLayout = () => {
|
||||
: null
|
||||
);
|
||||
|
||||
// TODO: add this function to bulk add issues to cycle
|
||||
const handleAddIssuesToModule = async (data: ISearchIssueResponse[]) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
const payload = {
|
||||
issues: data.map((i) => i.id),
|
||||
};
|
||||
|
||||
await moduleService
|
||||
.addIssuesToModule(workspaceSlug as string, projectId as string, moduleId as string, payload, user)
|
||||
.catch(() =>
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Selected issues could not be added to the module. Please try again.",
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const openIssuesListModal = () => {
|
||||
setModuleIssuesListModal(true);
|
||||
};
|
||||
|
||||
const toggleSidebar = () => {
|
||||
setValue(`${!isSidebarCollapsed}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* TODO: Update logic to bulk add issues to a cycle */}
|
||||
<ExistingIssuesListModal
|
||||
isOpen={moduleIssuesListModal}
|
||||
handleClose={() => setModuleIssuesListModal(false)}
|
||||
searchParams={{ module: true }}
|
||||
handleOnSubmit={handleAddIssuesToModule}
|
||||
/>
|
||||
{error ? (
|
||||
<EmptyState
|
||||
image={emptyModule}
|
||||
@ -97,7 +54,7 @@ const ModuleIssuesPage: NextPageWithLayout = () => {
|
||||
) : (
|
||||
<div className="flex h-full w-full">
|
||||
<div className="h-full w-full overflow-hidden">
|
||||
<ModuleLayoutRoot openIssuesListModal={openIssuesListModal} />
|
||||
<ModuleLayoutRoot />
|
||||
</div>
|
||||
{moduleId && !isSidebarCollapsed && (
|
||||
<div
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user