forked from github/plane
Merge branch 'develop' of github.com:makeplane/plane into fix/yarn-upgrade
This commit is contained in:
commit
191949657d
@ -80,9 +80,18 @@ class UserMeSettingsSerializer(BaseSerializer):
|
|||||||
workspace_invites = WorkspaceMemberInvite.objects.filter(
|
workspace_invites = WorkspaceMemberInvite.objects.filter(
|
||||||
email=obj.email
|
email=obj.email
|
||||||
).count()
|
).count()
|
||||||
if obj.last_workspace_id is not None:
|
if (
|
||||||
|
obj.last_workspace_id is not None
|
||||||
|
and Workspace.objects.filter(
|
||||||
|
pk=obj.last_workspace_id,
|
||||||
|
workspace_member__member=obj.id,
|
||||||
|
workspace_member__is_active=True,
|
||||||
|
).exists()
|
||||||
|
):
|
||||||
workspace = Workspace.objects.filter(
|
workspace = Workspace.objects.filter(
|
||||||
pk=obj.last_workspace_id, workspace_member__member=obj.id
|
pk=obj.last_workspace_id,
|
||||||
|
workspace_member__member=obj.id,
|
||||||
|
workspace_member__is_active=True,
|
||||||
).first()
|
).first()
|
||||||
return {
|
return {
|
||||||
"last_workspace_id": obj.last_workspace_id,
|
"last_workspace_id": obj.last_workspace_id,
|
||||||
@ -95,7 +104,9 @@ class UserMeSettingsSerializer(BaseSerializer):
|
|||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
fallback_workspace = (
|
fallback_workspace = (
|
||||||
Workspace.objects.filter(workspace_member__member_id=obj.id)
|
Workspace.objects.filter(
|
||||||
|
workspace_member__member_id=obj.id, workspace_member__is_active=True
|
||||||
|
)
|
||||||
.order_by("created_at")
|
.order_by("created_at")
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
@ -159,10 +170,14 @@ class ChangePasswordSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
if data.get("old_password") == data.get("new_password"):
|
if data.get("old_password") == data.get("new_password"):
|
||||||
raise serializers.ValidationError({"error": "New password cannot be same as old password."})
|
raise serializers.ValidationError(
|
||||||
|
{"error": "New password cannot be same as old password."}
|
||||||
|
)
|
||||||
|
|
||||||
if data.get("new_password") != data.get("confirm_password"):
|
if data.get("new_password") != data.get("confirm_password"):
|
||||||
raise serializers.ValidationError({"error": "Confirm password should be same as the new password."})
|
raise serializers.ValidationError(
|
||||||
|
{"error": "Confirm password should be same as the new password."}
|
||||||
|
)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import random
|
|||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
# Django imports
|
# Django imports
|
||||||
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
Prefetch,
|
Prefetch,
|
||||||
@ -367,6 +368,8 @@ class IssueListGroupedEndpoint(BaseAPIView):
|
|||||||
|
|
||||||
def get(self, request, slug, project_id):
|
def get(self, request, slug, project_id):
|
||||||
filters = issue_filters(request.query_params, "GET")
|
filters = issue_filters(request.query_params, "GET")
|
||||||
|
archive = request.GET.get("archived", False)
|
||||||
|
draft = request.GET.get("draft", False)
|
||||||
fields = [field for field in request.GET.get("fields", "").split(",") if field]
|
fields = [field for field in request.GET.get("fields", "").split(",") if field]
|
||||||
|
|
||||||
issue_queryset = (
|
issue_queryset = (
|
||||||
@ -384,6 +387,14 @@ class IssueListGroupedEndpoint(BaseAPIView):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
.filter(**filters)
|
.filter(**filters)
|
||||||
|
.filter(is_draft=bool(draft))
|
||||||
|
.filter(~Q(archived_at__isnull=bool(archive)))
|
||||||
|
.filter(
|
||||||
|
models.Q(issue_inbox__status=1)
|
||||||
|
| models.Q(issue_inbox__status=-1)
|
||||||
|
| models.Q(issue_inbox__status=2)
|
||||||
|
| models.Q(issue_inbox__isnull=True)
|
||||||
|
)
|
||||||
.annotate(cycle_id=F("issue_cycle__cycle_id"))
|
.annotate(cycle_id=F("issue_cycle__cycle_id"))
|
||||||
.annotate(module_id=F("issue_module__module_id"))
|
.annotate(module_id=F("issue_module__module_id"))
|
||||||
.annotate(
|
.annotate(
|
||||||
|
@ -113,6 +113,15 @@ class UserEndpoint(BaseViewSet):
|
|||||||
|
|
||||||
# Deactivate the user
|
# Deactivate the user
|
||||||
user.is_active = False
|
user.is_active = False
|
||||||
|
user.last_workspace_id = None
|
||||||
|
user.is_tour_completed = False
|
||||||
|
user.is_onboarded = False
|
||||||
|
user.onboarding_step = {
|
||||||
|
"workspace_join": False,
|
||||||
|
"profile_complete": False,
|
||||||
|
"workspace_create": False,
|
||||||
|
"workspace_invite": False,
|
||||||
|
}
|
||||||
user.save()
|
user.save()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
@ -135,9 +144,9 @@ class UpdateUserTourCompletedEndpoint(BaseAPIView):
|
|||||||
|
|
||||||
class UserActivityEndpoint(BaseAPIView, BasePaginator):
|
class UserActivityEndpoint(BaseAPIView, BasePaginator):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
queryset = IssueActivity.objects.filter(
|
queryset = IssueActivity.objects.filter(actor=request.user).select_related(
|
||||||
actor=request.user
|
"actor", "workspace", "issue", "project"
|
||||||
).select_related("actor", "workspace", "issue", "project")
|
)
|
||||||
|
|
||||||
return self.paginate(
|
return self.paginate(
|
||||||
request=request,
|
request=request,
|
||||||
|
@ -113,7 +113,10 @@ class WorkSpaceViewSet(BaseViewSet):
|
|||||||
return (
|
return (
|
||||||
self.filter_queryset(super().get_queryset().select_related("owner"))
|
self.filter_queryset(super().get_queryset().select_related("owner"))
|
||||||
.order_by("name")
|
.order_by("name")
|
||||||
.filter(workspace_member__member=self.request.user)
|
.filter(
|
||||||
|
workspace_member__member=self.request.user,
|
||||||
|
workspace_member__is_active=True,
|
||||||
|
)
|
||||||
.annotate(total_members=member_count)
|
.annotate(total_members=member_count)
|
||||||
.annotate(total_issues=issue_count)
|
.annotate(total_issues=issue_count)
|
||||||
.select_related("owner")
|
.select_related("owner")
|
||||||
@ -189,17 +192,21 @@ class UserWorkSpacesEndpoint(BaseAPIView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
workspace = (
|
workspace = (
|
||||||
(
|
|
||||||
Workspace.objects.prefetch_related(
|
Workspace.objects.prefetch_related(
|
||||||
Prefetch("workspace_member", queryset=WorkspaceMember.objects.all())
|
Prefetch(
|
||||||
|
"workspace_member",
|
||||||
|
queryset=WorkspaceMember.objects.filter(
|
||||||
|
member=request.user, is_active=True
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.filter(
|
|
||||||
workspace_member__member=request.user,
|
|
||||||
)
|
)
|
||||||
.select_related("owner")
|
.select_related("owner")
|
||||||
)
|
|
||||||
.annotate(total_members=member_count)
|
.annotate(total_members=member_count)
|
||||||
.annotate(total_issues=issue_count)
|
.annotate(total_issues=issue_count)
|
||||||
|
.filter(
|
||||||
|
workspace_member__member=request.user, workspace_member__is_active=True
|
||||||
|
)
|
||||||
|
.distinct()
|
||||||
)
|
)
|
||||||
|
|
||||||
serializer = WorkSpaceSerializer(self.filter_queryset(workspace), many=True)
|
serializer = WorkSpaceSerializer(self.filter_queryset(workspace), many=True)
|
||||||
@ -319,7 +326,7 @@ class WorkspaceInvitationsViewset(BaseViewSet):
|
|||||||
workspace_invitations, batch_size=10, ignore_conflicts=True
|
workspace_invitations, batch_size=10, ignore_conflicts=True
|
||||||
)
|
)
|
||||||
|
|
||||||
current_site = request.META.get('HTTP_ORIGIN')
|
current_site = request.META.get("HTTP_ORIGIN")
|
||||||
|
|
||||||
# Send invitations
|
# Send invitations
|
||||||
for invitation in workspace_invitations:
|
for invitation in workspace_invitations:
|
||||||
@ -418,7 +425,9 @@ class WorkspaceJoinEndpoint(BaseAPIView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get(self, request, slug, pk):
|
def get(self, request, slug, pk):
|
||||||
workspace_invitation = WorkspaceMemberInvite.objects.get(workspace__slug=slug, pk=pk)
|
workspace_invitation = WorkspaceMemberInvite.objects.get(
|
||||||
|
workspace__slug=slug, pk=pk
|
||||||
|
)
|
||||||
serializer = WorkSpaceMemberInviteSerializer(workspace_invitation)
|
serializer = WorkSpaceMemberInviteSerializer(workspace_invitation)
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
@ -1313,9 +1322,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
|
|||||||
return Response(issues, status=status.HTTP_200_OK)
|
return Response(issues, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WorkspaceUserProfileIssuesGroupedEndpoint(BaseAPIView):
|
class WorkspaceUserProfileIssuesGroupedEndpoint(BaseAPIView):
|
||||||
|
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
WorkspaceViewerPermission,
|
WorkspaceViewerPermission,
|
||||||
]
|
]
|
||||||
@ -1362,13 +1369,16 @@ class WorkspaceUserProfileIssuesGroupedEndpoint(BaseAPIView):
|
|||||||
)
|
)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
issues = IssueLiteSerializer(issue_queryset, many=True, fields=fields if fields else None).data
|
issues = IssueLiteSerializer(
|
||||||
|
issue_queryset, many=True, fields=fields if fields else None
|
||||||
|
).data
|
||||||
issue_dict = {str(issue["id"]): issue for issue in issues}
|
issue_dict = {str(issue["id"]): issue for issue in issues}
|
||||||
return Response(
|
return Response(
|
||||||
issue_dict,
|
issue_dict,
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WorkspaceLabelsEndpoint(BaseAPIView):
|
class WorkspaceLabelsEndpoint(BaseAPIView):
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
WorkspaceViewerPermission,
|
WorkspaceViewerPermission,
|
||||||
|
118
deploy/selfhost/migration-0.13-0.14.sh
Executable file
118
deploy/selfhost/migration-0.13-0.14.sh
Executable file
@ -0,0 +1,118 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo '
|
||||||
|
******************************************************************
|
||||||
|
|
||||||
|
This script is solely for the migration purpose only.
|
||||||
|
This is a 1 time migration of volume data from v0.13.2 => v0.14.x
|
||||||
|
|
||||||
|
Assumption:
|
||||||
|
1. Postgres data volume name ends with _pgdata
|
||||||
|
2. Minio data volume name ends with _uploads
|
||||||
|
3. Redis data volume name ends with _redisdata
|
||||||
|
|
||||||
|
Any changes to this script can break the migration.
|
||||||
|
|
||||||
|
Before you proceed, make sure you run the below command
|
||||||
|
to know the docker volumes
|
||||||
|
|
||||||
|
docker volume ls -q | grep -i "_pgdata"
|
||||||
|
docker volume ls -q | grep -i "_uploads"
|
||||||
|
docker volume ls -q | grep -i "_redisdata"
|
||||||
|
|
||||||
|
*******************************************************
|
||||||
|
'
|
||||||
|
|
||||||
|
DOWNLOAD_FOL=./download
|
||||||
|
rm -rf ${DOWNLOAD_FOL}
|
||||||
|
mkdir -p ${DOWNLOAD_FOL}
|
||||||
|
|
||||||
|
function volumeExists {
|
||||||
|
if [ "$(docker volume ls -f name=$1 | awk '{print $NF}' | grep -E '^'$1'$')" ]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPrefixes(){
|
||||||
|
echo ''
|
||||||
|
echo 'Given below list of REDIS volumes, identify the prefix of source and destination volumes leaving "_redisdata" '
|
||||||
|
echo '---------------------'
|
||||||
|
docker volume ls -q | grep -i "_redisdata"
|
||||||
|
echo ''
|
||||||
|
|
||||||
|
read -p "Provide the Source Volume Prefix : " SRC_VOL_PREFIX
|
||||||
|
until [ "$SRC_VOL_PREFIX" ]; do
|
||||||
|
read -p "Provide the Source Volume Prefix : " SRC_VOL_PREFIX
|
||||||
|
done
|
||||||
|
|
||||||
|
read -p "Provide the Destination Volume Prefix : " DEST_VOL_PREFIX
|
||||||
|
until [ "$DEST_VOL_PREFIX" ]; do
|
||||||
|
read -p "Provide the Source Volume Prefix : " DEST_VOL_PREFIX
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ''
|
||||||
|
echo 'Prefix Provided '
|
||||||
|
echo " Source : ${SRC_VOL_PREFIX}"
|
||||||
|
echo " Destination : ${DEST_VOL_PREFIX}"
|
||||||
|
echo '---------------------------------------'
|
||||||
|
}
|
||||||
|
|
||||||
|
function migrate(){
|
||||||
|
|
||||||
|
SRC_VOLUME=${SRC_VOL_PREFIX}_${VOL_NAME_SUFFIX}
|
||||||
|
DEST_VOLUME=${DEST_VOL_PREFIX}_${VOL_NAME_SUFFIX}
|
||||||
|
|
||||||
|
if volumeExists $SRC_VOLUME; then
|
||||||
|
if volumeExists $DEST_VOLUME; then
|
||||||
|
GOOD_TO_GO=1
|
||||||
|
else
|
||||||
|
echo "Destination Volume '$DEST_VOLUME' does not exist"
|
||||||
|
echo ''
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Source Volume '$SRC_VOLUME' does not exist"
|
||||||
|
echo ''
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $GOOD_TO_GO = 1 ]; then
|
||||||
|
|
||||||
|
echo "MIGRATING ${VOL_NAME_SUFFIX} FROM ${SRC_VOLUME} => ${DEST_VOLUME}"
|
||||||
|
|
||||||
|
TEMP_CONTAINER=$(docker run -d -v $SRC_VOLUME:$CONTAINER_VOL_FOLDER busybox true)
|
||||||
|
docker cp -q $TEMP_CONTAINER:$CONTAINER_VOL_FOLDER ${DOWNLOAD_FOL}/${VOL_NAME_SUFFIX}
|
||||||
|
docker rm $TEMP_CONTAINER &> /dev/null
|
||||||
|
|
||||||
|
TEMP_CONTAINER=$(docker run -d -v $DEST_VOLUME:$CONTAINER_VOL_FOLDER busybox true)
|
||||||
|
if [ "$VOL_NAME_SUFFIX" = "pgdata" ]; then
|
||||||
|
docker cp -q ${DOWNLOAD_FOL}/${VOL_NAME_SUFFIX} $TEMP_CONTAINER:$CONTAINER_VOL_FOLDER/_temp
|
||||||
|
docker run --rm -v $DEST_VOLUME:$CONTAINER_VOL_FOLDER \
|
||||||
|
-e DATA_FOLDER="${CONTAINER_VOL_FOLDER}" \
|
||||||
|
busybox /bin/sh -c 'cp -Rf $DATA_FOLDER/_temp/* $DATA_FOLDER '
|
||||||
|
else
|
||||||
|
docker cp -q ${DOWNLOAD_FOL}/${VOL_NAME_SUFFIX} $TEMP_CONTAINER:$CONTAINER_VOL_FOLDER
|
||||||
|
fi
|
||||||
|
docker rm $TEMP_CONTAINER &> /dev/null
|
||||||
|
|
||||||
|
echo ''
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
readPrefixes
|
||||||
|
|
||||||
|
# MIGRATE DB
|
||||||
|
CONTAINER_VOL_FOLDER=/var/lib/postgresql/data
|
||||||
|
VOL_NAME_SUFFIX=pgdata
|
||||||
|
migrate
|
||||||
|
|
||||||
|
# MIGRATE REDIS
|
||||||
|
CONTAINER_VOL_FOLDER=/data
|
||||||
|
VOL_NAME_SUFFIX=redisdata
|
||||||
|
migrate
|
||||||
|
|
||||||
|
# MIGRATE MINIO
|
||||||
|
CONTAINER_VOL_FOLDER=/export
|
||||||
|
VOL_NAME_SUFFIX=uploads
|
||||||
|
migrate
|
||||||
|
|
@ -28,7 +28,7 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
|
|||||||
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
|
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
cycleIssue: cycleIssueStore,
|
cycleIssues: cycleIssueStore,
|
||||||
commandPalette: commandPaletteStore,
|
commandPalette: commandPaletteStore,
|
||||||
trackEvent: { setTrackElement },
|
trackEvent: { setTrackElement },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
@ -40,9 +40,7 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
const issueIds = data.map((i) => i.id);
|
const issueIds = data.map((i) => i.id);
|
||||||
|
|
||||||
await cycleIssueStore
|
await cycleIssueStore.addIssueToCycle(workspaceSlug.toString(), cycleId.toString(), issueIds).catch(() => {
|
||||||
.addIssueToCycle(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), issueIds)
|
|
||||||
.catch(() => {
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
|
@ -22,7 +22,11 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
|
|||||||
// states
|
// states
|
||||||
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
|
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
|
||||||
|
|
||||||
const { moduleIssue: moduleIssueStore, commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore();
|
const {
|
||||||
|
moduleIssues: moduleIssueStore,
|
||||||
|
commandPalette: commandPaletteStore,
|
||||||
|
trackEvent: { setTrackElement },
|
||||||
|
} = useMobxStore();
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
@ -31,9 +35,7 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
const issueIds = data.map((i) => i.id);
|
const issueIds = data.map((i) => i.id);
|
||||||
|
|
||||||
await moduleIssueStore
|
await moduleIssueStore.addIssueToModule(workspaceSlug.toString(), moduleId.toString(), issueIds).catch(() =>
|
||||||
.addIssueToModule(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), issueIds)
|
|
||||||
.catch(() =>
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
@ -60,8 +62,8 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
|
|||||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setTrackElement("MODULE_EMPTY_STATE");
|
setTrackElement("MODULE_EMPTY_STATE");
|
||||||
commandPaletteStore.toggleCreateIssueModal(true)
|
commandPaletteStore.toggleCreateIssueModal(true);
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
secondaryButton={
|
secondaryButton={
|
||||||
<Button
|
<Button
|
||||||
|
@ -18,6 +18,7 @@ export const ProfileIssuesAppliedFiltersRoot: React.FC = observer(() => {
|
|||||||
const {
|
const {
|
||||||
workspace: { workspaceLabels },
|
workspace: { workspaceLabels },
|
||||||
workspaceProfileIssuesFilter: { issueFilters, updateFilters },
|
workspaceProfileIssuesFilter: { issueFilters, updateFilters },
|
||||||
|
projectMember: { projectMembers },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
const userFilters = issueFilters?.filters;
|
const userFilters = issueFilters?.filters;
|
||||||
@ -64,7 +65,7 @@ export const ProfileIssuesAppliedFiltersRoot: React.FC = observer(() => {
|
|||||||
handleClearAllFilters={handleClearAllFilters}
|
handleClearAllFilters={handleClearAllFilters}
|
||||||
handleRemoveFilter={handleRemoveFilter}
|
handleRemoveFilter={handleRemoveFilter}
|
||||||
labels={workspaceLabels ?? []}
|
labels={workspaceLabels ?? []}
|
||||||
members={[]}
|
members={projectMembers?.map((m) => m.member)}
|
||||||
states={[]}
|
states={[]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,7 +25,6 @@ import {
|
|||||||
IViewIssuesFilterStore,
|
IViewIssuesFilterStore,
|
||||||
IViewIssuesStore,
|
IViewIssuesStore,
|
||||||
} from "store/issues";
|
} from "store/issues";
|
||||||
import { EUserWorkspaceRoles } from "layouts/settings-layout/workspace/sidebar";
|
|
||||||
import { TUnGroupedIssues } from "store/issues/types";
|
import { TUnGroupedIssues } from "store/issues/types";
|
||||||
|
|
||||||
interface IBaseGanttRoot {
|
interface IBaseGanttRoot {
|
||||||
@ -46,6 +45,10 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
|||||||
|
|
||||||
const { projectDetails } = useProjectDetails();
|
const { projectDetails } = useProjectDetails();
|
||||||
|
|
||||||
|
const {
|
||||||
|
user: { currentProjectRole },
|
||||||
|
} = useMobxStore();
|
||||||
|
|
||||||
const appliedDisplayFilters = issueFiltersStore.issueFilters?.displayFilters;
|
const appliedDisplayFilters = issueFiltersStore.issueFilters?.displayFilters;
|
||||||
|
|
||||||
const issuesResponse = issueStore.getIssues;
|
const issuesResponse = issueStore.getIssues;
|
||||||
@ -69,7 +72,7 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAllowed = (projectDetails?.member_role || 0) >= EUserWorkspaceRoles.MEMBER;
|
const isAllowed = currentProjectRole && currentProjectRole >= 15;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -54,6 +54,7 @@ export interface IBaseKanBanLayout {
|
|||||||
showLoader?: boolean;
|
showLoader?: boolean;
|
||||||
viewId?: string;
|
viewId?: string;
|
||||||
currentStore?: EProjectStore;
|
currentStore?: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBaseKanBanLayout) => {
|
export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBaseKanBanLayout) => {
|
||||||
@ -66,6 +67,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||||||
showLoader,
|
showLoader,
|
||||||
viewId,
|
viewId,
|
||||||
currentStore,
|
currentStore,
|
||||||
|
addIssuesToView,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -186,6 +188,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||||||
disableIssueCreation={!enableIssueCreation}
|
disableIssueCreation={!enableIssueCreation}
|
||||||
isReadOnly={!enableInlineEditing}
|
isReadOnly={!enableInlineEditing}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<KanBanSwimLanes
|
<KanBanSwimLanes
|
||||||
@ -226,6 +229,11 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||||||
enableQuickIssueCreate={enableQuickAdd}
|
enableQuickIssueCreate={enableQuickAdd}
|
||||||
isReadOnly={!enableInlineEditing}
|
isReadOnly={!enableInlineEditing}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={(issues) => {
|
||||||
|
console.log("kanban existingIds", issues);
|
||||||
|
|
||||||
|
return Promise.resolve({} as IIssue);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</DragDropContext>
|
</DragDropContext>
|
||||||
|
@ -43,6 +43,7 @@ export interface IGroupByKanBan {
|
|||||||
viewId?: string;
|
viewId?: string;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore?: EProjectStore;
|
currentStore?: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +71,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
|||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
isReadOnly,
|
isReadOnly,
|
||||||
currentStore,
|
currentStore,
|
||||||
|
addIssuesToView,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const verticalAlignPosition = (_list: any) =>
|
const verticalAlignPosition = (_list: any) =>
|
||||||
@ -95,6 +97,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -201,6 +204,7 @@ export interface IKanBan {
|
|||||||
viewId?: string;
|
viewId?: string;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore?: EProjectStore;
|
currentStore?: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,6 +235,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
isReadOnly,
|
isReadOnly,
|
||||||
currentStore,
|
currentStore,
|
||||||
|
addIssuesToView,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const { issueKanBanView: issueKanBanViewStore } = useMobxStore();
|
const { issueKanBanView: issueKanBanViewStore } = useMobxStore();
|
||||||
@ -262,6 +267,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -290,6 +296,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -318,6 +325,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -346,6 +354,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -374,6 +383,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -402,6 +412,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -430,6 +441,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,6 +6,7 @@ import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
|||||||
// ui
|
// ui
|
||||||
import { Avatar } from "@plane/ui";
|
import { Avatar } from "@plane/ui";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface IAssigneesHeader {
|
export interface IAssigneesHeader {
|
||||||
column_id: string;
|
column_id: string;
|
||||||
@ -18,6 +19,7 @@ export interface IAssigneesHeader {
|
|||||||
handleKanBanToggle: any;
|
handleKanBanToggle: any;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore?: EProjectStore;
|
currentStore?: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Icon = ({ user }: any) => <Avatar name={user.display_name} src={user.avatar} size="base" />;
|
export const Icon = ({ user }: any) => <Avatar name={user.display_name} src={user.avatar} size="base" />;
|
||||||
@ -34,6 +36,7 @@ export const AssigneesHeader: FC<IAssigneesHeader> = observer((props) => {
|
|||||||
handleKanBanToggle,
|
handleKanBanToggle,
|
||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
currentStore,
|
currentStore,
|
||||||
|
addIssuesToView,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const assignee = column_value ?? null;
|
const assignee = column_value ?? null;
|
||||||
@ -63,6 +66,7 @@ export const AssigneesHeader: FC<IAssigneesHeader> = observer((props) => {
|
|||||||
issuePayload={{ assignees: [assignee?.id] }}
|
issuePayload={{ assignees: [assignee?.id] }}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
@ -5,6 +5,7 @@ import { HeaderGroupByCard } from "./group-by-card";
|
|||||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||||
import { Icon } from "./assignee";
|
import { Icon } from "./assignee";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface ICreatedByHeader {
|
export interface ICreatedByHeader {
|
||||||
column_id: string;
|
column_id: string;
|
||||||
@ -17,6 +18,7 @@ export interface ICreatedByHeader {
|
|||||||
handleKanBanToggle: any;
|
handleKanBanToggle: any;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore?: EProjectStore;
|
currentStore?: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
|
export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
|
||||||
@ -31,6 +33,7 @@ export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
|
|||||||
handleKanBanToggle,
|
handleKanBanToggle,
|
||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
currentStore,
|
currentStore,
|
||||||
|
addIssuesToView,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const createdBy = column_value ?? null;
|
const createdBy = column_value ?? null;
|
||||||
@ -60,6 +63,7 @@ export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
|
|||||||
issuePayload={{ created_by: createdBy?.id }}
|
issuePayload={{ created_by: createdBy?.id }}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
@ -29,11 +29,9 @@ interface IHeaderGroupByCard {
|
|||||||
issuePayload: Partial<IIssue>;
|
issuePayload: Partial<IIssue>;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore?: EProjectStore;
|
currentStore?: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const moduleService = new ModuleService();
|
|
||||||
const issueService = new IssueService();
|
|
||||||
|
|
||||||
export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
||||||
const {
|
const {
|
||||||
sub_group_by,
|
sub_group_by,
|
||||||
@ -46,6 +44,7 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
|||||||
issuePayload,
|
issuePayload,
|
||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
currentStore,
|
currentStore,
|
||||||
|
addIssuesToView,
|
||||||
} = props;
|
} = props;
|
||||||
const verticalAlignPosition = kanBanToggle?.groupByHeaderMinMax.includes(column_id);
|
const verticalAlignPosition = kanBanToggle?.groupByHeaderMinMax.includes(column_id);
|
||||||
|
|
||||||
@ -60,34 +59,13 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
|||||||
const renderExistingIssueModal = moduleId || cycleId;
|
const renderExistingIssueModal = moduleId || cycleId;
|
||||||
const ExistingIssuesListModalPayload = moduleId ? { module: true } : { cycle: true };
|
const ExistingIssuesListModalPayload = moduleId ? { module: true } : { cycle: true };
|
||||||
|
|
||||||
const handleAddIssuesToModule = async (data: ISearchIssueResponse[]) => {
|
const handleAddIssuesToView = async (data: ISearchIssueResponse[]) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
const payload = {
|
const issues = data.map((i) => i.id);
|
||||||
issues: data.map((i) => i.id),
|
|
||||||
};
|
|
||||||
|
|
||||||
await moduleService
|
addIssuesToView &&
|
||||||
.addIssuesToModule(workspaceSlug as string, projectId as string, moduleId as string, payload)
|
addIssuesToView(issues)?.catch(() => {
|
||||||
.catch(() =>
|
|
||||||
setToastAlert({
|
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message: "Selected issues could not be added to the module. Please try again.",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
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)
|
|
||||||
.catch(() => {
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
@ -109,7 +87,7 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
|||||||
isOpen={openExistingIssueListModal}
|
isOpen={openExistingIssueListModal}
|
||||||
handleClose={() => setOpenExistingIssueListModal(false)}
|
handleClose={() => setOpenExistingIssueListModal(false)}
|
||||||
searchParams={ExistingIssuesListModalPayload}
|
searchParams={ExistingIssuesListModalPayload}
|
||||||
handleOnSubmit={moduleId ? handleAddIssuesToModule : handleAddIssuesToCycle}
|
handleOnSubmit={handleAddIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
|
@ -9,6 +9,7 @@ import { CreatedByHeader } from "./created_by";
|
|||||||
// mobx
|
// mobx
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface IKanBanGroupByHeaderRoot {
|
export interface IKanBanGroupByHeaderRoot {
|
||||||
column_id: string;
|
column_id: string;
|
||||||
@ -20,6 +21,7 @@ export interface IKanBanGroupByHeaderRoot {
|
|||||||
handleKanBanToggle: any;
|
handleKanBanToggle: any;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore?: EProjectStore;
|
currentStore?: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = observer(
|
export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = observer(
|
||||||
@ -33,6 +35,7 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
|
|||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
handleKanBanToggle,
|
handleKanBanToggle,
|
||||||
currentStore,
|
currentStore,
|
||||||
|
addIssuesToView,
|
||||||
}) => (
|
}) => (
|
||||||
<>
|
<>
|
||||||
{group_by && group_by === "project" && (
|
{group_by && group_by === "project" && (
|
||||||
@ -47,6 +50,7 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -62,6 +66,7 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{group_by && group_by === "state_detail.group" && (
|
{group_by && group_by === "state_detail.group" && (
|
||||||
@ -76,6 +81,7 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{group_by && group_by === "priority" && (
|
{group_by && group_by === "priority" && (
|
||||||
@ -90,6 +96,7 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{group_by && group_by === "labels" && (
|
{group_by && group_by === "labels" && (
|
||||||
@ -104,6 +111,7 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{group_by && group_by === "assignees" && (
|
{group_by && group_by === "assignees" && (
|
||||||
@ -118,6 +126,7 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{group_by && group_by === "created_by" && (
|
{group_by && group_by === "created_by" && (
|
||||||
@ -132,6 +141,7 @@ export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = obser
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -4,6 +4,7 @@ import { observer } from "mobx-react-lite";
|
|||||||
import { HeaderGroupByCard } from "./group-by-card";
|
import { HeaderGroupByCard } from "./group-by-card";
|
||||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface ILabelHeader {
|
export interface ILabelHeader {
|
||||||
column_id: string;
|
column_id: string;
|
||||||
@ -16,6 +17,7 @@ export interface ILabelHeader {
|
|||||||
handleKanBanToggle: any;
|
handleKanBanToggle: any;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore?: EProjectStore;
|
currentStore?: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Icon = ({ color }: any) => (
|
const Icon = ({ color }: any) => (
|
||||||
@ -34,6 +36,7 @@ export const LabelHeader: FC<ILabelHeader> = observer((props) => {
|
|||||||
handleKanBanToggle,
|
handleKanBanToggle,
|
||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
currentStore,
|
currentStore,
|
||||||
|
addIssuesToView,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const label = column_value ?? null;
|
const label = column_value ?? null;
|
||||||
@ -63,6 +66,7 @@ export const LabelHeader: FC<ILabelHeader> = observer((props) => {
|
|||||||
issuePayload={{ labels: [label?.id] }}
|
issuePayload={{ labels: [label?.id] }}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
@ -8,6 +8,7 @@ import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
|||||||
// Icons
|
// Icons
|
||||||
import { PriorityIcon } from "@plane/ui";
|
import { PriorityIcon } from "@plane/ui";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface IPriorityHeader {
|
export interface IPriorityHeader {
|
||||||
column_id: string;
|
column_id: string;
|
||||||
@ -20,6 +21,7 @@ export interface IPriorityHeader {
|
|||||||
handleKanBanToggle: any;
|
handleKanBanToggle: any;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore?: EProjectStore;
|
currentStore?: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
|
export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
|
||||||
@ -34,6 +36,7 @@ export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
|
|||||||
handleKanBanToggle,
|
handleKanBanToggle,
|
||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
currentStore,
|
currentStore,
|
||||||
|
addIssuesToView,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const priority = column_value || null;
|
const priority = column_value || null;
|
||||||
@ -63,6 +66,7 @@ export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
|
|||||||
issuePayload={{ priority: priority?.key }}
|
issuePayload={{ priority: priority?.key }}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
@ -6,6 +6,7 @@ import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
|||||||
// emoji helper
|
// emoji helper
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
import { renderEmoji } from "helpers/emoji.helper";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface IProjectHeader {
|
export interface IProjectHeader {
|
||||||
column_id: string;
|
column_id: string;
|
||||||
@ -18,6 +19,7 @@ export interface IProjectHeader {
|
|||||||
handleKanBanToggle: any;
|
handleKanBanToggle: any;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore?: EProjectStore;
|
currentStore?: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Icon = ({ emoji }: any) => <div className="w-6 h-6">{renderEmoji(emoji)}</div>;
|
const Icon = ({ emoji }: any) => <div className="w-6 h-6">{renderEmoji(emoji)}</div>;
|
||||||
@ -34,6 +36,7 @@ export const ProjectHeader: FC<IProjectHeader> = observer((props) => {
|
|||||||
handleKanBanToggle,
|
handleKanBanToggle,
|
||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
currentStore,
|
currentStore,
|
||||||
|
addIssuesToView,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const project = column_value ?? null;
|
const project = column_value ?? null;
|
||||||
@ -63,6 +66,7 @@ export const ProjectHeader: FC<IProjectHeader> = observer((props) => {
|
|||||||
issuePayload={{ project: project?.id }}
|
issuePayload={{ project: project?.id }}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
@ -5,6 +5,7 @@ import { HeaderGroupByCard } from "./group-by-card";
|
|||||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||||
import { StateGroupIcon } from "@plane/ui";
|
import { StateGroupIcon } from "@plane/ui";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface IStateGroupHeader {
|
export interface IStateGroupHeader {
|
||||||
column_id: string;
|
column_id: string;
|
||||||
@ -17,6 +18,7 @@ export interface IStateGroupHeader {
|
|||||||
handleKanBanToggle: any;
|
handleKanBanToggle: any;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore?: EProjectStore;
|
currentStore?: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) => (
|
export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) => (
|
||||||
@ -37,6 +39,7 @@ export const StateGroupHeader: FC<IStateGroupHeader> = observer((props) => {
|
|||||||
handleKanBanToggle,
|
handleKanBanToggle,
|
||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
currentStore,
|
currentStore,
|
||||||
|
addIssuesToView,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const stateGroup = column_value || null;
|
const stateGroup = column_value || null;
|
||||||
@ -66,6 +69,7 @@ export const StateGroupHeader: FC<IStateGroupHeader> = observer((props) => {
|
|||||||
issuePayload={{}}
|
issuePayload={{}}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
@ -5,6 +5,7 @@ import { HeaderGroupByCard } from "./group-by-card";
|
|||||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||||
import { Icon } from "./state-group";
|
import { Icon } from "./state-group";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface IStateHeader {
|
export interface IStateHeader {
|
||||||
column_id: string;
|
column_id: string;
|
||||||
@ -17,6 +18,7 @@ export interface IStateHeader {
|
|||||||
handleKanBanToggle: any;
|
handleKanBanToggle: any;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore?: EProjectStore;
|
currentStore?: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StateHeader: FC<IStateHeader> = observer((props) => {
|
export const StateHeader: FC<IStateHeader> = observer((props) => {
|
||||||
@ -31,6 +33,7 @@ export const StateHeader: FC<IStateHeader> = observer((props) => {
|
|||||||
handleKanBanToggle,
|
handleKanBanToggle,
|
||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
currentStore,
|
currentStore,
|
||||||
|
addIssuesToView,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const state = column_value ?? null;
|
const state = column_value ?? null;
|
||||||
@ -60,6 +63,7 @@ export const StateHeader: FC<IStateHeader> = observer((props) => {
|
|||||||
issuePayload={{ state: state?.id }}
|
issuePayload={{ state: state?.id }}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
@ -8,6 +8,7 @@ import { PriorityHeader } from "./priority";
|
|||||||
import { LabelHeader } from "./label";
|
import { LabelHeader } from "./label";
|
||||||
import { CreatedByHeader } from "./created_by";
|
import { CreatedByHeader } from "./created_by";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface IKanBanSubGroupByHeaderRoot {
|
export interface IKanBanSubGroupByHeaderRoot {
|
||||||
column_id: string;
|
column_id: string;
|
||||||
@ -19,6 +20,7 @@ export interface IKanBanSubGroupByHeaderRoot {
|
|||||||
handleKanBanToggle: any;
|
handleKanBanToggle: any;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore?: EProjectStore;
|
currentStore?: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> = observer((props) => {
|
export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> = observer((props) => {
|
||||||
@ -32,6 +34,7 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
|
|||||||
handleKanBanToggle,
|
handleKanBanToggle,
|
||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
currentStore,
|
currentStore,
|
||||||
|
addIssuesToView,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -48,6 +51,7 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{sub_group_by && sub_group_by === "state_detail.group" && (
|
{sub_group_by && sub_group_by === "state_detail.group" && (
|
||||||
@ -62,6 +66,7 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{sub_group_by && sub_group_by === "priority" && (
|
{sub_group_by && sub_group_by === "priority" && (
|
||||||
@ -76,6 +81,7 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{sub_group_by && sub_group_by === "labels" && (
|
{sub_group_by && sub_group_by === "labels" && (
|
||||||
@ -90,6 +96,7 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{sub_group_by && sub_group_by === "assignees" && (
|
{sub_group_by && sub_group_by === "assignees" && (
|
||||||
@ -104,6 +111,7 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{sub_group_by && sub_group_by === "created_by" && (
|
{sub_group_by && sub_group_by === "created_by" && (
|
||||||
@ -118,6 +126,7 @@ export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> =
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -50,6 +50,7 @@ export const CycleKanBanLayout: React.FC = observer(() => {
|
|||||||
QuickActions={CycleIssueQuickActions}
|
QuickActions={CycleIssueQuickActions}
|
||||||
viewId={cycleId}
|
viewId={cycleId}
|
||||||
currentStore={EProjectStore.CYCLE}
|
currentStore={EProjectStore.CYCLE}
|
||||||
|
addIssuesToView={(issues: string[]) => cycleIssueStore.addIssueToCycle(workspaceSlug, cycleId, issues)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -72,6 +72,7 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
|
|||||||
QuickActions={ModuleIssueQuickActions}
|
QuickActions={ModuleIssueQuickActions}
|
||||||
viewId={moduleId}
|
viewId={moduleId}
|
||||||
currentStore={EProjectStore.MODULE}
|
currentStore={EProjectStore.MODULE}
|
||||||
|
addIssuesToView={(issues: string[]) => moduleIssueStore.addIssueToModule(workspaceSlug, moduleId, issues)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -23,6 +23,7 @@ interface ISubGroupSwimlaneHeader {
|
|||||||
handleKanBanToggle: any;
|
handleKanBanToggle: any;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore?: EProjectStore;
|
currentStore?: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
||||||
issueIds,
|
issueIds,
|
||||||
@ -34,6 +35,7 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
|||||||
handleKanBanToggle,
|
handleKanBanToggle,
|
||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
currentStore,
|
currentStore,
|
||||||
|
addIssuesToView,
|
||||||
}) => {
|
}) => {
|
||||||
const calculateIssueCount = (column_id: string) => {
|
const calculateIssueCount = (column_id: string) => {
|
||||||
let issueCount = 0;
|
let issueCount = 0;
|
||||||
@ -60,6 +62,7 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -114,6 +117,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
|||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
enableQuickIssueCreate,
|
enableQuickIssueCreate,
|
||||||
isReadOnly,
|
isReadOnly,
|
||||||
|
addIssuesToView,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const calculateIssueCount = (column_id: string) => {
|
const calculateIssueCount = (column_id: string) => {
|
||||||
@ -142,6 +146,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
|||||||
kanBanToggle={kanBanToggle}
|
kanBanToggle={kanBanToggle}
|
||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full border-b border-custom-border-400 border-dashed" />
|
<div className="w-full border-b border-custom-border-400 border-dashed" />
|
||||||
@ -170,6 +175,7 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
|||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||||
isDragStarted={isDragStarted}
|
isDragStarted={isDragStarted}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -200,6 +206,7 @@ export interface IKanBanSwimLanes {
|
|||||||
isDragStarted?: boolean;
|
isDragStarted?: boolean;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore?: EProjectStore;
|
currentStore?: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
enableQuickIssueCreate: boolean;
|
enableQuickIssueCreate: boolean;
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
}
|
}
|
||||||
@ -228,6 +235,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
enableQuickIssueCreate,
|
enableQuickIssueCreate,
|
||||||
isReadOnly,
|
isReadOnly,
|
||||||
currentStore,
|
currentStore,
|
||||||
|
addIssuesToView,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -245,6 +253,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -260,6 +269,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -275,6 +285,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -289,6 +300,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
kanBanToggle={kanBanToggle}
|
kanBanToggle={kanBanToggle}
|
||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -304,6 +316,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -319,6 +332,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -334,6 +348,7 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
|||||||
handleKanBanToggle={handleKanBanToggle}
|
handleKanBanToggle={handleKanBanToggle}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -54,10 +54,20 @@ interface IBaseListRoot {
|
|||||||
getProjects: (projectStore: IProjectStore) => IProject[] | null;
|
getProjects: (projectStore: IProjectStore) => IProject[] | null;
|
||||||
viewId?: string;
|
viewId?: string;
|
||||||
currentStore: EProjectStore;
|
currentStore: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BaseListRoot = observer((props: IBaseListRoot) => {
|
export const BaseListRoot = observer((props: IBaseListRoot) => {
|
||||||
const { issueFilterStore, issueStore, QuickActions, issueActions, getProjects, viewId, currentStore } = props;
|
const {
|
||||||
|
issueFilterStore,
|
||||||
|
issueStore,
|
||||||
|
QuickActions,
|
||||||
|
issueActions,
|
||||||
|
getProjects,
|
||||||
|
viewId,
|
||||||
|
currentStore,
|
||||||
|
addIssuesToView,
|
||||||
|
} = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
project: projectStore,
|
project: projectStore,
|
||||||
@ -130,6 +140,7 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
|||||||
isReadonly={!enableInlineEditing}
|
isReadonly={!enableInlineEditing}
|
||||||
disableIssueCreation={!enableIssueCreation}
|
disableIssueCreation={!enableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -32,6 +32,7 @@ export interface IGroupByList {
|
|||||||
) => Promise<IIssue | undefined>;
|
) => Promise<IIssue | undefined>;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore: EProjectStore;
|
currentStore: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
viewId?: string;
|
viewId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,6 +55,7 @@ const GroupByList: React.FC<IGroupByList> = (props) => {
|
|||||||
viewId,
|
viewId,
|
||||||
disableIssueCreation,
|
disableIssueCreation,
|
||||||
currentStore,
|
currentStore,
|
||||||
|
addIssuesToView,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const prePopulateQuickAddData = (groupByKey: string | null, value: any) => {
|
const prePopulateQuickAddData = (groupByKey: string | null, value: any) => {
|
||||||
@ -91,6 +93,7 @@ const GroupByList: React.FC<IGroupByList> = (props) => {
|
|||||||
}
|
}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -147,6 +150,7 @@ export interface IList {
|
|||||||
viewId?: string;
|
viewId?: string;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore: EProjectStore;
|
currentStore: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const List: React.FC<IList> = (props) => {
|
export const List: React.FC<IList> = (props) => {
|
||||||
@ -170,6 +174,7 @@ export const List: React.FC<IList> = (props) => {
|
|||||||
members,
|
members,
|
||||||
projects,
|
projects,
|
||||||
currentStore,
|
currentStore,
|
||||||
|
addIssuesToView,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -193,6 +198,7 @@ export const List: React.FC<IList> = (props) => {
|
|||||||
viewId={viewId}
|
viewId={viewId}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -214,6 +220,7 @@ export const List: React.FC<IList> = (props) => {
|
|||||||
viewId={viewId}
|
viewId={viewId}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -235,6 +242,7 @@ export const List: React.FC<IList> = (props) => {
|
|||||||
viewId={viewId}
|
viewId={viewId}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -256,6 +264,7 @@ export const List: React.FC<IList> = (props) => {
|
|||||||
viewId={viewId}
|
viewId={viewId}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -277,6 +286,7 @@ export const List: React.FC<IList> = (props) => {
|
|||||||
viewId={viewId}
|
viewId={viewId}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -298,6 +308,7 @@ export const List: React.FC<IList> = (props) => {
|
|||||||
viewId={viewId}
|
viewId={viewId}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -319,6 +330,7 @@ export const List: React.FC<IList> = (props) => {
|
|||||||
viewId={viewId}
|
viewId={viewId}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -340,6 +352,7 @@ export const List: React.FC<IList> = (props) => {
|
|||||||
viewId={viewId}
|
viewId={viewId}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,6 +5,7 @@ import { HeaderGroupByCard } from "./group-by-card";
|
|||||||
// ui
|
// ui
|
||||||
import { Avatar } from "@plane/ui";
|
import { Avatar } from "@plane/ui";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface IAssigneesHeader {
|
export interface IAssigneesHeader {
|
||||||
column_id: string;
|
column_id: string;
|
||||||
@ -12,12 +13,13 @@ export interface IAssigneesHeader {
|
|||||||
issues_count: number;
|
issues_count: number;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore: EProjectStore;
|
currentStore: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Icon = ({ user }: any) => <Avatar name={user.display_name} src={user.avatar} size="md" />;
|
export const Icon = ({ user }: any) => <Avatar name={user.display_name} src={user.avatar} size="md" />;
|
||||||
|
|
||||||
export const AssigneesHeader: FC<IAssigneesHeader> = observer((props) => {
|
export const AssigneesHeader: FC<IAssigneesHeader> = observer((props) => {
|
||||||
const { column_value, issues_count, disableIssueCreation, currentStore } = props;
|
const { column_value, issues_count, disableIssueCreation, currentStore, addIssuesToView } = props;
|
||||||
|
|
||||||
const assignee = column_value ?? null;
|
const assignee = column_value ?? null;
|
||||||
|
|
||||||
@ -31,6 +33,7 @@ export const AssigneesHeader: FC<IAssigneesHeader> = observer((props) => {
|
|||||||
issuePayload={{ assignees: [assignee?.member?.id] }}
|
issuePayload={{ assignees: [assignee?.member?.id] }}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -4,6 +4,7 @@ import { observer } from "mobx-react-lite";
|
|||||||
import { HeaderGroupByCard } from "./group-by-card";
|
import { HeaderGroupByCard } from "./group-by-card";
|
||||||
import { Icon } from "./assignee";
|
import { Icon } from "./assignee";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface ICreatedByHeader {
|
export interface ICreatedByHeader {
|
||||||
column_id: string;
|
column_id: string;
|
||||||
@ -11,10 +12,11 @@ export interface ICreatedByHeader {
|
|||||||
issues_count: number;
|
issues_count: number;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore: EProjectStore;
|
currentStore: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
|
export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
|
||||||
const { column_value, issues_count, disableIssueCreation, currentStore } = props;
|
const { column_value, issues_count, disableIssueCreation, currentStore, addIssuesToView } = props;
|
||||||
|
|
||||||
const createdBy = column_value ?? null;
|
const createdBy = column_value ?? null;
|
||||||
|
|
||||||
@ -28,6 +30,7 @@ export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
|
|||||||
issuePayload={{ created_by: createdBy?.member?.id }}
|
issuePayload={{ created_by: createdBy?.member?.id }}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -2,6 +2,7 @@ import { observer } from "mobx-react-lite";
|
|||||||
// components
|
// components
|
||||||
import { HeaderGroupByCard } from "./group-by-card";
|
import { HeaderGroupByCard } from "./group-by-card";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface IEmptyHeader {
|
export interface IEmptyHeader {
|
||||||
column_id: string;
|
column_id: string;
|
||||||
@ -9,10 +10,11 @@ export interface IEmptyHeader {
|
|||||||
issues_count: number;
|
issues_count: number;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore: EProjectStore;
|
currentStore: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EmptyHeader: React.FC<IEmptyHeader> = observer((props) => {
|
export const EmptyHeader: React.FC<IEmptyHeader> = observer((props) => {
|
||||||
const { column_id, column_value, issues_count, disableIssueCreation, currentStore } = props;
|
const { column_id, column_value, issues_count, disableIssueCreation, currentStore, addIssuesToView } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HeaderGroupByCard
|
<HeaderGroupByCard
|
||||||
@ -21,6 +23,7 @@ export const EmptyHeader: React.FC<IEmptyHeader> = observer((props) => {
|
|||||||
issuePayload={{}}
|
issuePayload={{}}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -11,6 +11,7 @@ import { observer } from "mobx-react-lite";
|
|||||||
// types
|
// types
|
||||||
import { IIssue, ISearchIssueResponse } from "types";
|
import { IIssue, ISearchIssueResponse } from "types";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import useToast from "hooks/use-toast";
|
||||||
|
|
||||||
interface IHeaderGroupByCard {
|
interface IHeaderGroupByCard {
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
@ -19,10 +20,11 @@ interface IHeaderGroupByCard {
|
|||||||
issuePayload: Partial<IIssue>;
|
issuePayload: Partial<IIssue>;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore: EProjectStore;
|
currentStore: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HeaderGroupByCard = observer(
|
export const HeaderGroupByCard = observer(
|
||||||
({ icon, title, count, issuePayload, disableIssueCreation, currentStore }: IHeaderGroupByCard) => {
|
({ icon, title, count, issuePayload, disableIssueCreation, currentStore, addIssuesToView }: IHeaderGroupByCard) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, moduleId, cycleId } = router.query;
|
const { workspaceSlug, projectId, moduleId, cycleId } = router.query;
|
||||||
|
|
||||||
@ -30,43 +32,24 @@ export const HeaderGroupByCard = observer(
|
|||||||
|
|
||||||
const [openExistingIssueListModal, setOpenExistingIssueListModal] = React.useState(false);
|
const [openExistingIssueListModal, setOpenExistingIssueListModal] = React.useState(false);
|
||||||
|
|
||||||
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const renderExistingIssueModal = moduleId || cycleId;
|
const renderExistingIssueModal = moduleId || cycleId;
|
||||||
const ExistingIssuesListModalPayload = moduleId ? { module: true } : { cycle: true };
|
const ExistingIssuesListModalPayload = moduleId ? { module: true } : { cycle: true };
|
||||||
|
|
||||||
const handleAddIssuesToModule = async (data: ISearchIssueResponse[]) => {
|
const handleAddIssuesToView = async (data: ISearchIssueResponse[]) => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
|
|
||||||
const payload = {
|
const issues = data.map((i) => i.id);
|
||||||
issues: data.map((i) => i.id),
|
|
||||||
};
|
|
||||||
|
|
||||||
// await moduleService
|
addIssuesToView &&
|
||||||
// .addIssuesToModule(workspaceSlug as string, projectId as string, moduleId as string, payload, user)
|
addIssuesToView(issues)?.catch(() => {
|
||||||
// .catch(() =>
|
setToastAlert({
|
||||||
// setToastAlert({
|
type: "error",
|
||||||
// type: "error",
|
title: "Error!",
|
||||||
// title: "Error!",
|
message: "Selected issues could not be added to the cycle. Please try again.",
|
||||||
// message: "Selected issues could not be added to the module. Please try again.",
|
});
|
||||||
// })
|
});
|
||||||
// );
|
|
||||||
};
|
|
||||||
|
|
||||||
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.",
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -119,7 +102,7 @@ export const HeaderGroupByCard = observer(
|
|||||||
isOpen={openExistingIssueListModal}
|
isOpen={openExistingIssueListModal}
|
||||||
handleClose={() => setOpenExistingIssueListModal(false)}
|
handleClose={() => setOpenExistingIssueListModal(false)}
|
||||||
searchParams={ExistingIssuesListModalPayload}
|
searchParams={ExistingIssuesListModalPayload}
|
||||||
handleOnSubmit={moduleId ? handleAddIssuesToModule : handleAddIssuesToCycle}
|
handleOnSubmit={handleAddIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,6 +10,7 @@ import { CreatedByHeader } from "./created-by";
|
|||||||
// mobx
|
// mobx
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface IListGroupByHeaderRoot {
|
export interface IListGroupByHeaderRoot {
|
||||||
column_id: string;
|
column_id: string;
|
||||||
@ -18,10 +19,12 @@ export interface IListGroupByHeaderRoot {
|
|||||||
issues_count: number;
|
issues_count: number;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore: EProjectStore;
|
currentStore: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ListGroupByHeaderRoot: React.FC<IListGroupByHeaderRoot> = observer((props) => {
|
export const ListGroupByHeaderRoot: React.FC<IListGroupByHeaderRoot> = observer((props) => {
|
||||||
const { column_id, column_value, group_by, issues_count, disableIssueCreation, currentStore } = props;
|
const { column_id, column_value, group_by, issues_count, disableIssueCreation, currentStore, addIssuesToView } =
|
||||||
|
props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -32,6 +35,7 @@ export const ListGroupByHeaderRoot: React.FC<IListGroupByHeaderRoot> = observer(
|
|||||||
issues_count={issues_count}
|
issues_count={issues_count}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{group_by && group_by === "project" && (
|
{group_by && group_by === "project" && (
|
||||||
@ -41,6 +45,7 @@ export const ListGroupByHeaderRoot: React.FC<IListGroupByHeaderRoot> = observer(
|
|||||||
issues_count={issues_count}
|
issues_count={issues_count}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -51,6 +56,7 @@ export const ListGroupByHeaderRoot: React.FC<IListGroupByHeaderRoot> = observer(
|
|||||||
issues_count={issues_count}
|
issues_count={issues_count}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{group_by && group_by === "state_detail.group" && (
|
{group_by && group_by === "state_detail.group" && (
|
||||||
@ -60,6 +66,7 @@ export const ListGroupByHeaderRoot: React.FC<IListGroupByHeaderRoot> = observer(
|
|||||||
issues_count={issues_count}
|
issues_count={issues_count}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{group_by && group_by === "priority" && (
|
{group_by && group_by === "priority" && (
|
||||||
@ -69,6 +76,7 @@ export const ListGroupByHeaderRoot: React.FC<IListGroupByHeaderRoot> = observer(
|
|||||||
issues_count={issues_count}
|
issues_count={issues_count}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{group_by && group_by === "labels" && (
|
{group_by && group_by === "labels" && (
|
||||||
@ -78,6 +86,7 @@ export const ListGroupByHeaderRoot: React.FC<IListGroupByHeaderRoot> = observer(
|
|||||||
issues_count={issues_count}
|
issues_count={issues_count}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{group_by && group_by === "assignees" && (
|
{group_by && group_by === "assignees" && (
|
||||||
@ -87,6 +96,7 @@ export const ListGroupByHeaderRoot: React.FC<IListGroupByHeaderRoot> = observer(
|
|||||||
issues_count={issues_count}
|
issues_count={issues_count}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{group_by && group_by === "created_by" && (
|
{group_by && group_by === "created_by" && (
|
||||||
@ -96,6 +106,7 @@ export const ListGroupByHeaderRoot: React.FC<IListGroupByHeaderRoot> = observer(
|
|||||||
issues_count={issues_count}
|
issues_count={issues_count}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -3,6 +3,7 @@ import { observer } from "mobx-react-lite";
|
|||||||
// components
|
// components
|
||||||
import { HeaderGroupByCard } from "./group-by-card";
|
import { HeaderGroupByCard } from "./group-by-card";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface ILabelHeader {
|
export interface ILabelHeader {
|
||||||
column_id: string;
|
column_id: string;
|
||||||
@ -10,6 +11,7 @@ export interface ILabelHeader {
|
|||||||
issues_count: number;
|
issues_count: number;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore: EProjectStore;
|
currentStore: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Icon = ({ color }: any) => (
|
const Icon = ({ color }: any) => (
|
||||||
@ -17,7 +19,7 @@ const Icon = ({ color }: any) => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const LabelHeader: FC<ILabelHeader> = observer((props) => {
|
export const LabelHeader: FC<ILabelHeader> = observer((props) => {
|
||||||
const { column_value, issues_count, disableIssueCreation, currentStore } = props;
|
const { column_value, issues_count, disableIssueCreation, currentStore, addIssuesToView } = props;
|
||||||
|
|
||||||
const label = column_value ?? null;
|
const label = column_value ?? null;
|
||||||
|
|
||||||
@ -31,6 +33,7 @@ export const LabelHeader: FC<ILabelHeader> = observer((props) => {
|
|||||||
issuePayload={{ labels: [label.id] }}
|
issuePayload={{ labels: [label.id] }}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -4,6 +4,7 @@ import { AlertCircle, SignalHigh, SignalMedium, SignalLow, Ban } from "lucide-re
|
|||||||
// components
|
// components
|
||||||
import { HeaderGroupByCard } from "./group-by-card";
|
import { HeaderGroupByCard } from "./group-by-card";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface IPriorityHeader {
|
export interface IPriorityHeader {
|
||||||
column_id: string;
|
column_id: string;
|
||||||
@ -11,6 +12,7 @@ export interface IPriorityHeader {
|
|||||||
issues_count: number;
|
issues_count: number;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore: EProjectStore;
|
currentStore: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Icon = ({ priority }: any) => (
|
const Icon = ({ priority }: any) => (
|
||||||
@ -40,7 +42,7 @@ const Icon = ({ priority }: any) => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
|
export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
|
||||||
const { column_id, column_value, issues_count, disableIssueCreation, currentStore } = props;
|
const { column_id, column_value, issues_count, disableIssueCreation, currentStore, addIssuesToView } = props;
|
||||||
|
|
||||||
const priority = column_value ?? null;
|
const priority = column_value ?? null;
|
||||||
|
|
||||||
@ -54,6 +56,7 @@ export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
|
|||||||
issuePayload={{ priority: priority?.key }}
|
issuePayload={{ priority: priority?.key }}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -5,6 +5,7 @@ import { HeaderGroupByCard } from "./group-by-card";
|
|||||||
// emoji helper
|
// emoji helper
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
import { renderEmoji } from "helpers/emoji.helper";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface IProjectHeader {
|
export interface IProjectHeader {
|
||||||
column_id: string;
|
column_id: string;
|
||||||
@ -12,12 +13,13 @@ export interface IProjectHeader {
|
|||||||
issues_count: number;
|
issues_count: number;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore: EProjectStore;
|
currentStore: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Icon = ({ emoji }: any) => <div className="w-6 h-6">{renderEmoji(emoji)}</div>;
|
const Icon = ({ emoji }: any) => <div className="w-6 h-6">{renderEmoji(emoji)}</div>;
|
||||||
|
|
||||||
export const ProjectHeader: FC<IProjectHeader> = observer((props) => {
|
export const ProjectHeader: FC<IProjectHeader> = observer((props) => {
|
||||||
const { column_value, issues_count, disableIssueCreation, currentStore } = props;
|
const { column_value, issues_count, disableIssueCreation, currentStore, addIssuesToView } = props;
|
||||||
|
|
||||||
const project = column_value ?? null;
|
const project = column_value ?? null;
|
||||||
|
|
||||||
@ -31,6 +33,7 @@ export const ProjectHeader: FC<IProjectHeader> = observer((props) => {
|
|||||||
issuePayload={{ project: project?.id ?? "" }}
|
issuePayload={{ project: project?.id ?? "" }}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -7,6 +7,7 @@ import { StateGroupIcon } from "@plane/ui";
|
|||||||
// helpers
|
// helpers
|
||||||
import { capitalizeFirstLetter } from "helpers/string.helper";
|
import { capitalizeFirstLetter } from "helpers/string.helper";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface IStateGroupHeader {
|
export interface IStateGroupHeader {
|
||||||
column_id: string;
|
column_id: string;
|
||||||
@ -14,6 +15,7 @@ export interface IStateGroupHeader {
|
|||||||
issues_count: number;
|
issues_count: number;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore: EProjectStore;
|
currentStore: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) => (
|
export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) => (
|
||||||
@ -23,7 +25,7 @@ export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const StateGroupHeader: FC<IStateGroupHeader> = observer((props) => {
|
export const StateGroupHeader: FC<IStateGroupHeader> = observer((props) => {
|
||||||
const { column_value, issues_count, disableIssueCreation, currentStore } = props;
|
const { column_value, issues_count, disableIssueCreation, currentStore, addIssuesToView } = props;
|
||||||
|
|
||||||
const stateGroup = column_value ?? null;
|
const stateGroup = column_value ?? null;
|
||||||
|
|
||||||
@ -37,6 +39,7 @@ export const StateGroupHeader: FC<IStateGroupHeader> = observer((props) => {
|
|||||||
issuePayload={{}}
|
issuePayload={{}}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -4,6 +4,7 @@ import { observer } from "mobx-react-lite";
|
|||||||
import { HeaderGroupByCard } from "./group-by-card";
|
import { HeaderGroupByCard } from "./group-by-card";
|
||||||
import { Icon } from "./state-group";
|
import { Icon } from "./state-group";
|
||||||
import { EProjectStore } from "store/command-palette.store";
|
import { EProjectStore } from "store/command-palette.store";
|
||||||
|
import { IIssue } from "types";
|
||||||
|
|
||||||
export interface IStateHeader {
|
export interface IStateHeader {
|
||||||
column_id: string;
|
column_id: string;
|
||||||
@ -11,10 +12,11 @@ export interface IStateHeader {
|
|||||||
issues_count: number;
|
issues_count: number;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
currentStore: EProjectStore;
|
currentStore: EProjectStore;
|
||||||
|
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StateHeader: FC<IStateHeader> = observer((props) => {
|
export const StateHeader: FC<IStateHeader> = observer((props) => {
|
||||||
const { column_value, issues_count, disableIssueCreation, currentStore } = props;
|
const { column_value, issues_count, disableIssueCreation, currentStore, addIssuesToView } = props;
|
||||||
|
|
||||||
const state = column_value ?? null;
|
const state = column_value ?? null;
|
||||||
|
|
||||||
@ -28,6 +30,7 @@ export const StateHeader: FC<IStateHeader> = observer((props) => {
|
|||||||
issuePayload={{ state: state?.id }}
|
issuePayload={{ state: state?.id }}
|
||||||
disableIssueCreation={disableIssueCreation}
|
disableIssueCreation={disableIssueCreation}
|
||||||
currentStore={currentStore}
|
currentStore={currentStore}
|
||||||
|
addIssuesToView={addIssuesToView}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -49,6 +49,7 @@ export const CycleListLayout: React.FC = observer(() => {
|
|||||||
getProjects={getProjects}
|
getProjects={getProjects}
|
||||||
viewId={cycleId}
|
viewId={cycleId}
|
||||||
currentStore={EProjectStore.CYCLE}
|
currentStore={EProjectStore.CYCLE}
|
||||||
|
addIssuesToView={(issues: string[]) => cycleIssueStore.addIssueToCycle(workspaceSlug, cycleId, issues)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -50,6 +50,7 @@ export const ModuleListLayout: React.FC = observer(() => {
|
|||||||
getProjects={getProjects}
|
getProjects={getProjects}
|
||||||
viewId={moduleId}
|
viewId={moduleId}
|
||||||
currentStore={EProjectStore.MODULE}
|
currentStore={EProjectStore.MODULE}
|
||||||
|
addIssuesToView={(issues: string[]) => moduleIssueStore.addIssueToModule(workspaceSlug, moduleId, issues)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -220,13 +220,13 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
|||||||
const addIssueToCycle = async (issue: IIssue, cycleId: string) => {
|
const addIssueToCycle = async (issue: IIssue, cycleId: string) => {
|
||||||
if (!workspaceSlug || !activeProject) return;
|
if (!workspaceSlug || !activeProject) return;
|
||||||
|
|
||||||
cycleIssueStore.addIssueToCycle(workspaceSlug, activeProject, cycleId, issue);
|
cycleIssueStore.addIssueToCycle(workspaceSlug, cycleId, [issue.id]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addIssueToModule = async (issue: IIssue, moduleId: string) => {
|
const addIssueToModule = async (issue: IIssue, moduleId: string) => {
|
||||||
if (!workspaceSlug || !activeProject) return;
|
if (!workspaceSlug || !activeProject) return;
|
||||||
|
|
||||||
moduleIssueStore.addIssueToModule(workspaceSlug, activeProject, moduleId, issue);
|
moduleIssueStore.addIssueToModule(workspaceSlug, moduleId, [issue.id]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createIssue = async (payload: Partial<IIssue>) => {
|
const createIssue = async (payload: Partial<IIssue>) => {
|
||||||
@ -240,7 +240,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
|||||||
if (handleSubmit) {
|
if (handleSubmit) {
|
||||||
await handleSubmit(res);
|
await handleSubmit(res);
|
||||||
} else {
|
} else {
|
||||||
currentIssueStore.fetchIssues(workspaceSlug, dataIdToUpdate, "mutation");
|
currentIssueStore.fetchIssues(workspaceSlug, dataIdToUpdate, "mutation", viewId);
|
||||||
|
|
||||||
if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res, payload.cycle);
|
if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res, payload.cycle);
|
||||||
if (payload.module && payload.module !== "") await addIssueToModule(res, payload.module);
|
if (payload.module && payload.module !== "") await addIssueToModule(res, payload.module);
|
||||||
|
@ -2,7 +2,17 @@ import React, { useEffect, useRef, useState } from "react";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { Listbox, Transition } from "@headlessui/react";
|
import { Listbox, Transition } from "@headlessui/react";
|
||||||
import { Control, Controller, FieldArrayWithId, UseFieldArrayRemove, useFieldArray, useForm } from "react-hook-form";
|
import {
|
||||||
|
Control,
|
||||||
|
Controller,
|
||||||
|
FieldArrayWithId,
|
||||||
|
UseFieldArrayRemove,
|
||||||
|
UseFormGetValues,
|
||||||
|
UseFormSetValue,
|
||||||
|
UseFormWatch,
|
||||||
|
useFieldArray,
|
||||||
|
useForm,
|
||||||
|
} from "react-hook-form";
|
||||||
import { Check, ChevronDown, Plus, XCircle } from "lucide-react";
|
import { Check, ChevronDown, Plus, XCircle } from "lucide-react";
|
||||||
// services
|
// services
|
||||||
import { WorkspaceService } from "services/workspace.service";
|
import { WorkspaceService } from "services/workspace.service";
|
||||||
@ -34,6 +44,7 @@ type Props = {
|
|||||||
type EmailRole = {
|
type EmailRole = {
|
||||||
email: string;
|
email: string;
|
||||||
role: TUserWorkspaceRole;
|
role: TUserWorkspaceRole;
|
||||||
|
role_active: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FormValues = {
|
type FormValues = {
|
||||||
@ -44,6 +55,9 @@ type InviteMemberFormProps = {
|
|||||||
index: number;
|
index: number;
|
||||||
remove: UseFieldArrayRemove;
|
remove: UseFieldArrayRemove;
|
||||||
control: Control<FormValues, any>;
|
control: Control<FormValues, any>;
|
||||||
|
setValue: UseFormSetValue<FormValues>;
|
||||||
|
getValues: UseFormGetValues<FormValues>;
|
||||||
|
watch: UseFormWatch<FormValues>;
|
||||||
field: FieldArrayWithId<FormValues, "emails", "id">;
|
field: FieldArrayWithId<FormValues, "emails", "id">;
|
||||||
fields: FieldArrayWithId<FormValues, "emails", "id">[];
|
fields: FieldArrayWithId<FormValues, "emails", "id">[];
|
||||||
errors: any;
|
errors: any;
|
||||||
@ -53,9 +67,33 @@ type InviteMemberFormProps = {
|
|||||||
|
|
||||||
// services
|
// services
|
||||||
const workspaceService = new WorkspaceService();
|
const workspaceService = new WorkspaceService();
|
||||||
|
const emailRegex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
|
||||||
|
|
||||||
|
const placeholderEmails = [
|
||||||
|
"charlie.taylor@frstflit.com",
|
||||||
|
"octave.chanute@frstflit.com",
|
||||||
|
"george.spratt@frstflit.com",
|
||||||
|
"frank.coffyn@frstflit.com",
|
||||||
|
"amos.root@frstflit.com",
|
||||||
|
"edward.deeds@frstflit.com",
|
||||||
|
"charles.m.manly@frstflit.com",
|
||||||
|
"glenn.curtiss@frstflit.com",
|
||||||
|
"thomas.selfridge@frstflit.com",
|
||||||
|
"albert.zahm@frstflit.com",
|
||||||
|
];
|
||||||
const InviteMemberForm: React.FC<InviteMemberFormProps> = (props) => {
|
const InviteMemberForm: React.FC<InviteMemberFormProps> = (props) => {
|
||||||
const { control, index, fields, remove, errors, isInvitationDisabled, setIsInvitationDisabled } = props;
|
const {
|
||||||
|
control,
|
||||||
|
index,
|
||||||
|
fields,
|
||||||
|
remove,
|
||||||
|
errors,
|
||||||
|
isInvitationDisabled,
|
||||||
|
setIsInvitationDisabled,
|
||||||
|
setValue,
|
||||||
|
getValues,
|
||||||
|
watch,
|
||||||
|
} = props;
|
||||||
|
|
||||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
@ -64,7 +102,34 @@ const InviteMemberForm: React.FC<InviteMemberFormProps> = (props) => {
|
|||||||
|
|
||||||
useDynamicDropdownPosition(isDropdownOpen, () => setIsDropdownOpen(false), buttonRef, dropdownRef);
|
useDynamicDropdownPosition(isDropdownOpen, () => setIsDropdownOpen(false), buttonRef, dropdownRef);
|
||||||
|
|
||||||
|
const email = watch(`emails.${index}.email`);
|
||||||
|
|
||||||
|
const emailOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (event.target.value === "") {
|
||||||
|
const validEmail = fields.map((_, i) => emailRegex.test(getValues(`emails.${i}.email`))).includes(true);
|
||||||
|
if (validEmail) {
|
||||||
|
setIsInvitationDisabled(false);
|
||||||
|
} else {
|
||||||
|
setIsInvitationDisabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getValues(`emails.${index}.role_active`)) {
|
||||||
|
setValue(`emails.${index}.role_active`, false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!getValues(`emails.${index}.role_active`)) {
|
||||||
|
setValue(`emails.${index}.role_active`, true);
|
||||||
|
}
|
||||||
|
if (isInvitationDisabled && emailRegex.test(event.target.value)) {
|
||||||
|
setIsInvitationDisabled(false);
|
||||||
|
} else if (!isInvitationDisabled && !emailRegex.test(event.target.value)) {
|
||||||
|
setIsInvitationDisabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div>
|
||||||
<div className="group relative grid grid-cols-11 gap-4">
|
<div className="group relative grid grid-cols-11 gap-4">
|
||||||
<div className="col-span-7 bg-onboarding-background-200 rounded-md">
|
<div className="col-span-7 bg-onboarding-background-200 rounded-md">
|
||||||
<Controller
|
<Controller
|
||||||
@ -72,7 +137,7 @@ const InviteMemberForm: React.FC<InviteMemberFormProps> = (props) => {
|
|||||||
name={`emails.${index}.email`}
|
name={`emails.${index}.email`}
|
||||||
rules={{
|
rules={{
|
||||||
pattern: {
|
pattern: {
|
||||||
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
value: emailRegex,
|
||||||
message: "Invalid Email ID",
|
message: "Invalid Email ID",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
@ -83,34 +148,12 @@ const InviteMemberForm: React.FC<InviteMemberFormProps> = (props) => {
|
|||||||
type="text"
|
type="text"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (event.target.value === "") {
|
emailOnChange(event);
|
||||||
const validEmail = !fields
|
|
||||||
.filter((ele) => {
|
|
||||||
ele.id !== `emails.${index}.email`;
|
|
||||||
})
|
|
||||||
.map((ele) => ele.email)
|
|
||||||
.includes("");
|
|
||||||
if (validEmail) {
|
|
||||||
setIsInvitationDisabled(false);
|
|
||||||
} else {
|
|
||||||
setIsInvitationDisabled(true);
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
isInvitationDisabled &&
|
|
||||||
/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(event.target.value)
|
|
||||||
) {
|
|
||||||
setIsInvitationDisabled(false);
|
|
||||||
} else if (
|
|
||||||
!isInvitationDisabled &&
|
|
||||||
!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(event.target.value)
|
|
||||||
) {
|
|
||||||
setIsInvitationDisabled(true);
|
|
||||||
}
|
|
||||||
onChange(event);
|
onChange(event);
|
||||||
}}
|
}}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
hasError={Boolean(errors.emails?.[index]?.email)}
|
hasError={Boolean(errors.emails?.[index]?.email)}
|
||||||
placeholder="Enter their email..."
|
placeholder={placeholderEmails[index % placeholderEmails.length]}
|
||||||
className="text-xs sm:text-sm w-full h-12 placeholder:text-onboarding-text-400 border-onboarding-border-100"
|
className="text-xs sm:text-sm w-full h-12 placeholder:text-onboarding-text-400 border-onboarding-border-100"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -128,6 +171,7 @@ const InviteMemberForm: React.FC<InviteMemberFormProps> = (props) => {
|
|||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
onChange(val);
|
onChange(val);
|
||||||
setIsDropdownOpen(false);
|
setIsDropdownOpen(false);
|
||||||
|
setValue(`emails.${index}.role_active`, true);
|
||||||
}}
|
}}
|
||||||
className="flex-shrink-0 text-left w-full"
|
className="flex-shrink-0 text-left w-full"
|
||||||
>
|
>
|
||||||
@ -137,9 +181,23 @@ const InviteMemberForm: React.FC<InviteMemberFormProps> = (props) => {
|
|||||||
onClick={() => setIsDropdownOpen((prev) => !prev)}
|
onClick={() => setIsDropdownOpen((prev) => !prev)}
|
||||||
className="flex items-center px-2.5 h-11 py-2 text-xs justify-between gap-1 w-full rounded-md duration-300"
|
className="flex items-center px-2.5 h-11 py-2 text-xs justify-between gap-1 w-full rounded-md duration-300"
|
||||||
>
|
>
|
||||||
<span className="text-xs text-onboarding-text-400 sm:text-sm">{ROLE[value]}</span>
|
<span
|
||||||
|
className={`text-xs ${
|
||||||
|
!getValues(`emails.${index}.role_active`)
|
||||||
|
? "text-onboarding-text-400"
|
||||||
|
: "text-onboarding-text-100"
|
||||||
|
} sm:text-sm`}
|
||||||
|
>
|
||||||
|
{ROLE[value]}
|
||||||
|
</span>
|
||||||
|
|
||||||
<ChevronDown className="h-4 w-4 stroke-onboarding-text-400" />
|
<ChevronDown
|
||||||
|
className={`h-4 w-4 ${
|
||||||
|
!getValues(`emails.${index}.role_active`)
|
||||||
|
? "stroke-onboarding-text-400"
|
||||||
|
: "stroke-onboarding-text-100"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
</Listbox.Button>
|
</Listbox.Button>
|
||||||
|
|
||||||
<Transition
|
<Transition
|
||||||
@ -162,7 +220,8 @@ const InviteMemberForm: React.FC<InviteMemberFormProps> = (props) => {
|
|||||||
key={key}
|
key={key}
|
||||||
value={parseInt(key)}
|
value={parseInt(key)}
|
||||||
className={({ active, selected }) =>
|
className={({ active, selected }) =>
|
||||||
`cursor-pointer select-none truncate rounded px-1 py-1.5 ${active || selected ? "bg-onboarding-background-400/40" : ""
|
`cursor-pointer select-none truncate rounded px-1 py-1.5 ${
|
||||||
|
active || selected ? "bg-onboarding-background-400/40" : ""
|
||||||
} ${selected ? "text-onboarding-text-100" : "text-custom-text-200"}`
|
} ${selected ? "text-onboarding-text-100" : "text-custom-text-200"}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -191,6 +250,13 @@ const InviteMemberForm: React.FC<InviteMemberFormProps> = (props) => {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{email && !emailRegex.test(email) && (
|
||||||
|
<div className="">
|
||||||
|
<span className="text-sm">🤥</span>{" "}
|
||||||
|
<span className="text-xs text-red-500 mt-1">That doesn{"'"}t look like an email address.</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -204,6 +270,9 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
|
watch,
|
||||||
|
getValues,
|
||||||
|
setValue,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { isSubmitting, errors, isValid },
|
formState: { isSubmitting, errors, isValid },
|
||||||
} = useForm<FormValues>();
|
} = useForm<FormValues>();
|
||||||
@ -229,7 +298,12 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
|||||||
payload = { emails: payload.emails.filter((email) => email.email !== "") };
|
payload = { emails: payload.emails.filter((email) => email.email !== "") };
|
||||||
|
|
||||||
await workspaceService
|
await workspaceService
|
||||||
.inviteWorkspace(workspace.slug, payload)
|
.inviteWorkspace(workspace.slug, {
|
||||||
|
emails: payload.emails.map((email) => ({
|
||||||
|
email: email.email,
|
||||||
|
role: email.role,
|
||||||
|
})),
|
||||||
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
@ -249,16 +323,16 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const appendField = () => {
|
const appendField = () => {
|
||||||
append({ email: "", role: 15 });
|
append({ email: "", role: 15, role_active: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (fields.length === 0) {
|
if (fields.length === 0) {
|
||||||
append(
|
append(
|
||||||
[
|
[
|
||||||
{ email: "", role: 15 },
|
{ email: "", role: 15, role_active: false },
|
||||||
{ email: "", role: 15 },
|
{ email: "", role: 15, role_active: false },
|
||||||
{ email: "", role: 15 },
|
{ email: "", role: 15, role_active: false },
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
focusIndex: 0,
|
focusIndex: 0,
|
||||||
@ -325,6 +399,9 @@ export const InviteMembers: React.FC<Props> = (props) => {
|
|||||||
<div className="space-y-3 sm:space-y-4 mb-3">
|
<div className="space-y-3 sm:space-y-4 mb-3">
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
<InviteMemberForm
|
<InviteMemberForm
|
||||||
|
watch={watch}
|
||||||
|
getValues={getValues}
|
||||||
|
setValue={setValue}
|
||||||
isInvitationDisabled={isInvitationDisabled}
|
isInvitationDisabled={isInvitationDisabled}
|
||||||
setIsInvitationDisabled={(value: boolean) => setIsInvitationDisabled(value)}
|
setIsInvitationDisabled={(value: boolean) => setIsInvitationDisabled(value)}
|
||||||
control={control}
|
control={control}
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
import React, { ReactElement } from "react";
|
import React from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// layouts
|
|
||||||
import { AppLayout } from "layouts/app-layout";
|
|
||||||
import { ProfileAuthWrapper } from "layouts/user-profile-layout";
|
|
||||||
// components
|
// components
|
||||||
import { UserProfileHeader } from "components/headers";
|
|
||||||
import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root";
|
import { ProfileIssuesListLayout } from "components/issues/issue-layouts/list/roots/profile-issues-root";
|
||||||
import { ProfileIssuesKanBanLayout } from "components/issues/issue-layouts/kanban/roots/profile-issues-root";
|
import { ProfileIssuesKanBanLayout } from "components/issues/issue-layouts/kanban/roots/profile-issues-root";
|
||||||
import { ProfileIssuesAppliedFiltersRoot } from "components/issues";
|
import { ProfileIssuesAppliedFiltersRoot } from "components/issues";
|
||||||
@ -31,12 +27,15 @@ export const ProfileIssuesPage = observer((props: IProfileIssuesPage) => {
|
|||||||
workspaceProfileIssuesFilter: { issueFilters, fetchFilters },
|
workspaceProfileIssuesFilter: { issueFilters, fetchFilters },
|
||||||
}: RootStore = useMobxStore();
|
}: RootStore = useMobxStore();
|
||||||
|
|
||||||
useSWR(workspaceSlug && userId ? `CURRENT_WORKSPACE_PROFILE_ISSUES_${workspaceSlug}_${userId}` : null, async () => {
|
useSWR(
|
||||||
|
workspaceSlug && userId ? `CURRENT_WORKSPACE_PROFILE_ISSUES_${workspaceSlug}_${userId}_${props.type}` : null,
|
||||||
|
async () => {
|
||||||
if (workspaceSlug && userId) {
|
if (workspaceSlug && userId) {
|
||||||
await fetchFilters(workspaceSlug);
|
await fetchFilters(workspaceSlug);
|
||||||
await fetchIssues(workspaceSlug, userId, getIssues ? "mutation" : "init-loader", props.type);
|
await fetchIssues(workspaceSlug, userId, getIssues ? "mutation" : "init-loader", props.type);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const activeLayout = issueFilters?.displayFilters?.layout || undefined;
|
const activeLayout = issueFilters?.displayFilters?.layout || undefined;
|
||||||
|
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
import { Plus, X } from "lucide-react";
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// ui
|
// ui
|
||||||
import { Button, CustomSelect, Input } from "@plane/ui";
|
import { Button, CustomSelect, Input } from "@plane/ui";
|
||||||
// icons
|
|
||||||
import { Plus, X } from "lucide-react";
|
|
||||||
// types
|
// types
|
||||||
import { IWorkspaceBulkInviteFormData, TUserWorkspaceRole } from "types";
|
import { IWorkspaceBulkInviteFormData, TUserWorkspaceRole } from "types";
|
||||||
// constants
|
// constants
|
||||||
@ -34,9 +36,12 @@ const defaultValues: FormValues = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SendWorkspaceInvitationModal: React.FC<Props> = (props) => {
|
export const SendWorkspaceInvitationModal: React.FC<Props> = observer((props) => {
|
||||||
const { isOpen, onClose, onSubmit } = props;
|
const { isOpen, onClose, onSubmit } = props;
|
||||||
|
// mobx store
|
||||||
|
const {
|
||||||
|
user: { currentWorkspaceRole },
|
||||||
|
} = useMobxStore();
|
||||||
// form info
|
// form info
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
@ -59,30 +64,6 @@ export const SendWorkspaceInvitationModal: React.FC<Props> = (props) => {
|
|||||||
}, 350);
|
}, 350);
|
||||||
};
|
};
|
||||||
|
|
||||||
// const onSubmit = async (formData: FormValues) => {
|
|
||||||
// if (!workspaceSlug) return;
|
|
||||||
|
|
||||||
// return workspaceService
|
|
||||||
// .inviteWorkspace(workspaceSlug, formData, user)
|
|
||||||
|
|
||||||
// .then(async () => {
|
|
||||||
// if (onSuccess) await onSuccess();
|
|
||||||
// handleClose();
|
|
||||||
// setToastAlert({
|
|
||||||
// type: "success",
|
|
||||||
// title: "Success!",
|
|
||||||
// message: "Invitations sent successfully.",
|
|
||||||
// });
|
|
||||||
// })
|
|
||||||
// .catch((err) =>
|
|
||||||
// setToastAlert({
|
|
||||||
// type: "error",
|
|
||||||
// title: "Error!",
|
|
||||||
// message: `${err.error ?? "Something went wrong. Please try again."}`,
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
|
||||||
const appendField = () => {
|
const appendField = () => {
|
||||||
append({ email: "", role: 15 });
|
append({ email: "", role: 15 });
|
||||||
};
|
};
|
||||||
@ -181,11 +162,14 @@ export const SendWorkspaceInvitationModal: React.FC<Props> = (props) => {
|
|||||||
width="w-full"
|
width="w-full"
|
||||||
input
|
input
|
||||||
>
|
>
|
||||||
{Object.entries(ROLE).map(([key, value]) => (
|
{Object.entries(ROLE).map(([key, value]) => {
|
||||||
|
if (currentWorkspaceRole && currentWorkspaceRole >= parseInt(key))
|
||||||
|
return (
|
||||||
<CustomSelect.Option key={key} value={parseInt(key)}>
|
<CustomSelect.Option key={key} value={parseInt(key)}>
|
||||||
{value}
|
{value}
|
||||||
</CustomSelect.Option>
|
</CustomSelect.Option>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</CustomSelect>
|
</CustomSelect>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -230,4 +214,4 @@ export const SendWorkspaceInvitationModal: React.FC<Props> = (props) => {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</Transition.Root>
|
</Transition.Root>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { useState, FC } from "react";
|
import { useState, FC } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
|
import { ChevronDown, Dot, XCircle } from "lucide-react";
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// hooks
|
// hooks
|
||||||
@ -10,12 +12,10 @@ import useToast from "hooks/use-toast";
|
|||||||
import { ConfirmWorkspaceMemberRemove } from "components/workspace";
|
import { ConfirmWorkspaceMemberRemove } from "components/workspace";
|
||||||
// ui
|
// ui
|
||||||
import { CustomSelect, Tooltip } from "@plane/ui";
|
import { CustomSelect, Tooltip } from "@plane/ui";
|
||||||
// icons
|
|
||||||
import { ChevronDown, Dot, XCircle } from "lucide-react";
|
|
||||||
// types
|
// types
|
||||||
import { TUserWorkspaceRole } from "types";
|
import { TUserWorkspaceRole } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { ROLE } from "constants/workspace";
|
import { EUserWorkspaceRoles, ROLE } from "constants/workspace";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
member: {
|
member: {
|
||||||
@ -33,7 +33,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WorkspaceMembersListItem: FC<Props> = (props) => {
|
export const WorkspaceMembersListItem: FC<Props> = observer((props) => {
|
||||||
const { member } = props;
|
const { member } = props;
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -43,7 +43,6 @@ export const WorkspaceMembersListItem: FC<Props> = (props) => {
|
|||||||
workspaceMember: { removeMember, updateMember, deleteWorkspaceInvitation },
|
workspaceMember: { removeMember, updateMember, deleteWorkspaceInvitation },
|
||||||
user: { currentWorkspaceMemberInfo, currentWorkspaceRole, currentUser, currentUserSettings, leaveWorkspace },
|
user: { currentWorkspaceMemberInfo, currentWorkspaceRole, currentUser, currentUserSettings, leaveWorkspace },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
const isAdmin = currentWorkspaceRole === 20;
|
|
||||||
// states
|
// states
|
||||||
const [removeMemberModal, setRemoveMemberModal] = useState(false);
|
const [removeMemberModal, setRemoveMemberModal] = useState(false);
|
||||||
// hooks
|
// hooks
|
||||||
@ -53,10 +52,7 @@ export const WorkspaceMembersListItem: FC<Props> = (props) => {
|
|||||||
if (!workspaceSlug || !currentUserSettings) return;
|
if (!workspaceSlug || !currentUserSettings) return;
|
||||||
|
|
||||||
await leaveWorkspace(workspaceSlug.toString())
|
await leaveWorkspace(workspaceSlug.toString())
|
||||||
.then(() => {
|
.then(() => router.push("/profile"))
|
||||||
if (currentUserSettings.workspace?.invites > 0) router.push("/invitations");
|
|
||||||
else router.push("/create-workspace");
|
|
||||||
})
|
|
||||||
.catch((err) =>
|
.catch((err) =>
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
@ -114,6 +110,20 @@ export const WorkspaceMembersListItem: FC<Props> = (props) => {
|
|||||||
} else await handleRemoveInvitation();
|
} else await handleRemoveInvitation();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// is the member current logged in user
|
||||||
|
const isCurrentUser = member.memberId === currentWorkspaceMemberInfo?.member;
|
||||||
|
// is the current logged in user admin
|
||||||
|
const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
|
||||||
|
// role change access-
|
||||||
|
// 1. user cannot change their own role
|
||||||
|
// 2. only admin or member can change role
|
||||||
|
// 3. user cannot change role of higher role
|
||||||
|
const hasRoleChangeAccess =
|
||||||
|
currentWorkspaceRole &&
|
||||||
|
!isCurrentUser &&
|
||||||
|
[EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentWorkspaceRole) &&
|
||||||
|
member.role <= currentWorkspaceRole;
|
||||||
|
|
||||||
if (!currentWorkspaceMemberInfo) return null;
|
if (!currentWorkspaceMemberInfo) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -180,12 +190,12 @@ export const WorkspaceMembersListItem: FC<Props> = (props) => {
|
|||||||
<div className="flex item-center gap-1 px-2 py-0.5 rounded">
|
<div className="flex item-center gap-1 px-2 py-0.5 rounded">
|
||||||
<span
|
<span
|
||||||
className={`flex items-center text-xs font-medium rounded ${
|
className={`flex items-center text-xs font-medium rounded ${
|
||||||
member.memberId !== currentWorkspaceMemberInfo.member ? "" : "text-custom-sidebar-text-400"
|
hasRoleChangeAccess ? "" : "text-custom-sidebar-text-400"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{ROLE[member.role as keyof typeof ROLE]}
|
{ROLE[member.role as keyof typeof ROLE]}
|
||||||
</span>
|
</span>
|
||||||
{member.memberId !== currentWorkspaceMemberInfo.member && (
|
{hasRoleChangeAccess && (
|
||||||
<span className="grid place-items-center">
|
<span className="grid place-items-center">
|
||||||
<ChevronDown className="h-3 w-3" />
|
<ChevronDown className="h-3 w-3" />
|
||||||
</span>
|
</span>
|
||||||
@ -206,11 +216,7 @@ export const WorkspaceMembersListItem: FC<Props> = (props) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
disabled={
|
disabled={!hasRoleChangeAccess}
|
||||||
member.memberId === currentWorkspaceMemberInfo.member ||
|
|
||||||
!member.status ||
|
|
||||||
Boolean(currentWorkspaceRole && currentWorkspaceRole !== 20 && currentWorkspaceRole < member.role)
|
|
||||||
}
|
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
>
|
>
|
||||||
{Object.keys(ROLE).map((key) => {
|
{Object.keys(ROLE).map((key) => {
|
||||||
@ -224,23 +230,24 @@ export const WorkspaceMembersListItem: FC<Props> = (props) => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</CustomSelect>
|
</CustomSelect>
|
||||||
{isAdmin && (
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
tooltipContent={
|
tooltipContent={isCurrentUser ? "Leave workspace" : "Remove member"}
|
||||||
member.memberId === currentWorkspaceMemberInfo.member ? "Leave workspace" : "Remove member"
|
disabled={!isAdmin && !isCurrentUser}
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setRemoveMemberModal(true)}
|
onClick={() => setRemoveMemberModal(true)}
|
||||||
className="opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto"
|
className={
|
||||||
|
isAdmin || isCurrentUser
|
||||||
|
? "opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto"
|
||||||
|
: "opacity-0 pointer-events-none"
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<XCircle className="h-3.5 w-3.5 text-custom-text-400" strokeWidth={2} />
|
<XCircle className="h-3.5 w-3.5 text-custom-text-400" strokeWidth={2} />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -9,18 +9,14 @@ import { WorkspaceMembersListItem } from "components/workspace";
|
|||||||
// ui
|
// ui
|
||||||
import { Loader } from "@plane/ui";
|
import { Loader } from "@plane/ui";
|
||||||
|
|
||||||
export const WorkspaceMembersList: FC<{ searchQuery: string }> = observer(({ searchQuery }) => {
|
export const WorkspaceMembersList: FC<{ searchQuery: string }> = observer((props) => {
|
||||||
|
const { searchQuery } = props;
|
||||||
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
// store
|
// store
|
||||||
const {
|
const {
|
||||||
workspaceMember: {
|
workspaceMember: { workspaceMembersWithInvitations, fetchWorkspaceMemberInvitations },
|
||||||
workspaceMembers,
|
|
||||||
workspaceMembersWithInvitations,
|
|
||||||
workspaceMemberInvitations,
|
|
||||||
fetchWorkspaceMemberInvitations,
|
|
||||||
},
|
|
||||||
user: { currentWorkspaceMemberInfo },
|
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
// fetching workspace invitations
|
// fetching workspace invitations
|
||||||
useSWR(
|
useSWR(
|
||||||
@ -36,12 +32,7 @@ export const WorkspaceMembersList: FC<{ searchQuery: string }> = observer(({ sea
|
|||||||
return `${email}${displayName}${fullName}`.includes(searchQuery.toLowerCase());
|
return `${email}${displayName}${fullName}`.includes(searchQuery.toLowerCase());
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (!workspaceMembersWithInvitations)
|
||||||
!workspaceMembers ||
|
|
||||||
!workspaceMemberInvitations ||
|
|
||||||
!workspaceMembersWithInvitations ||
|
|
||||||
!currentWorkspaceMemberInfo
|
|
||||||
)
|
|
||||||
return (
|
return (
|
||||||
<Loader className="space-y-5">
|
<Loader className="space-y-5">
|
||||||
<Loader.Item height="40px" />
|
<Loader.Item height="40px" />
|
||||||
|
@ -14,12 +14,12 @@ import { DeleteWorkspaceModal } from "components/workspace";
|
|||||||
import { WorkspaceImageUploadModal } from "components/core";
|
import { WorkspaceImageUploadModal } from "components/core";
|
||||||
// ui
|
// ui
|
||||||
import { Button, CustomSelect, Input, Spinner } from "@plane/ui";
|
import { Button, CustomSelect, Input, Spinner } from "@plane/ui";
|
||||||
|
// helpers
|
||||||
|
import { copyUrlToClipboard } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IWorkspace } from "types";
|
import { IWorkspace } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { ORGANIZATION_SIZE } from "constants/workspace";
|
import { EUserWorkspaceRoles, ORGANIZATION_SIZE } from "constants/workspace";
|
||||||
import { trackEvent } from "helpers/event-tracker.helper";
|
|
||||||
import { copyUrlToClipboard } from "helpers/string.helper";
|
|
||||||
|
|
||||||
const defaultValues: Partial<IWorkspace> = {
|
const defaultValues: Partial<IWorkspace> = {
|
||||||
name: "",
|
name: "",
|
||||||
@ -40,9 +40,9 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||||||
const {
|
const {
|
||||||
workspace: { currentWorkspace, updateWorkspace },
|
workspace: { currentWorkspace, updateWorkspace },
|
||||||
user: { currentWorkspaceRole },
|
user: { currentWorkspaceRole },
|
||||||
trackEvent: { postHogEventTracker }
|
trackEvent: { postHogEventTracker },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
const isAdmin = currentWorkspaceRole === 20;
|
|
||||||
// hooks
|
// hooks
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
// form info
|
// form info
|
||||||
@ -67,28 +67,22 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||||||
|
|
||||||
await updateWorkspace(currentWorkspace.slug, payload)
|
await updateWorkspace(currentWorkspace.slug, payload)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
postHogEventTracker(
|
postHogEventTracker("WORKSPACE_UPDATE", {
|
||||||
'WORKSPACE_UPDATE',
|
|
||||||
{
|
|
||||||
...res,
|
...res,
|
||||||
state: "SUCCESS"
|
state: "SUCCESS",
|
||||||
}
|
});
|
||||||
)
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
type: "success",
|
type: "success",
|
||||||
message: "Workspace updated successfully",
|
message: "Workspace updated successfully",
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
})
|
||||||
postHogEventTracker(
|
.catch((err) => {
|
||||||
'WORKSPACE_UPDATE',
|
postHogEventTracker("WORKSPACE_UPDATE", {
|
||||||
{
|
state: "FAILED",
|
||||||
state: "FAILED"
|
});
|
||||||
}
|
console.error(err);
|
||||||
);
|
});
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveLogo = () => {
|
const handleRemoveLogo = () => {
|
||||||
@ -136,6 +130,8 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||||||
if (currentWorkspace) reset({ ...currentWorkspace });
|
if (currentWorkspace) reset({ ...currentWorkspace });
|
||||||
}, [currentWorkspace, reset]);
|
}, [currentWorkspace, reset]);
|
||||||
|
|
||||||
|
const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
|
||||||
|
|
||||||
if (!currentWorkspace)
|
if (!currentWorkspace)
|
||||||
return (
|
return (
|
||||||
<div className="grid place-items-center h-full w-full px-4 sm:px-0">
|
<div className="grid place-items-center h-full w-full px-4 sm:px-0">
|
||||||
@ -192,11 +188,10 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||||||
<button type="button" onClick={handleCopyUrl} className="text-sm tracking-tight">{`${
|
<button type="button" onClick={handleCopyUrl} className="text-sm tracking-tight">{`${
|
||||||
typeof window !== "undefined" && window.location.origin.replace("http://", "").replace("https://", "")
|
typeof window !== "undefined" && window.location.origin.replace("http://", "").replace("https://", "")
|
||||||
}/${currentWorkspace.slug}`}</button>
|
}/${currentWorkspace.slug}`}</button>
|
||||||
<div className="flex item-center gap-2.5">
|
{isAdmin && (
|
||||||
<button
|
<button
|
||||||
className="flex items-center gap-1.5 text-xs text-left text-custom-primary-100 font-medium"
|
className="flex items-center gap-1.5 text-xs text-left text-custom-primary-100 font-medium"
|
||||||
onClick={() => setIsImageUploadModalOpen(true)}
|
onClick={() => setIsImageUploadModalOpen(true)}
|
||||||
disabled={!isAdmin}
|
|
||||||
>
|
>
|
||||||
{watch("logo") && watch("logo") !== null && watch("logo") !== "" ? (
|
{watch("logo") && watch("logo") !== null && watch("logo") !== "" ? (
|
||||||
<>
|
<>
|
||||||
@ -207,14 +202,14 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||||||
"Upload logo"
|
"Upload logo"
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-8 my-10">
|
<div className="flex flex-col gap-8 my-10">
|
||||||
<div className="grid grid-col grid-cols-1 xl:grid-cols-2 2xl:grid-cols-3 items-center justify-between gap-10 w-full">
|
<div className="grid grid-col grid-cols-1 xl:grid-cols-2 2xl:grid-cols-3 items-center justify-between gap-10 w-full">
|
||||||
<div className="flex flex-col gap-1 ">
|
<div className="flex flex-col gap-1">
|
||||||
<h4 className="text-sm">Workspace Name</h4>
|
<h4 className="text-sm">Workspace name</h4>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="name"
|
name="name"
|
||||||
@ -243,7 +238,7 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-1 ">
|
<div className="flex flex-col gap-1 ">
|
||||||
<h4 className="text-sm">Company Size</h4>
|
<h4 className="text-sm">Company size</h4>
|
||||||
<Controller
|
<Controller
|
||||||
name="organization_size"
|
name="organization_size"
|
||||||
control={control}
|
control={control}
|
||||||
@ -277,7 +272,8 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||||||
id="url"
|
id="url"
|
||||||
name="url"
|
name="url"
|
||||||
type="url"
|
type="url"
|
||||||
value={`${typeof window !== "undefined" &&
|
value={`${
|
||||||
|
typeof window !== "undefined" &&
|
||||||
window.location.origin.replace("http://", "").replace("https://", "")
|
window.location.origin.replace("http://", "").replace("https://", "")
|
||||||
}/${currentWorkspace.slug}`}
|
}/${currentWorkspace.slug}`}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
@ -291,11 +287,13 @@ export const WorkspaceDetails: FC = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{isAdmin && (
|
||||||
<div className="flex items-center justify-between py-2">
|
<div className="flex items-center justify-between py-2">
|
||||||
<Button variant="primary" onClick={handleSubmit(onSubmit)} loading={isSubmitting} disabled={!isAdmin}>
|
<Button variant="primary" onClick={handleSubmit(onSubmit)} loading={isSubmitting}>
|
||||||
{isSubmitting ? "Updating..." : "Update Workspace"}
|
{isSubmitting ? "Updating..." : "Update Workspace"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<Disclosure as="div" className="border-t border-custom-border-100">
|
<Disclosure as="div" className="border-t border-custom-border-100">
|
||||||
|
@ -4,8 +4,16 @@ import JiraLogo from "public/services/jira.svg";
|
|||||||
import CSVLogo from "public/services/csv.svg";
|
import CSVLogo from "public/services/csv.svg";
|
||||||
import ExcelLogo from "public/services/excel.svg";
|
import ExcelLogo from "public/services/excel.svg";
|
||||||
import JSONLogo from "public/services/json.svg";
|
import JSONLogo from "public/services/json.svg";
|
||||||
|
// types
|
||||||
import { TStaticViewTypes } from "types";
|
import { TStaticViewTypes } from "types";
|
||||||
|
|
||||||
|
export enum EUserWorkspaceRoles {
|
||||||
|
GUEST = 5,
|
||||||
|
VIEWER = 10,
|
||||||
|
MEMBER = 15,
|
||||||
|
ADMIN = 20,
|
||||||
|
}
|
||||||
|
|
||||||
export const ROLE = {
|
export const ROLE = {
|
||||||
5: "Guest",
|
5: "Guest",
|
||||||
10: "Viewer",
|
10: "Viewer",
|
||||||
@ -105,3 +113,50 @@ export const RESTRICTED_URLS = [
|
|||||||
"spaces",
|
"spaces",
|
||||||
"workspace-invitations",
|
"workspace-invitations",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const WORKSPACE_SETTINGS_LINKS: {
|
||||||
|
label: string;
|
||||||
|
href: string;
|
||||||
|
access: EUserWorkspaceRoles;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
label: "General",
|
||||||
|
href: `/settings`,
|
||||||
|
access: EUserWorkspaceRoles.GUEST,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Members",
|
||||||
|
href: `/settings/members`,
|
||||||
|
access: EUserWorkspaceRoles.GUEST,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Billing and plans",
|
||||||
|
href: `/settings/billing`,
|
||||||
|
access: EUserWorkspaceRoles.ADMIN,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Integrations",
|
||||||
|
href: `/settings/integrations`,
|
||||||
|
access: EUserWorkspaceRoles.ADMIN,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Imports",
|
||||||
|
href: `/settings/imports`,
|
||||||
|
access: EUserWorkspaceRoles.ADMIN,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Exports",
|
||||||
|
href: `/settings/exports`,
|
||||||
|
access: EUserWorkspaceRoles.MEMBER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Webhooks",
|
||||||
|
href: `/settings/webhooks`,
|
||||||
|
access: EUserWorkspaceRoles.ADMIN,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "API tokens",
|
||||||
|
href: `/settings/api-tokens`,
|
||||||
|
access: EUserWorkspaceRoles.ADMIN,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
@ -1,82 +1,35 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { RootStore } from "store/root";
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
|
// constants
|
||||||
export enum EUserWorkspaceRoles {
|
import { EUserWorkspaceRoles, WORKSPACE_SETTINGS_LINKS } from "constants/workspace";
|
||||||
GUEST = 5,
|
|
||||||
MEMBER = 15,
|
|
||||||
ADMIN = 20,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const WorkspaceSettingsSidebar = () => {
|
export const WorkspaceSettingsSidebar = () => {
|
||||||
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
const { user: userStore }: RootStore = useMobxStore();
|
// mobx store
|
||||||
|
const {
|
||||||
|
user: { currentWorkspaceRole },
|
||||||
|
} = useMobxStore();
|
||||||
|
|
||||||
const workspaceMemberInfo = userStore.currentWorkspaceRole || EUserWorkspaceRoles.GUEST;
|
const workspaceMemberInfo = currentWorkspaceRole || EUserWorkspaceRoles.GUEST;
|
||||||
|
|
||||||
const workspaceLinks: Array<{
|
|
||||||
label: string;
|
|
||||||
href: string;
|
|
||||||
access: EUserWorkspaceRoles;
|
|
||||||
}> = [
|
|
||||||
{
|
|
||||||
label: "General",
|
|
||||||
href: `/${workspaceSlug}/settings`,
|
|
||||||
access: EUserWorkspaceRoles.GUEST,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Members",
|
|
||||||
href: `/${workspaceSlug}/settings/members`,
|
|
||||||
access: EUserWorkspaceRoles.GUEST,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Billing and plans",
|
|
||||||
href: `/${workspaceSlug}/settings/billing`,
|
|
||||||
access: EUserWorkspaceRoles.ADMIN,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Integrations",
|
|
||||||
href: `/${workspaceSlug}/settings/integrations`,
|
|
||||||
access: EUserWorkspaceRoles.ADMIN,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Imports",
|
|
||||||
href: `/${workspaceSlug}/settings/imports`,
|
|
||||||
access: EUserWorkspaceRoles.ADMIN,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Exports",
|
|
||||||
href: `/${workspaceSlug}/settings/exports`,
|
|
||||||
access: EUserWorkspaceRoles.MEMBER,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Webhooks",
|
|
||||||
href: `/${workspaceSlug}/settings/webhooks`,
|
|
||||||
access: EUserWorkspaceRoles.ADMIN,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "API tokens",
|
|
||||||
href: `/${workspaceSlug}/settings/api-tokens`,
|
|
||||||
access: EUserWorkspaceRoles.ADMIN,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-6 w-80 px-5">
|
<div className="flex flex-col gap-6 w-80 px-5">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<span className="text-xs text-custom-sidebar-text-400 font-semibold">SETTINGS</span>
|
<span className="text-xs text-custom-sidebar-text-400 font-semibold">SETTINGS</span>
|
||||||
<div className="flex flex-col gap-1 w-full">
|
<div className="flex flex-col gap-1 w-full">
|
||||||
{workspaceLinks.map(
|
{WORKSPACE_SETTINGS_LINKS.map(
|
||||||
(link) =>
|
(link) =>
|
||||||
workspaceMemberInfo >= link.access && (
|
workspaceMemberInfo >= link.access && (
|
||||||
<Link key={link.href} href={link.href}>
|
<Link key={link.href} href={`/${workspaceSlug}/${link.href}`}>
|
||||||
<a>
|
<a>
|
||||||
<div
|
<div
|
||||||
className={`px-4 py-2 text-sm font-medium rounded-md ${
|
className={`px-4 py-2 text-sm font-medium rounded-md ${
|
||||||
router.pathname.split("/")?.[3] === link.href.split("/")?.[3]
|
router.pathname.split("/")?.[3] === link.href.split("/")?.[2]
|
||||||
? "bg-custom-primary-100/10 text-custom-primary-100"
|
? "bg-custom-primary-100/10 text-custom-primary-100"
|
||||||
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
|
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
|
||||||
}`}
|
}`}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// layouts
|
// layouts
|
||||||
import { AppLayout } from "layouts/app-layout";
|
import { AppLayout } from "layouts/app-layout";
|
||||||
import { WorkspaceSettingLayout } from "layouts/settings-layout";
|
import { WorkspaceSettingLayout } from "layouts/settings-layout";
|
||||||
@ -15,8 +18,7 @@ import { APITokenService } from "services/api_token.service";
|
|||||||
import { NextPageWithLayout } from "types/app";
|
import { NextPageWithLayout } from "types/app";
|
||||||
// constants
|
// constants
|
||||||
import { API_TOKENS_LIST } from "constants/fetch-keys";
|
import { API_TOKENS_LIST } from "constants/fetch-keys";
|
||||||
import { observer } from "mobx-react-lite";
|
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
|
||||||
|
|
||||||
const apiTokenService = new APITokenService();
|
const apiTokenService = new APITokenService();
|
||||||
|
|
||||||
@ -31,7 +33,7 @@ const ApiTokensPage: NextPageWithLayout = observer(() => {
|
|||||||
user: { currentWorkspaceRole },
|
user: { currentWorkspaceRole },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
|
|
||||||
const isAdmin = currentWorkspaceRole === 20;
|
const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
|
||||||
|
|
||||||
const { data: tokens } = useSWR(workspaceSlug && isAdmin ? API_TOKENS_LIST(workspaceSlug.toString()) : null, () =>
|
const { data: tokens } = useSWR(workspaceSlug && isAdmin ? API_TOKENS_LIST(workspaceSlug.toString()) : null, () =>
|
||||||
workspaceSlug && isAdmin ? apiTokenService.getApiTokens(workspaceSlug.toString()) : null
|
workspaceSlug && isAdmin ? apiTokenService.getApiTokens(workspaceSlug.toString()) : null
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { ReactElement } from "react";
|
import { observer } from "mobx-react-lite";
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// layouts
|
// layouts
|
||||||
import { AppLayout } from "layouts/app-layout";
|
import { AppLayout } from "layouts/app-layout";
|
||||||
import { WorkspaceSettingLayout } from "layouts/settings-layout";
|
import { WorkspaceSettingLayout } from "layouts/settings-layout";
|
||||||
@ -8,8 +10,24 @@ import { WorkspaceSettingHeader } from "components/headers";
|
|||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { NextPageWithLayout } from "types/app";
|
import { NextPageWithLayout } from "types/app";
|
||||||
|
// constants
|
||||||
|
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||||
|
|
||||||
const BillingSettingsPage: NextPageWithLayout = () => (
|
const BillingSettingsPage: NextPageWithLayout = observer(() => {
|
||||||
|
const {
|
||||||
|
user: { currentWorkspaceRole },
|
||||||
|
} = useMobxStore();
|
||||||
|
|
||||||
|
const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
|
||||||
|
|
||||||
|
if (!isAdmin)
|
||||||
|
return (
|
||||||
|
<div className="h-full w-full flex justify-center mt-10 p-4">
|
||||||
|
<p className="text-custom-text-300 text-sm">You are not authorized to access this page.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
<section className="pr-9 py-8 w-full overflow-y-auto">
|
<section className="pr-9 py-8 w-full overflow-y-auto">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center py-3.5 border-b border-custom-border-100">
|
<div className="flex items-center py-3.5 border-b border-custom-border-100">
|
||||||
@ -26,9 +44,10 @@ const BillingSettingsPage: NextPageWithLayout = () => (
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
BillingSettingsPage.getLayout = function getLayout(page: ReactElement) {
|
BillingSettingsPage.getLayout = function getLayout(page: React.ReactElement) {
|
||||||
return (
|
return (
|
||||||
<AppLayout header={<WorkspaceSettingHeader title="Billing & Plans Settings" />}>
|
<AppLayout header={<WorkspaceSettingHeader title="Billing & Plans Settings" />}>
|
||||||
<WorkspaceSettingLayout>{page}</WorkspaceSettingLayout>
|
<WorkspaceSettingLayout>{page}</WorkspaceSettingLayout>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { ReactElement } from "react";
|
import { observer } from "mobx-react-lite";
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// layout
|
// layout
|
||||||
import { AppLayout } from "layouts/app-layout";
|
import { AppLayout } from "layouts/app-layout";
|
||||||
import { WorkspaceSettingLayout } from "layouts/settings-layout";
|
import { WorkspaceSettingLayout } from "layouts/settings-layout";
|
||||||
@ -7,17 +9,35 @@ import { WorkspaceSettingHeader } from "components/headers";
|
|||||||
import ExportGuide from "components/exporter/guide";
|
import ExportGuide from "components/exporter/guide";
|
||||||
// types
|
// types
|
||||||
import { NextPageWithLayout } from "types/app";
|
import { NextPageWithLayout } from "types/app";
|
||||||
|
// constants
|
||||||
|
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||||
|
|
||||||
const ExportsPage: NextPageWithLayout = () => (
|
const ExportsPage: NextPageWithLayout = observer(() => {
|
||||||
|
const {
|
||||||
|
user: { currentWorkspaceRole },
|
||||||
|
} = useMobxStore();
|
||||||
|
|
||||||
|
const hasPageAccess =
|
||||||
|
currentWorkspaceRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentWorkspaceRole);
|
||||||
|
|
||||||
|
if (!hasPageAccess)
|
||||||
|
return (
|
||||||
|
<div className="h-full w-full flex justify-center mt-10 p-4">
|
||||||
|
<p className="text-custom-text-300 text-sm">You are not authorized to access this page.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
<div className="pr-9 py-8 w-full overflow-y-auto">
|
<div className="pr-9 py-8 w-full overflow-y-auto">
|
||||||
<div className="flex items-center py-3.5 border-b border-custom-border-100">
|
<div className="flex items-center py-3.5 border-b border-custom-border-100">
|
||||||
<h3 className="text-xl font-medium">Exports</h3>
|
<h3 className="text-xl font-medium">Exports</h3>
|
||||||
</div>
|
</div>
|
||||||
<ExportGuide />
|
<ExportGuide />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
ExportsPage.getLayout = function getLayout(page: ReactElement) {
|
ExportsPage.getLayout = function getLayout(page: React.ReactElement) {
|
||||||
return (
|
return (
|
||||||
<AppLayout header={<WorkspaceSettingHeader title="Export Settings" />}>
|
<AppLayout header={<WorkspaceSettingHeader title="Export Settings" />}>
|
||||||
<WorkspaceSettingLayout>{page}</WorkspaceSettingLayout>
|
<WorkspaceSettingLayout>{page}</WorkspaceSettingLayout>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { ReactElement } from "react";
|
import { observer } from "mobx-react-lite";
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// layouts
|
// layouts
|
||||||
import { WorkspaceSettingLayout } from "layouts/settings-layout";
|
import { WorkspaceSettingLayout } from "layouts/settings-layout";
|
||||||
import { AppLayout } from "layouts/app-layout";
|
import { AppLayout } from "layouts/app-layout";
|
||||||
@ -7,17 +9,34 @@ import IntegrationGuide from "components/integration/guide";
|
|||||||
import { WorkspaceSettingHeader } from "components/headers";
|
import { WorkspaceSettingHeader } from "components/headers";
|
||||||
// types
|
// types
|
||||||
import { NextPageWithLayout } from "types/app";
|
import { NextPageWithLayout } from "types/app";
|
||||||
|
// constants
|
||||||
|
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||||
|
|
||||||
const ImportsPage: NextPageWithLayout = () => (
|
const ImportsPage: NextPageWithLayout = observer(() => {
|
||||||
|
const {
|
||||||
|
user: { currentWorkspaceRole },
|
||||||
|
} = useMobxStore();
|
||||||
|
|
||||||
|
const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
|
||||||
|
|
||||||
|
if (!isAdmin)
|
||||||
|
return (
|
||||||
|
<div className="h-full w-full flex justify-center mt-10 p-4">
|
||||||
|
<p className="text-custom-text-300 text-sm">You are not authorized to access this page.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
<section className="pr-9 py-8 w-full overflow-y-auto">
|
<section className="pr-9 py-8 w-full overflow-y-auto">
|
||||||
<div className="flex items-center py-3.5 border-b border-custom-border-100">
|
<div className="flex items-center py-3.5 border-b border-custom-border-100">
|
||||||
<h3 className="text-xl font-medium">Imports</h3>
|
<h3 className="text-xl font-medium">Imports</h3>
|
||||||
</div>
|
</div>
|
||||||
<IntegrationGuide />
|
<IntegrationGuide />
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
ImportsPage.getLayout = function getLayout(page: ReactElement) {
|
ImportsPage.getLayout = function getLayout(page: React.ReactElement) {
|
||||||
return (
|
return (
|
||||||
<AppLayout header={<WorkspaceSettingHeader title="Import Settings" />}>
|
<AppLayout header={<WorkspaceSettingHeader title="Import Settings" />}>
|
||||||
<WorkspaceSettingLayout>{page}</WorkspaceSettingLayout>
|
<WorkspaceSettingLayout>{page}</WorkspaceSettingLayout>
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// services
|
// services
|
||||||
import { IntegrationService } from "services/integrations";
|
import { IntegrationService } from "services/integrations";
|
||||||
// layouts
|
// layouts
|
||||||
@ -16,16 +19,31 @@ import { Loader } from "@plane/ui";
|
|||||||
import { NextPageWithLayout } from "types/app";
|
import { NextPageWithLayout } from "types/app";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { APP_INTEGRATIONS } from "constants/fetch-keys";
|
import { APP_INTEGRATIONS } from "constants/fetch-keys";
|
||||||
|
// constants
|
||||||
|
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||||
|
|
||||||
// services
|
|
||||||
const integrationService = new IntegrationService();
|
const integrationService = new IntegrationService();
|
||||||
|
|
||||||
const WorkspaceIntegrationsPage: NextPageWithLayout = () => {
|
const WorkspaceIntegrationsPage: NextPageWithLayout = observer(() => {
|
||||||
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
// mobx store
|
||||||
|
const {
|
||||||
|
user: { currentWorkspaceRole },
|
||||||
|
} = useMobxStore();
|
||||||
|
|
||||||
const { data: appIntegrations } = useSWR(workspaceSlug ? APP_INTEGRATIONS : null, () =>
|
const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
|
||||||
workspaceSlug ? integrationService.getAppIntegrationsList() : null
|
|
||||||
|
if (!isAdmin)
|
||||||
|
return (
|
||||||
|
<div className="h-full w-full flex justify-center mt-10 p-4">
|
||||||
|
<p className="text-custom-text-300 text-sm">You are not authorized to access this page.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: appIntegrations } = useSWR(workspaceSlug && isAdmin ? APP_INTEGRATIONS : null, () =>
|
||||||
|
workspaceSlug && isAdmin ? integrationService.getAppIntegrationsList() : null
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -43,7 +61,7 @@ const WorkspaceIntegrationsPage: NextPageWithLayout = () => {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
WorkspaceIntegrationsPage.getLayout = function getLayout(page: ReactElement) {
|
WorkspaceIntegrationsPage.getLayout = function getLayout(page: ReactElement) {
|
||||||
return (
|
return (
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { useState, ReactElement } from "react";
|
import { useState, ReactElement } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { Search } from "lucide-react";
|
||||||
|
// mobx store
|
||||||
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
|
||||||
// layouts
|
// layouts
|
||||||
import { AppLayout } from "layouts/app-layout";
|
import { AppLayout } from "layouts/app-layout";
|
||||||
import { WorkspaceSettingLayout } from "layouts/settings-layout";
|
import { WorkspaceSettingLayout } from "layouts/settings-layout";
|
||||||
@ -12,21 +14,20 @@ import { WorkspaceSettingHeader } from "components/headers";
|
|||||||
import { SendWorkspaceInvitationModal, WorkspaceMembersList } from "components/workspace";
|
import { SendWorkspaceInvitationModal, WorkspaceMembersList } from "components/workspace";
|
||||||
// ui
|
// ui
|
||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// icons
|
|
||||||
import { Search } from "lucide-react";
|
|
||||||
// helpers
|
|
||||||
import { trackEvent } from "helpers/event-tracker.helper";
|
|
||||||
// types
|
// types
|
||||||
import { NextPageWithLayout } from "types/app";
|
import { NextPageWithLayout } from "types/app";
|
||||||
import { IWorkspaceBulkInviteFormData } from "types";
|
import { IWorkspaceBulkInviteFormData } from "types";
|
||||||
|
// constants
|
||||||
|
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||||
|
|
||||||
const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => {
|
const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
// store
|
// store
|
||||||
const {
|
const {
|
||||||
|
user: { currentWorkspaceRole },
|
||||||
workspaceMember: { inviteMembersToWorkspace },
|
workspaceMember: { inviteMembersToWorkspace },
|
||||||
trackEvent: { postHogEventTracker, setTrackElement }
|
trackEvent: { postHogEventTracker, setTrackElement },
|
||||||
} = useMobxStore();
|
} = useMobxStore();
|
||||||
// states
|
// states
|
||||||
const [inviteModal, setInviteModal] = useState(false);
|
const [inviteModal, setInviteModal] = useState(false);
|
||||||
@ -57,15 +58,16 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasAddMemberPermission =
|
||||||
|
currentWorkspaceRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentWorkspaceRole);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{workspaceSlug && (
|
|
||||||
<SendWorkspaceInvitationModal
|
<SendWorkspaceInvitationModal
|
||||||
isOpen={inviteModal}
|
isOpen={inviteModal}
|
||||||
onClose={() => setInviteModal(false)}
|
onClose={() => setInviteModal(false)}
|
||||||
onSubmit={handleWorkspaceInvite}
|
onSubmit={handleWorkspaceInvite}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
<section className="pr-9 py-8 w-full overflow-y-auto">
|
<section className="pr-9 py-8 w-full overflow-y-auto">
|
||||||
<div className="flex items-center justify-between gap-4 py-3.5 border-b border-custom-border-100">
|
<div className="flex items-center justify-between gap-4 py-3.5 border-b border-custom-border-100">
|
||||||
<h4 className="text-xl font-medium">Members</h4>
|
<h4 className="text-xl font-medium">Members</h4>
|
||||||
@ -79,13 +81,18 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => {
|
|||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="primary" size="sm" onClick={() => {
|
{hasAddMemberPermission && (
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
setTrackElement("WORKSPACE_SETTINGS_MEMBERS_PAGE_HEADER");
|
setTrackElement("WORKSPACE_SETTINGS_MEMBERS_PAGE_HEADER");
|
||||||
setInviteModal(true)
|
setInviteModal(true);
|
||||||
}
|
}}
|
||||||
}>
|
>
|
||||||
Add Member
|
Add member
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<WorkspaceMembersList searchQuery={searchQuery} />
|
<WorkspaceMembersList searchQuery={searchQuery} />
|
||||||
</section>
|
</section>
|
||||||
|
@ -5,6 +5,7 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
|
|||||||
import { EFilterType } from "store/issues/types";
|
import { EFilterType } from "store/issues/types";
|
||||||
import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
|
import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
|
||||||
import { IssueFilterBaseStore } from "../project-issues/base-issue-filter.store";
|
import { IssueFilterBaseStore } from "../project-issues/base-issue-filter.store";
|
||||||
|
import { isEmpty } from "lodash";
|
||||||
|
|
||||||
interface IProjectIssuesFiltersOptions {
|
interface IProjectIssuesFiltersOptions {
|
||||||
filters: IIssueFilterOptions;
|
filters: IIssueFilterOptions;
|
||||||
@ -121,12 +122,18 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl
|
|||||||
|
|
||||||
let _projectIssueFilters = this.projectIssueFilters;
|
let _projectIssueFilters = this.projectIssueFilters;
|
||||||
if (!_projectIssueFilters) _projectIssueFilters = {};
|
if (!_projectIssueFilters) _projectIssueFilters = {};
|
||||||
if (!_projectIssueFilters[workspaceSlug])
|
if (!_projectIssueFilters[workspaceSlug]) {
|
||||||
_projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} };
|
_projectIssueFilters[workspaceSlug] = { displayProperties: {} } as IProjectIssuesDisplayOptions;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isEmpty(_projectIssueFilters[workspaceSlug].filters) ||
|
||||||
|
isEmpty(_projectIssueFilters[workspaceSlug].displayFilters)
|
||||||
|
) {
|
||||||
_projectIssueFilters[workspaceSlug] = {
|
_projectIssueFilters[workspaceSlug] = {
|
||||||
..._projectIssueFilters[workspaceSlug],
|
..._projectIssueFilters[workspaceSlug],
|
||||||
...issueFilters,
|
...issueFilters,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.projectIssueFilters = _projectIssueFilters;
|
this.projectIssueFilters = _projectIssueFilters;
|
||||||
@ -192,15 +199,15 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl
|
|||||||
fetchDisplayProperties = async (workspaceSlug: string) => {
|
fetchDisplayProperties = async (workspaceSlug: string) => {
|
||||||
try {
|
try {
|
||||||
const displayProperties: IIssueDisplayProperties = {
|
const displayProperties: IIssueDisplayProperties = {
|
||||||
assignee: false,
|
assignee: true,
|
||||||
start_date: false,
|
start_date: true,
|
||||||
due_date: false,
|
due_date: true,
|
||||||
labels: false,
|
labels: false,
|
||||||
key: false,
|
key: false,
|
||||||
priority: false,
|
priority: true,
|
||||||
state: false,
|
state: false,
|
||||||
sub_issue_count: false,
|
sub_issue_count: true,
|
||||||
link: false,
|
link: true,
|
||||||
attachment_count: false,
|
attachment_count: false,
|
||||||
estimate: false,
|
estimate: false,
|
||||||
created_on: false,
|
created_on: false,
|
||||||
@ -209,12 +216,15 @@ export class GlobalIssuesFilterStore extends IssueFilterBaseStore implements IGl
|
|||||||
|
|
||||||
let _projectIssueFilters = { ...this.projectIssueFilters };
|
let _projectIssueFilters = { ...this.projectIssueFilters };
|
||||||
if (!_projectIssueFilters) _projectIssueFilters = {};
|
if (!_projectIssueFilters) _projectIssueFilters = {};
|
||||||
if (!_projectIssueFilters[workspaceSlug])
|
if (!_projectIssueFilters[workspaceSlug]) {
|
||||||
_projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} };
|
_projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {} } as IProjectIssuesDisplayOptions;
|
||||||
|
}
|
||||||
|
if (isEmpty(_projectIssueFilters[workspaceSlug].displayProperties)) {
|
||||||
_projectIssueFilters[workspaceSlug] = {
|
_projectIssueFilters[workspaceSlug] = {
|
||||||
..._projectIssueFilters[workspaceSlug],
|
..._projectIssueFilters[workspaceSlug],
|
||||||
displayProperties: displayProperties,
|
displayProperties: displayProperties,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.projectIssueFilters = _projectIssueFilters;
|
this.projectIssueFilters = _projectIssueFilters;
|
||||||
|
@ -5,6 +5,7 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
|
|||||||
import { EFilterType } from "store/issues/types";
|
import { EFilterType } from "store/issues/types";
|
||||||
import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
|
import { handleIssueQueryParamsByLayout } from "helpers/issue.helper";
|
||||||
import { IssueFilterBaseStore } from "../project-issues/base-issue-filter.store";
|
import { IssueFilterBaseStore } from "../project-issues/base-issue-filter.store";
|
||||||
|
import isEmpty from "lodash/isEmpty";
|
||||||
|
|
||||||
interface IProjectIssuesFiltersOptions {
|
interface IProjectIssuesFiltersOptions {
|
||||||
filters: IIssueFilterOptions;
|
filters: IIssueFilterOptions;
|
||||||
@ -121,12 +122,18 @@ export class ProfileIssuesFilterStore extends IssueFilterBaseStore implements IP
|
|||||||
|
|
||||||
let _projectIssueFilters = this.projectIssueFilters;
|
let _projectIssueFilters = this.projectIssueFilters;
|
||||||
if (!_projectIssueFilters) _projectIssueFilters = {};
|
if (!_projectIssueFilters) _projectIssueFilters = {};
|
||||||
if (!_projectIssueFilters[workspaceSlug])
|
if (!_projectIssueFilters[workspaceSlug]) {
|
||||||
_projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} };
|
_projectIssueFilters[workspaceSlug] = { displayProperties: {} } as IProjectIssuesDisplayOptions;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isEmpty(_projectIssueFilters[workspaceSlug].filters) ||
|
||||||
|
isEmpty(_projectIssueFilters[workspaceSlug].displayFilters)
|
||||||
|
) {
|
||||||
_projectIssueFilters[workspaceSlug] = {
|
_projectIssueFilters[workspaceSlug] = {
|
||||||
..._projectIssueFilters[workspaceSlug],
|
..._projectIssueFilters[workspaceSlug],
|
||||||
...issueFilters,
|
...issueFilters,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.projectIssueFilters = _projectIssueFilters;
|
this.projectIssueFilters = _projectIssueFilters;
|
||||||
@ -184,7 +191,6 @@ export class ProfileIssuesFilterStore extends IssueFilterBaseStore implements IP
|
|||||||
|
|
||||||
return _filters;
|
return _filters;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.fetchDisplayFilters(workspaceSlug);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -192,12 +198,12 @@ export class ProfileIssuesFilterStore extends IssueFilterBaseStore implements IP
|
|||||||
fetchDisplayProperties = async (workspaceSlug: string) => {
|
fetchDisplayProperties = async (workspaceSlug: string) => {
|
||||||
try {
|
try {
|
||||||
const displayProperties: IIssueDisplayProperties = {
|
const displayProperties: IIssueDisplayProperties = {
|
||||||
assignee: false,
|
assignee: true,
|
||||||
start_date: false,
|
start_date: false,
|
||||||
due_date: false,
|
due_date: false,
|
||||||
labels: false,
|
labels: true,
|
||||||
key: false,
|
key: true,
|
||||||
priority: false,
|
priority: true,
|
||||||
state: false,
|
state: false,
|
||||||
sub_issue_count: false,
|
sub_issue_count: false,
|
||||||
link: false,
|
link: false,
|
||||||
@ -209,12 +215,15 @@ export class ProfileIssuesFilterStore extends IssueFilterBaseStore implements IP
|
|||||||
|
|
||||||
let _projectIssueFilters = { ...this.projectIssueFilters };
|
let _projectIssueFilters = { ...this.projectIssueFilters };
|
||||||
if (!_projectIssueFilters) _projectIssueFilters = {};
|
if (!_projectIssueFilters) _projectIssueFilters = {};
|
||||||
if (!_projectIssueFilters[workspaceSlug])
|
if (!_projectIssueFilters[workspaceSlug]) {
|
||||||
_projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {}, displayProperties: {} };
|
_projectIssueFilters[workspaceSlug] = { filters: {}, displayFilters: {} } as IProjectIssuesDisplayOptions;
|
||||||
|
}
|
||||||
|
if (isEmpty(_projectIssueFilters[workspaceSlug].displayProperties)) {
|
||||||
_projectIssueFilters[workspaceSlug] = {
|
_projectIssueFilters[workspaceSlug] = {
|
||||||
..._projectIssueFilters[workspaceSlug],
|
..._projectIssueFilters[workspaceSlug],
|
||||||
displayProperties: displayProperties,
|
displayProperties: displayProperties,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.projectIssueFilters = _projectIssueFilters;
|
this.projectIssueFilters = _projectIssueFilters;
|
||||||
@ -242,7 +251,6 @@ export class ProfileIssuesFilterStore extends IssueFilterBaseStore implements IP
|
|||||||
|
|
||||||
return properties;
|
return properties;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.fetchDisplayProperties(workspaceSlug);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -19,7 +19,7 @@ export interface IProfileIssuesStore {
|
|||||||
loader: TLoader;
|
loader: TLoader;
|
||||||
issues: { [user_id: string]: IProfileIssueTabTypes } | undefined;
|
issues: { [user_id: string]: IProfileIssueTabTypes } | undefined;
|
||||||
currentUserId: string | null;
|
currentUserId: string | null;
|
||||||
currentUserIssueTab: "assigned" | "created" | "subscribed" | null;
|
currentUserIssueTab: "assigned" | "created" | "subscribed" | undefined;
|
||||||
// computed
|
// computed
|
||||||
getIssues: IIssueResponse | undefined;
|
getIssues: IIssueResponse | undefined;
|
||||||
getIssuesIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined;
|
getIssuesIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined;
|
||||||
@ -28,6 +28,7 @@ export interface IProfileIssuesStore {
|
|||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
loadType: TLoader,
|
loadType: TLoader,
|
||||||
|
_?: string,
|
||||||
type?: "assigned" | "created" | "subscribed"
|
type?: "assigned" | "created" | "subscribed"
|
||||||
) => Promise<IIssueResponse>;
|
) => Promise<IIssueResponse>;
|
||||||
createIssue: (workspaceSlug: string, userId: string, data: Partial<IIssue>) => Promise<IIssue | undefined>;
|
createIssue: (workspaceSlug: string, userId: string, data: Partial<IIssue>) => Promise<IIssue | undefined>;
|
||||||
@ -51,7 +52,7 @@ export class ProfileIssuesStore extends IssueBaseStore implements IProfileIssues
|
|||||||
loader: TLoader = "init-loader";
|
loader: TLoader = "init-loader";
|
||||||
issues: { [user_id: string]: IProfileIssueTabTypes } | undefined = undefined;
|
issues: { [user_id: string]: IProfileIssueTabTypes } | undefined = undefined;
|
||||||
currentUserId: string | null = null;
|
currentUserId: string | null = null;
|
||||||
currentUserIssueTab: "assigned" | "created" | "subscribed" | null = null;
|
currentUserIssueTab: "assigned" | "created" | "subscribed" | undefined = undefined;
|
||||||
// root store
|
// root store
|
||||||
rootStore;
|
rootStore;
|
||||||
// service
|
// service
|
||||||
@ -86,7 +87,9 @@ export class ProfileIssuesStore extends IssueBaseStore implements IProfileIssues
|
|||||||
if (!workspaceSlug || !this.currentUserId || !this.currentUserIssueTab) return;
|
if (!workspaceSlug || !this.currentUserId || !this.currentUserIssueTab) return;
|
||||||
|
|
||||||
const userFilters = this.rootStore?.workspaceProfileIssuesFilter?.issueFilters?.filters;
|
const userFilters = this.rootStore?.workspaceProfileIssuesFilter?.issueFilters?.filters;
|
||||||
if (userFilters) this.fetchIssues(workspaceSlug, this.currentUserId, "mutation", this.currentUserIssueTab);
|
if (userFilters) {
|
||||||
|
this.fetchIssues(workspaceSlug, this.currentUserId, "mutation", this.currentUserIssueTab);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,6 +105,7 @@ export class ProfileIssuesStore extends IssueBaseStore implements IProfileIssues
|
|||||||
const displayFilters = this.rootStore?.workspaceProfileIssuesFilter?.issueFilters?.displayFilters;
|
const displayFilters = this.rootStore?.workspaceProfileIssuesFilter?.issueFilters?.displayFilters;
|
||||||
if (!displayFilters) return undefined;
|
if (!displayFilters) return undefined;
|
||||||
|
|
||||||
|
const subGroupBy = displayFilters?.sub_group_by;
|
||||||
const groupBy = displayFilters?.group_by;
|
const groupBy = displayFilters?.group_by;
|
||||||
const orderBy = displayFilters?.order_by;
|
const orderBy = displayFilters?.order_by;
|
||||||
const layout = displayFilters?.layout;
|
const layout = displayFilters?.layout;
|
||||||
@ -113,6 +117,15 @@ export class ProfileIssuesStore extends IssueBaseStore implements IProfileIssues
|
|||||||
if (layout === "list" && orderBy) {
|
if (layout === "list" && orderBy) {
|
||||||
if (groupBy) issues = this.groupedIssues(groupBy, orderBy, this.issues[currentUserId][this.currentUserIssueTab]);
|
if (groupBy) issues = this.groupedIssues(groupBy, orderBy, this.issues[currentUserId][this.currentUserIssueTab]);
|
||||||
else issues = this.unGroupedIssues(orderBy, this.issues[currentUserId][this.currentUserIssueTab]);
|
else issues = this.unGroupedIssues(orderBy, this.issues[currentUserId][this.currentUserIssueTab]);
|
||||||
|
} else if (layout === "kanban" && groupBy && orderBy) {
|
||||||
|
if (subGroupBy)
|
||||||
|
issues = this.subGroupedIssues(
|
||||||
|
subGroupBy,
|
||||||
|
groupBy,
|
||||||
|
orderBy,
|
||||||
|
this.issues[currentUserId][this.currentUserIssueTab]
|
||||||
|
);
|
||||||
|
else issues = this.groupedIssues(groupBy, orderBy, this.issues[currentUserId][this.currentUserIssueTab]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return issues;
|
return issues;
|
||||||
@ -138,6 +151,7 @@ export class ProfileIssuesStore extends IssueBaseStore implements IProfileIssues
|
|||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
loadType: TLoader = "init-loader",
|
loadType: TLoader = "init-loader",
|
||||||
|
_?: string,
|
||||||
type?: "assigned" | "created" | "subscribed"
|
type?: "assigned" | "created" | "subscribed"
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
@ -186,20 +200,12 @@ export class ProfileIssuesStore extends IssueBaseStore implements IProfileIssues
|
|||||||
createIssue = async (workspaceSlug: string, userId: string, data: Partial<IIssue>) => {
|
createIssue = async (workspaceSlug: string, userId: string, data: Partial<IIssue>) => {
|
||||||
try {
|
try {
|
||||||
const projectId = data.project;
|
const projectId = data.project;
|
||||||
const moduleId = data.module_id;
|
|
||||||
const cycleId = data.cycle_id;
|
|
||||||
|
|
||||||
if (!projectId) return;
|
if (!projectId) return;
|
||||||
|
|
||||||
let response = {} as IIssue;
|
let response = {} as IIssue;
|
||||||
response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data);
|
response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data);
|
||||||
|
|
||||||
// if (moduleId)
|
|
||||||
// response = await this.rootStore.moduleIssues.addIssueToModule(workspaceSlug, projectId, moduleId, response);
|
|
||||||
|
|
||||||
// if (cycleId)
|
|
||||||
// response = await this.rootStore.cycleIssues.addIssueToCycle(workspaceSlug, projectId, cycleId, response);
|
|
||||||
|
|
||||||
let _issues = this.issues;
|
let _issues = this.issues;
|
||||||
if (!_issues) _issues = {};
|
if (!_issues) _issues = {};
|
||||||
if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} };
|
if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} };
|
||||||
@ -227,8 +233,8 @@ export class ProfileIssuesStore extends IssueBaseStore implements IProfileIssues
|
|||||||
let _issues = { ...this.issues };
|
let _issues = { ...this.issues };
|
||||||
if (!_issues) _issues = {};
|
if (!_issues) _issues = {};
|
||||||
if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} };
|
if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} };
|
||||||
_issues[projectId][this.currentUserIssueTab][userId] = {
|
_issues[userId][this.currentUserIssueTab][userId] = {
|
||||||
..._issues[projectId][this.currentUserIssueTab][userId],
|
..._issues[userId][this.currentUserIssueTab][userId],
|
||||||
...data,
|
...data,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -119,8 +119,17 @@ export class ProjectArchivedIssuesStore extends IssueBaseStore implements IProje
|
|||||||
|
|
||||||
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||||
try {
|
try {
|
||||||
await this.archivedIssueService.unarchiveIssue(workspaceSlug, projectId, issueId);
|
let _issues = { ...this.issues };
|
||||||
return;
|
if (!_issues) _issues = {};
|
||||||
|
if (!_issues[projectId]) _issues[projectId] = {};
|
||||||
|
delete _issues?.[projectId]?.[issueId];
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues = _issues;
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await this.archivedIssueService.deleteArchivedIssue(workspaceSlug, projectId, issueId);
|
||||||
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@ -128,8 +137,17 @@ export class ProjectArchivedIssuesStore extends IssueBaseStore implements IProje
|
|||||||
|
|
||||||
removeIssueFromArchived = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
removeIssueFromArchived = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||||
try {
|
try {
|
||||||
await this.archivedIssueService.deleteArchivedIssue(workspaceSlug, projectId, issueId);
|
let _issues = { ...this.issues };
|
||||||
return;
|
if (!_issues) _issues = {};
|
||||||
|
if (!_issues[projectId]) _issues[projectId] = {};
|
||||||
|
delete _issues?.[projectId]?.[issueId];
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues = _issues;
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await this.archivedIssueService.unarchiveIssue(workspaceSlug, projectId, issueId);
|
||||||
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { IssueBaseStore } from "store/issues";
|
|||||||
import { IssueService } from "services/issue";
|
import { IssueService } from "services/issue";
|
||||||
import { CycleService } from "services/cycle.service";
|
import { CycleService } from "services/cycle.service";
|
||||||
// types
|
// types
|
||||||
import { TIssueGroupByOptions } from "types";
|
import { CycleIssueResponse, TIssueGroupByOptions } from "types";
|
||||||
import { IIssue } from "types/issues";
|
import { IIssue } from "types/issues";
|
||||||
import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues, ViewFlags } from "../../types";
|
import { IIssueResponse, TLoader, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues, ViewFlags } from "../../types";
|
||||||
import { RootStore } from "store/root";
|
import { RootStore } from "store/root";
|
||||||
@ -49,7 +49,12 @@ export interface ICycleIssuesStore {
|
|||||||
data: IIssue,
|
data: IIssue,
|
||||||
cycleId?: string | undefined
|
cycleId?: string | undefined
|
||||||
) => Promise<IIssue | undefined>;
|
) => Promise<IIssue | undefined>;
|
||||||
addIssueToCycle: (workspaceSlug: string, projectId: string, cycleId: string, data: IIssue) => Promise<IIssue>;
|
addIssueToCycle: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
cycleId: string,
|
||||||
|
issueIds: string[],
|
||||||
|
fetchAfterAddition?: boolean
|
||||||
|
) => Promise<IIssue>;
|
||||||
removeIssueFromCycle: (
|
removeIssueFromCycle: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
@ -70,6 +75,9 @@ export class CycleIssuesStore extends IssueBaseStore implements ICycleIssuesStor
|
|||||||
cycleService;
|
cycleService;
|
||||||
issueService;
|
issueService;
|
||||||
|
|
||||||
|
//projectId
|
||||||
|
currentProjectId: string | undefined;
|
||||||
|
|
||||||
//viewData
|
//viewData
|
||||||
viewFlags = {
|
viewFlags = {
|
||||||
enableQuickAdd: true,
|
enableQuickAdd: true,
|
||||||
@ -157,6 +165,8 @@ export class CycleIssuesStore extends IssueBaseStore implements ICycleIssuesStor
|
|||||||
try {
|
try {
|
||||||
this.loader = loadType;
|
this.loader = loadType;
|
||||||
|
|
||||||
|
this.currentProjectId = projectId;
|
||||||
|
|
||||||
const params = this.rootStore?.cycleIssuesFilter?.appliedFilters;
|
const params = this.rootStore?.cycleIssuesFilter?.appliedFilters;
|
||||||
const response = await this.cycleService.getV3CycleIssues(workspaceSlug, projectId, cycleId, params);
|
const response = await this.cycleService.getV3CycleIssues(workspaceSlug, projectId, cycleId, params);
|
||||||
|
|
||||||
@ -185,7 +195,16 @@ export class CycleIssuesStore extends IssueBaseStore implements ICycleIssuesStor
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data);
|
const response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data);
|
||||||
const issueToCycle = await this.addIssueToCycle(workspaceSlug, projectId, cycleId, response);
|
const issueToCycle = await this.addIssueToCycle(workspaceSlug, cycleId, [response.id], false);
|
||||||
|
|
||||||
|
let _issues = this.issues;
|
||||||
|
if (!_issues) _issues = {};
|
||||||
|
if (!_issues[cycleId]) _issues[cycleId] = {};
|
||||||
|
_issues[cycleId] = { ..._issues[cycleId], ...{ [response.id]: response } };
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues = _issues;
|
||||||
|
});
|
||||||
|
|
||||||
return issueToCycle;
|
return issueToCycle;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -287,24 +306,19 @@ export class CycleIssuesStore extends IssueBaseStore implements ICycleIssuesStor
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
addIssueToCycle = async (workspaceSlug: string, projectId: string, cycleId: string, data: IIssue) => {
|
addIssueToCycle = async (workspaceSlug: string, cycleId: string, issueIds: string[], fetchAfterAddition = true) => {
|
||||||
|
if (!this.currentProjectId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const issueToCycle = await this.issueService.addIssueToCycle(workspaceSlug, projectId, cycleId, {
|
const issueToCycle = await this.issueService.addIssueToCycle(workspaceSlug, this.currentProjectId, cycleId, {
|
||||||
issues: [data.id],
|
issues: issueIds,
|
||||||
});
|
});
|
||||||
|
|
||||||
let _issues = this.issues;
|
if (fetchAfterAddition) this.fetchIssues(workspaceSlug, this.currentProjectId, "mutation", cycleId);
|
||||||
if (!_issues) _issues = {};
|
|
||||||
if (!_issues[cycleId]) _issues[cycleId] = {};
|
|
||||||
_issues[cycleId] = { ..._issues[cycleId], ...{ [data.id]: data } };
|
|
||||||
|
|
||||||
runInAction(() => {
|
|
||||||
this.issues = _issues;
|
|
||||||
});
|
|
||||||
|
|
||||||
return issueToCycle;
|
return issueToCycle;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
|
this.fetchIssues(workspaceSlug, this.currentProjectId, "mutation", cycleId);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -49,7 +49,12 @@ export interface IModuleIssuesStore {
|
|||||||
data: IIssue,
|
data: IIssue,
|
||||||
moduleId?: string | undefined
|
moduleId?: string | undefined
|
||||||
) => Promise<IIssue | undefined>;
|
) => Promise<IIssue | undefined>;
|
||||||
addIssueToModule: (workspaceSlug: string, projectId: string, moduleId: string, data: IIssue) => Promise<IIssue>;
|
addIssueToModule: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
moduleId: string,
|
||||||
|
issueIds: string[],
|
||||||
|
fetchAfterAddition?: boolean
|
||||||
|
) => Promise<IIssue>;
|
||||||
removeIssueFromModule: (
|
removeIssueFromModule: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
@ -70,6 +75,8 @@ export class ModuleIssuesStore extends IssueBaseStore implements IModuleIssuesSt
|
|||||||
moduleService;
|
moduleService;
|
||||||
issueService;
|
issueService;
|
||||||
|
|
||||||
|
currentProjectId: string | undefined;
|
||||||
|
|
||||||
//viewData
|
//viewData
|
||||||
viewFlags = {
|
viewFlags = {
|
||||||
enableQuickAdd: true,
|
enableQuickAdd: true,
|
||||||
@ -154,6 +161,7 @@ export class ModuleIssuesStore extends IssueBaseStore implements IModuleIssuesSt
|
|||||||
) => {
|
) => {
|
||||||
if (!moduleId) return undefined;
|
if (!moduleId) return undefined;
|
||||||
|
|
||||||
|
this.currentProjectId = projectId;
|
||||||
try {
|
try {
|
||||||
this.loader = loadType;
|
this.loader = loadType;
|
||||||
|
|
||||||
@ -185,7 +193,16 @@ export class ModuleIssuesStore extends IssueBaseStore implements IModuleIssuesSt
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data);
|
const response = await this.rootStore.projectIssues.createIssue(workspaceSlug, projectId, data);
|
||||||
const issueToModule = await this.addIssueToModule(workspaceSlug, projectId, moduleId, response);
|
const issueToModule = await this.addIssueToModule(workspaceSlug, moduleId, [response.id], false);
|
||||||
|
|
||||||
|
let _issues = this.issues;
|
||||||
|
if (!_issues) _issues = {};
|
||||||
|
if (!_issues[moduleId]) _issues[moduleId] = {};
|
||||||
|
_issues[moduleId] = { ..._issues[moduleId], ...{ [response.id]: response } };
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues = _issues;
|
||||||
|
});
|
||||||
|
|
||||||
return issueToModule;
|
return issueToModule;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -289,24 +306,19 @@ export class ModuleIssuesStore extends IssueBaseStore implements IModuleIssuesSt
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
addIssueToModule = async (workspaceSlug: string, projectId: string, moduleId: string, data: IIssue) => {
|
addIssueToModule = async (workspaceSlug: string, moduleId: string, issueIds: string[], fetchAfterAddition = true) => {
|
||||||
|
if (!this.currentProjectId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const issueToModule = await this.moduleService.addIssuesToModule(workspaceSlug, projectId, moduleId, {
|
const issueToModule = await this.moduleService.addIssuesToModule(workspaceSlug, this.currentProjectId, moduleId, {
|
||||||
issues: [data.id],
|
issues: issueIds,
|
||||||
});
|
});
|
||||||
|
|
||||||
let _issues = this.issues;
|
if (fetchAfterAddition) this.fetchIssues(workspaceSlug, this.currentProjectId, "mutation", moduleId);
|
||||||
if (!_issues) _issues = {};
|
|
||||||
if (!_issues[moduleId]) _issues[moduleId] = {};
|
|
||||||
_issues[moduleId] = { ..._issues[moduleId], ...{ [data.id]: data } };
|
|
||||||
|
|
||||||
runInAction(() => {
|
|
||||||
this.issues = _issues;
|
|
||||||
});
|
|
||||||
|
|
||||||
return issueToModule;
|
return issueToModule;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.fetchIssues(workspaceSlug, projectId, "mutation", moduleId);
|
this.fetchIssues(workspaceSlug, this.currentProjectId, "mutation", moduleId);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -88,7 +88,8 @@ export class WorkspaceMemberStore implements IWorkspaceMemberStore {
|
|||||||
* computed value provides the members information including the invitations.
|
* computed value provides the members information including the invitations.
|
||||||
*/
|
*/
|
||||||
get workspaceMembersWithInvitations() {
|
get workspaceMembersWithInvitations() {
|
||||||
if (!this.workspaceMembers || !this.workspaceMemberInvitations) return null;
|
if (!this.workspaceMembers) return null;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...(this.workspaceMemberInvitations?.map((item) => ({
|
...(this.workspaceMemberInvitations?.map((item) => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
|
2
web/types/workspace.d.ts
vendored
2
web/types/workspace.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
import type { IProjectMember, IUser, IUserLite, IUserMemberLite, IWorkspaceViewProps } from "types";
|
import type { IProjectMember, IUser, IUserLite, IWorkspaceViewProps } from "types";
|
||||||
|
|
||||||
export type TUserWorkspaceRole = 5 | 10 | 15 | 20;
|
export type TUserWorkspaceRole = 5 | 10 | 15 | 20;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user