chore: merge develop

This commit is contained in:
Aaryan Khandelwal 2023-12-18 18:48:09 +05:30
commit 9d2e0e29e7
97 changed files with 1200 additions and 379 deletions

View File

@ -3,7 +3,7 @@ name: Create Sync Action
on: on:
pull_request: pull_request:
branches: branches:
- develop # Change this to preview - preview
types: types:
- closed - closed
env: env:
@ -33,23 +33,14 @@ jobs:
sudo apt update sudo apt update
sudo apt install gh -y sudo apt install gh -y
- name: Create Pull Request - name: Push Changes to Target Repo
env: env:
GH_TOKEN: ${{ secrets.ACCESS_TOKEN }} GH_TOKEN: ${{ secrets.ACCESS_TOKEN }}
run: | run: |
TARGET_REPO="${{ secrets.SYNC_TARGET_REPO_NAME }}" TARGET_REPO="${{ secrets.SYNC_TARGET_REPO_NAME }}"
TARGET_BRANCH="${{ secrets.SYNC_TARGET_BRANCH_NAME }}" TARGET_BRANCH="${{ secrets.SYNC_TARGET_BRANCH_NAME }}"
TARGET_BASE_BRANCH="${{ secrets.SYNC_TARGET_BASE_BRANCH_NAME }}"
SOURCE_BRANCH="${{ env.SOURCE_BRANCH_NAME }}" SOURCE_BRANCH="${{ env.SOURCE_BRANCH_NAME }}"
git checkout $SOURCE_BRANCH git checkout $SOURCE_BRANCH
git remote add target-origin "https://$GH_TOKEN@github.com/$TARGET_REPO.git" git remote add target-origin "https://$GH_TOKEN@github.com/$TARGET_REPO.git"
git push target-origin $SOURCE_BRANCH:$TARGET_BRANCH git push target-origin $SOURCE_BRANCH:$TARGET_BRANCH
PR_TITLE=${{secrets.SYNC_PR_TITLE}}
gh pr create \
--base $TARGET_BASE_BRANCH \
--head $TARGET_BRANCH \
--title "$PR_TITLE" \
--repo $TARGET_REPO

View File

@ -999,11 +999,18 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView):
def get(self, request): def get(self, request):
files = [] files = []
s3 = boto3.client( s3_client_params = {
"s3", "service_name": "s3",
aws_access_key_id=settings.AWS_ACCESS_KEY_ID, "aws_access_key_id": settings.AWS_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, "aws_secret_access_key": settings.AWS_SECRET_ACCESS_KEY,
) }
# Use AWS_S3_ENDPOINT_URL if it is present in the settings
if hasattr(settings, "AWS_S3_ENDPOINT_URL") and settings.AWS_S3_ENDPOINT_URL:
s3_client_params["endpoint_url"] = settings.AWS_S3_ENDPOINT_URL
s3 = boto3.client(**s3_client_params)
params = { params = {
"Bucket": settings.AWS_STORAGE_BUCKET_NAME, "Bucket": settings.AWS_STORAGE_BUCKET_NAME,
"Prefix": "static/project-cover/", "Prefix": "static/project-cover/",
@ -1016,6 +1023,16 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView):
if not content["Key"].endswith( if not content["Key"].endswith(
"/" "/"
): # This line ensures we're only getting files, not "sub-folders" ): # This line ensures we're only getting files, not "sub-folders"
if (
hasattr(settings, "AWS_S3_CUSTOM_DOMAIN")
and settings.AWS_S3_CUSTOM_DOMAIN
and hasattr(settings, "AWS_S3_URL_PROTOCOL")
and settings.AWS_S3_URL_PROTOCOL
):
files.append(
f"{settings.AWS_S3_URL_PROTOCOL}//{settings.AWS_S3_CUSTOM_DOMAIN}/{content['Key']}"
)
else:
files.append( files.append(
f"https://{settings.AWS_STORAGE_BUCKET_NAME}.s3.{settings.AWS_REGION}.amazonaws.com/{content['Key']}" f"https://{settings.AWS_STORAGE_BUCKET_NAME}.s3.{settings.AWS_REGION}.amazonaws.com/{content['Key']}"
) )

View File

@ -30,7 +30,7 @@ openpyxl==3.1.2
beautifulsoup4==4.12.2 beautifulsoup4==4.12.2
dj-database-url==2.1.0 dj-database-url==2.1.0
posthog==3.0.2 posthog==3.0.2
cryptography==41.0.5 cryptography==41.0.6
lxml==4.9.3 lxml==4.9.3
boto3==1.28.40 boto3==1.28.40

View File

@ -39,7 +39,7 @@ function download(){
echo "" echo ""
echo "Latest version is now available for you to use" echo "Latest version is now available for you to use"
echo "" echo ""
echo "In case of Upgrade, your new setting file is availabe as 'variables-upgrade.env'. Please compare and set the required values in '.env 'file." echo "In case of Upgrade, your new setting file is available as 'variables-upgrade.env'. Please compare and set the required values in '.env 'file."
echo "" echo ""
} }

View File

@ -19,7 +19,7 @@ type Props = {
icon?: any; icon?: any;
text: string; text: string;
onClick: () => void; onClick: () => void;
}; } | null;
disabled?: boolean; disabled?: boolean;
}; };

View File

@ -1,14 +1,14 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR from "swr"; import { observer } from "mobx-react-lite";
// react hook form
import { SubmitHandler, useForm } from "react-hook-form"; import { SubmitHandler, useForm } from "react-hook-form";
// headless ui
import { Combobox, Dialog, Transition } from "@headlessui/react"; import { Combobox, Dialog, Transition } from "@headlessui/react";
import useSWR from "swr";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast";
// services // services
import { IssueService } from "services/issue"; import { IssueService } from "services/issue";
// hooks
import useToast from "hooks/use-toast";
// ui // ui
import { Button, LayersIcon } from "@plane/ui"; import { Button, LayersIcon } from "@plane/ui";
// icons // icons
@ -30,17 +30,25 @@ type Props = {
const issueService = new IssueService(); const issueService = new IssueService();
export const BulkDeleteIssuesModal: React.FC<Props> = (props) => { export const BulkDeleteIssuesModal: React.FC<Props> = observer((props) => {
const { isOpen, onClose } = props; const { isOpen, onClose } = props;
// states
const [query, setQuery] = useState("");
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
// states // store hooks
const [query, setQuery] = useState(""); const {
user: { hasPermissionToCurrentProject },
} = useMobxStore();
// fetching project issues. // fetching project issues.
const { data: issues } = useSWR( const { data: issues } = useSWR(
workspaceSlug && projectId ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) : null, workspaceSlug && projectId && hasPermissionToCurrentProject
workspaceSlug && projectId ? () => issueService.getIssues(workspaceSlug as string, projectId as string) : null ? PROJECT_ISSUES_LIST(workspaceSlug.toString(), projectId.toString())
: null,
workspaceSlug && projectId && hasPermissionToCurrentProject
? () => issueService.getIssues(workspaceSlug.toString(), projectId.toString())
: null
); );
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
@ -222,4 +230,4 @@ export const BulkDeleteIssuesModal: React.FC<Props> = (props) => {
</Dialog> </Dialog>
</Transition.Root> </Transition.Root>
); );
}; });

View File

@ -50,8 +50,8 @@ export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, handleEdit
</Tooltip> </Tooltip>
</div> </div>
{!isNotAllowed && (
<div className="z-[1] flex flex-shrink-0 items-center gap-2"> <div className="z-[1] flex flex-shrink-0 items-center gap-2">
{!isNotAllowed && (
<button <button
type="button" type="button"
className="flex items-center justify-center p-1 hover:bg-custom-background-80" className="flex items-center justify-center p-1 hover:bg-custom-background-80"
@ -63,6 +63,7 @@ export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, handleEdit
> >
<Pencil className="h-3 w-3 stroke-[1.5] text-custom-text-200" /> <Pencil className="h-3 w-3 stroke-[1.5] text-custom-text-200" />
</button> </button>
)}
<a <a
href={link.url} href={link.url}
target="_blank" target="_blank"
@ -71,6 +72,7 @@ export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, handleEdit
> >
<ExternalLinkIcon className="h-3 w-3 stroke-[1.5] text-custom-text-200" /> <ExternalLinkIcon className="h-3 w-3 stroke-[1.5] text-custom-text-200" />
</a> </a>
{!isNotAllowed && (
<button <button
type="button" type="button"
className="flex items-center justify-center p-1 hover:bg-custom-background-80" className="flex items-center justify-center p-1 hover:bg-custom-background-80"
@ -82,9 +84,9 @@ export const LinksList: React.FC<Props> = ({ links, handleDeleteLink, handleEdit
> >
<Trash2 className="h-3 w-3" /> <Trash2 className="h-3 w-3" />
</button> </button>
</div>
)} )}
</div> </div>
</div>
<div className="px-5"> <div className="px-5">
<p className="mt-0.5 stroke-[1.5] text-xs text-custom-text-300"> <p className="mt-0.5 stroke-[1.5] text-xs text-custom-text-300">
Added {timeAgo(link.created_at)} Added {timeAgo(link.created_at)}

View File

@ -14,6 +14,7 @@ import { SingleProgressStats } from "components/core";
import { Avatar, StateGroupIcon } from "@plane/ui"; import { Avatar, StateGroupIcon } from "@plane/ui";
// types // types
import { import {
IIssueFilterOptions,
IModule, IModule,
TAssigneesDistribution, TAssigneesDistribution,
TCompletionChartDistribution, TCompletionChartDistribution,
@ -35,6 +36,9 @@ type Props = {
roundedTab?: boolean; roundedTab?: boolean;
noBackground?: boolean; noBackground?: boolean;
isPeekView?: boolean; isPeekView?: boolean;
isCompleted?: boolean;
filters?: IIssueFilterOptions;
handleFiltersUpdate: (key: keyof IIssueFilterOptions, value: string | string[]) => void;
}; };
export const SidebarProgressStats: React.FC<Props> = ({ export const SidebarProgressStats: React.FC<Props> = ({
@ -44,7 +48,10 @@ export const SidebarProgressStats: React.FC<Props> = ({
module, module,
roundedTab, roundedTab,
noBackground, noBackground,
isCompleted = false,
isPeekView = false, isPeekView = false,
filters,
handleFiltersUpdate,
}) => { }) => {
const { storedValue: tab, setValue: setTab } = useLocalStorage("tab", "Assignees"); const { storedValue: tab, setValue: setTab } = useLocalStorage("tab", "Assignees");
@ -140,19 +147,10 @@ export const SidebarProgressStats: React.FC<Props> = ({
} }
completed={assignee.completed_issues} completed={assignee.completed_issues}
total={assignee.total_issues} total={assignee.total_issues}
{...(!isPeekView && { {...(!isPeekView &&
onClick: () => { !isCompleted && {
// TODO: set filters here onClick: () => handleFiltersUpdate("assignees", assignee.assignee_id ?? ""),
// if (filters?.assignees?.includes(assignee.assignee_id ?? "")) selected: filters?.assignees?.includes(assignee.assignee_id ?? ""),
// setFilters({
// assignees: filters?.assignees?.filter((a) => a !== assignee.assignee_id),
// });
// else
// setFilters({
// assignees: [...(filters?.assignees ?? []), assignee.assignee_id ?? ""],
// });
},
// selected: filters?.assignees?.includes(assignee.assignee_id ?? ""),
})} })}
/> />
); );
@ -200,16 +198,10 @@ export const SidebarProgressStats: React.FC<Props> = ({
} }
completed={label.completed_issues} completed={label.completed_issues}
total={label.total_issues} total={label.total_issues}
{...(!isPeekView && { {...(!isPeekView &&
// TODO: set filters here !isCompleted && {
onClick: () => { onClick: () => handleFiltersUpdate("labels", label.label_id ?? ""),
// if (filters.labels?.includes(label.label_id ?? "")) selected: filters?.labels?.includes(label.label_id ?? `no-label-${index}`),
// setFilters({
// labels: filters?.labels?.filter((l) => l !== label.label_id),
// });
// else setFilters({ labels: [...(filters?.labels ?? []), label.label_id ?? ""] });
},
// selected: filters?.labels?.includes(label.label_id ?? ""),
})} })}
/> />
)) ))

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } 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 { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
@ -28,7 +28,8 @@ import {
renderShortMonthDate, renderShortMonthDate,
} from "helpers/date-time.helper"; } from "helpers/date-time.helper";
// types // types
import { ICycle } from "types"; import { ICycle, IIssueFilterOptions } from "types";
import { EFilterType } from "store/issues/types";
// constants // constants
import { EUserWorkspaceRoles } from "constants/workspace"; import { EUserWorkspaceRoles } from "constants/workspace";
// fetch-keys // fetch-keys
@ -52,12 +53,20 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId, peekCycle } = router.query; const { workspaceSlug, projectId, peekCycle } = router.query;
// store hooks // store hooks
const { const {
<<<<<<< HEAD
eventTracker: { setTrackElement }, eventTracker: { setTrackElement },
} = useApplication(); } = useApplication();
const { const {
membership: { currentProjectRole }, membership: { currentProjectRole },
} = useUser(); } = useUser();
const { getCycleById, updateCycleDetails } = useCycle(); const { getCycleById, updateCycleDetails } = useCycle();
=======
cycle: cycleDetailsStore,
cycleIssuesFilter: { issueFilters, updateFilters },
trackEvent: { setTrackElement },
user: { currentProjectRole },
} = useMobxStore();
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
const cycleDetails = getCycleById(cycleId); const cycleDetails = getCycleById(cycleId);
@ -246,6 +255,25 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
} }
}; };
const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !projectId) return;
const newValues = issueFilters?.filters?.[key] ?? [];
if (Array.isArray(value)) {
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
});
} else {
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
}
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, { [key]: newValues }, cycleId);
},
[workspaceSlug, projectId, cycleId, issueFilters, updateFilters]
);
const cycleStatus = const cycleStatus =
cycleDetails?.start_date && cycleDetails?.end_date cycleDetails?.start_date && cycleDetails?.end_date
? getDateRangeStatus(cycleDetails?.start_date, cycleDetails?.end_date) ? getDateRangeStatus(cycleDetails?.start_date, cycleDetails?.end_date)
@ -539,6 +567,9 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
}} }}
totalIssues={cycleDetails.total_issues} totalIssues={cycleDetails.total_issues}
isPeekView={Boolean(peekCycle)} isPeekView={Boolean(peekCycle)}
isCompleted={isCompleted}
filters={issueFilters?.filters}
handleFiltersUpdate={handleFiltersUpdate}
/> />
</div> </div>
)} )}

View File

@ -20,6 +20,7 @@ export interface EmailFormValues {
EMAIL_HOST_PASSWORD: string; EMAIL_HOST_PASSWORD: string;
EMAIL_USE_TLS: string; EMAIL_USE_TLS: string;
// EMAIL_USE_SSL: string; // EMAIL_USE_SSL: string;
EMAIL_FROM: string;
} }
export const InstanceEmailForm: FC<IInstanceEmailForm> = (props) => { export const InstanceEmailForm: FC<IInstanceEmailForm> = (props) => {
@ -44,6 +45,7 @@ export const InstanceEmailForm: FC<IInstanceEmailForm> = (props) => {
EMAIL_HOST_PASSWORD: config["EMAIL_HOST_PASSWORD"], EMAIL_HOST_PASSWORD: config["EMAIL_HOST_PASSWORD"],
EMAIL_USE_TLS: config["EMAIL_USE_TLS"], EMAIL_USE_TLS: config["EMAIL_USE_TLS"],
// EMAIL_USE_SSL: config["EMAIL_USE_SSL"], // EMAIL_USE_SSL: config["EMAIL_USE_SSL"],
EMAIL_FROM: config["EMAIL_FROM"],
}, },
}); });
@ -167,6 +169,31 @@ export const InstanceEmailForm: FC<IInstanceEmailForm> = (props) => {
</div> </div>
</div> </div>
</div> </div>
<div className="grid-col grid w-full max-w-4xl grid-cols-1 items-center justify-between gap-x-16 gap-y-8 lg:grid-cols-2">
<div className="flex flex-col gap-1">
<h4 className="text-sm">From address</h4>
<Controller
control={control}
name="EMAIL_FROM"
render={({ field: { value, onChange, ref } }) => (
<Input
id="EMAIL_FROM"
name="EMAIL_FROM"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.EMAIL_FROM)}
placeholder="no-reply@projectplane.so"
className="w-full rounded-md font-medium"
/>
)}
/>
<p className="text-xs text-custom-text-400">
You will have to verify your email address to being sending emails.
</p>
</div>
</div>
<div className="flex w-full max-w-md flex-col gap-y-8 px-1"> <div className="flex w-full max-w-md flex-col gap-y-8 px-1">
<div className="mr-8 flex items-center gap-10 pt-4"> <div className="mr-8 flex items-center gap-10 pt-4">

View File

@ -1,4 +1,4 @@
import { useState } from "react"; import React, { useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
@ -24,7 +24,14 @@ import { IIssueAttachment } from "types";
const issueAttachmentService = new IssueAttachmentService(); const issueAttachmentService = new IssueAttachmentService();
const projectMemberService = new ProjectMemberService(); const projectMemberService = new ProjectMemberService();
export const IssueAttachments = () => { type Props = {
editable: boolean;
};
export const IssueAttachments: React.FC<Props> = (props) => {
const { editable } = props;
// states
const [deleteAttachment, setDeleteAttachment] = useState<IIssueAttachment | null>(null); const [deleteAttachment, setDeleteAttachment] = useState<IIssueAttachment | null>(null);
const [attachmentDeleteModal, setAttachmentDeleteModal] = useState<boolean>(false); const [attachmentDeleteModal, setAttachmentDeleteModal] = useState<boolean>(false);
@ -86,6 +93,7 @@ export const IssueAttachments = () => {
</div> </div>
</Link> </Link>
{editable && (
<button <button
onClick={() => { onClick={() => {
setDeleteAttachment(file); setDeleteAttachment(file);
@ -94,6 +102,7 @@ export const IssueAttachments = () => {
> >
<X className="h-4 w-4 text-custom-text-200 hover:text-custom-text-100" /> <X className="h-4 w-4 text-custom-text-200 hover:text-custom-text-100" />
</button> </button>
)}
</div> </div>
))} ))}
</> </>

View File

@ -135,7 +135,9 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
debouncedFormSave(); debouncedFormSave();
}} }}
required required
className="min-h-min block w-full resize-none overflow-hidden rounded border-none bg-transparent px-3 py-2 text-2xl font-medium outline-none ring-0 focus:ring-1 focus:ring-custom-primary" className={`min-h-min block w-full resize-none overflow-hidden rounded border-none bg-transparent px-3 py-2 text-2xl font-medium outline-none ring-0 focus:ring-1 focus:ring-custom-primary ${
!isAllowed ? "hover:cursor-not-allowed" : ""
}`}
hasError={Boolean(errors?.description)} hasError={Boolean(errors?.description)}
role="textbox" role="textbox"
disabled={!isAllowed} disabled={!isAllowed}
@ -170,7 +172,9 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
setShouldShowAlert={setShowAlert} setShouldShowAlert={setShowAlert}
setIsSubmitting={setIsSubmitting} setIsSubmitting={setIsSubmitting}
dragDropEnabled dragDropEnabled
customClassName={isAllowed ? "min-h-[150px] shadow-sm" : "!p-0 !pt-2 text-custom-text-200"} customClassName={
isAllowed ? "min-h-[150px] shadow-sm" : "!p-0 !pt-2 text-custom-text-200 pointer-events-none"
}
noBorder={!isAllowed} noBorder={!isAllowed}
onChange={(description: Object, description_html: string) => { onChange={(description: Object, description_html: string) => {
setShowAlert(true); setShowAlert(true);

View File

@ -224,6 +224,7 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
reset({ reset({
...defaultValues, ...defaultValues,
...initialData, ...initialData,
project: projectId,
}); });
}, [setFocus, initialData, reset]); }, [setFocus, initialData, reset]);

View File

@ -120,8 +120,8 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
workspaceSlug={workspaceSlug.toString()} workspaceSlug={workspaceSlug.toString()}
projectId={peekProjectId.toString()} projectId={peekProjectId.toString()}
issueId={peekIssueId.toString()} issueId={peekIssueId.toString()}
handleIssue={async (issueToUpdate) => handleIssue={async (issueToUpdate, action: EIssueActions) =>
await handleIssues(issueToUpdate.target_date ?? "", issueToUpdate as IIssue, EIssueActions.UPDATE) await handleIssues(issueToUpdate.target_date ?? "", issueToUpdate as IIssue, action)
} }
/> />
)} )}

View File

@ -9,7 +9,14 @@ import { Tooltip } from "@plane/ui";
import useOutsideClickDetector from "hooks/use-outside-click-detector"; import useOutsideClickDetector from "hooks/use-outside-click-detector";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
<<<<<<< HEAD
import { IIssueResponse } from "store_legacy/issues/types"; import { IIssueResponse } from "store_legacy/issues/types";
=======
import { IIssueResponse } from "store/issues/types";
import { useMobxStore } from "lib/mobx/store-provider";
// constants
import { EUserWorkspaceRoles } from "constants/workspace";
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
type Props = { type Props = {
issues: IIssueResponse | undefined; issues: IIssueResponse | undefined;
@ -26,15 +33,24 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
// states // states
const [isMenuActive, setIsMenuActive] = useState(false); const [isMenuActive, setIsMenuActive] = useState(false);
// mobx store
const {
user: { currentProjectRole },
} = useMobxStore();
const menuActionRef = useRef<HTMLDivElement | null>(null); const menuActionRef = useRef<HTMLDivElement | null>(null);
const handleIssuePeekOverview = (issue: IIssue) => { const handleIssuePeekOverview = (issue: IIssue, event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const { query } = router; const { query } = router;
if (event.ctrlKey || event.metaKey) {
const issueUrl = `/${issue.workspace_detail.slug}/projects/${issue.project_detail.id}/issues/${issue?.id}`;
window.open(issueUrl, "_blank"); // Open link in a new tab
} else {
router.push({ router.push({
pathname: router.pathname, pathname: router.pathname,
query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project }, query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project },
}); });
}
}; };
useOutsideClickDetector(menuActionRef, () => setIsMenuActive(false)); useOutsideClickDetector(menuActionRef, () => setIsMenuActive(false));
@ -51,6 +67,8 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
</div> </div>
); );
const isEditable = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
return ( return (
<> <>
{issueIdList?.slice(0, showAllIssues ? issueIdList.length : 4).map((issueId, index) => { {issueIdList?.slice(0, showAllIssues ? issueIdList.length : 4).map((issueId, index) => {
@ -58,14 +76,14 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
const issue = issues?.[issueId]; const issue = issues?.[issueId];
return ( return (
<Draggable key={issue.id} draggableId={issue.id} index={index}> <Draggable key={issue.id} draggableId={issue.id} index={index} isDragDisabled={!isEditable}>
{(provided, snapshot) => ( {(provided, snapshot) => (
<div <div
className="relative cursor-pointer p-1 px-2" className="relative cursor-pointer p-1 px-2"
{...provided.draggableProps} {...provided.draggableProps}
{...provided.dragHandleProps} {...provided.dragHandleProps}
ref={provided.innerRef} ref={provided.innerRef}
onClick={() => handleIssuePeekOverview(issue)} onClick={(e) => handleIssuePeekOverview(issue, e)}
> >
{issue?.tempId !== undefined && ( {issue?.tempId !== undefined && (
<div className="absolute left-0 top-0 z-[99999] h-full w-full animate-pulse bg-custom-background-100/20" /> <div className="absolute left-0 top-0 z-[99999] h-full w-full animate-pulse bg-custom-background-100/20" />

View File

@ -34,14 +34,18 @@ export const ProjectEmptyState: React.FC = observer(() => {
description: description:
"Redesign the Plane UI, Rebrand the company, or Launch the new fuel injection system are examples of issues that likely have sub-issues.", "Redesign the Plane UI, Rebrand the company, or Launch the new fuel injection system are examples of issues that likely have sub-issues.",
}} }}
primaryButton={{ primaryButton={
isEditingAllowed
? {
text: "Create your first issue", text: "Create your first issue",
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />, icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
onClick: () => { onClick: () => {
setTrackElement("PROJECT_EMPTY_STATE"); setTrackElement("PROJECT_EMPTY_STATE");
commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.PROJECT); commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.PROJECT);
}, },
}} }
: null
}
disabled={!isEditingAllowed} disabled={!isEditingAllowed}
/> />
</div> </div>

View File

@ -1,4 +1,4 @@
import React from "react"; import React, { useCallback } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// hooks // hooks
@ -23,9 +23,17 @@ import {
IProjectIssuesStore, IProjectIssuesStore,
IViewIssuesFilterStore, IViewIssuesFilterStore,
IViewIssuesStore, IViewIssuesStore,
<<<<<<< HEAD
} from "store_legacy/issues"; } from "store_legacy/issues";
import { TUnGroupedIssues } from "store_legacy/issues/types"; import { TUnGroupedIssues } from "store_legacy/issues/types";
import { EUserProjectRoles } from "constants/project"; import { EUserProjectRoles } from "constants/project";
=======
} from "store/issues";
import { TUnGroupedIssues } from "store/issues/types";
import { EIssueActions } from "../types";
// constants
import { EUserWorkspaceRoles } from "constants/workspace";
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
interface IBaseGanttRoot { interface IBaseGanttRoot {
issueFiltersStore: issueFiltersStore:
@ -35,11 +43,21 @@ interface IBaseGanttRoot {
| IViewIssuesFilterStore; | IViewIssuesFilterStore;
issueStore: IProjectIssuesStore | IModuleIssuesStore | ICycleIssuesStore | IViewIssuesStore; issueStore: IProjectIssuesStore | IModuleIssuesStore | ICycleIssuesStore | IViewIssuesStore;
viewId?: string; viewId?: string;
issueActions: {
[EIssueActions.DELETE]: (issue: IIssue) => Promise<void>;
[EIssueActions.UPDATE]?: (issue: IIssue) => Promise<void>;
[EIssueActions.REMOVE]?: (issue: IIssue) => Promise<void>;
};
} }
export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGanttRoot) => { export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGanttRoot) => {
<<<<<<< HEAD
const { issueFiltersStore, issueStore, viewId } = props; const { issueFiltersStore, issueStore, viewId } = props;
// router // router
=======
const { issueFiltersStore, issueStore, viewId, issueActions } = props;
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
const router = useRouter(); const router = useRouter();
const { workspaceSlug, peekIssueId, peekProjectId } = router.query; const { workspaceSlug, peekIssueId, peekProjectId } = router.query;
// store hooks // store hooks
@ -64,11 +82,14 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
await issueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, payload, viewId); await issueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, payload, viewId);
}; };
const updateIssue = async (projectId: string, issueId: string, payload: Partial<IIssue>) => { const handleIssues = useCallback(
if (!workspaceSlug) return; async (issue: IIssue, action: EIssueActions) => {
if (issueActions[action]) {
await issueStore.updateIssue(workspaceSlug.toString(), projectId, issueId, payload, viewId); await issueActions[action]!(issue);
}; }
},
[issueActions]
);
const isAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; const isAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
@ -102,8 +123,8 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
workspaceSlug={workspaceSlug.toString()} workspaceSlug={workspaceSlug.toString()}
projectId={peekProjectId.toString()} projectId={peekProjectId.toString()}
issueId={peekIssueId.toString()} issueId={peekIssueId.toString()}
handleIssue={async (issueToUpdate) => { handleIssue={async (issueToUpdate, action) => {
await updateIssue(peekProjectId.toString(), peekIssueId.toString(), issueToUpdate); await handleIssues(issueToUpdate as IIssue, action);
}} }}
/> />
)} )}

View File

@ -9,13 +9,17 @@ import { IIssue } from "types";
export const IssueGanttBlock = ({ data }: { data: IIssue }) => { export const IssueGanttBlock = ({ data }: { data: IIssue }) => {
const router = useRouter(); const router = useRouter();
const handleIssuePeekOverview = () => { const handleIssuePeekOverview = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const { query } = router; const { query } = router;
if (event.ctrlKey || event.metaKey) {
const issueUrl = `/${data?.workspace_detail.slug}/projects/${data?.project_detail.id}/issues/${data?.id}`;
window.open(issueUrl, "_blank"); // Open link in a new tab
} else {
router.push({ router.push({
pathname: router.pathname, pathname: router.pathname,
query: { ...query, peekIssueId: data?.id, peekProjectId: data?.project }, query: { ...query, peekIssueId: data?.id, peekProjectId: data?.project },
}); });
}
}; };
return ( return (

View File

@ -4,15 +4,43 @@ import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { BaseGanttRoot } from "./base-gantt-root"; import { BaseGanttRoot } from "./base-gantt-root";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// types
import { EIssueActions } from "../types";
import { IIssue } from "types";
export const CycleGanttLayout: React.FC = observer(() => { export const CycleGanttLayout: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { cycleId } = router.query; const { cycleId, workspaceSlug } = router.query;
const { cycleIssues: cycleIssueStore, cycleIssuesFilter: cycleIssueFilterStore } = useMobxStore(); const { cycleIssues: cycleIssueStore, cycleIssuesFilter: cycleIssueFilterStore } = useMobxStore();
const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug || !cycleId) return;
await cycleIssueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue, cycleId.toString());
},
[EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug || !cycleId) return;
await cycleIssueStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id, cycleId.toString());
},
[EIssueActions.REMOVE]: async (issue: IIssue) => {
if (!workspaceSlug || !cycleId || !issue.bridge_id) return;
await cycleIssueStore.removeIssueFromCycle(
workspaceSlug.toString(),
issue.project,
cycleId.toString(),
issue.id,
issue.bridge_id
);
},
};
return ( return (
<BaseGanttRoot <BaseGanttRoot
issueActions={issueActions}
issueFiltersStore={cycleIssueFilterStore} issueFiltersStore={cycleIssueFilterStore}
issueStore={cycleIssueStore} issueStore={cycleIssueStore}
viewId={cycleId?.toString()} viewId={cycleId?.toString()}

View File

@ -4,15 +4,43 @@ import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { BaseGanttRoot } from "./base-gantt-root"; import { BaseGanttRoot } from "./base-gantt-root";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// types
import { EIssueActions } from "../types";
import { IIssue } from "types";
export const ModuleGanttLayout: React.FC = observer(() => { export const ModuleGanttLayout: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { moduleId } = router.query; const { moduleId, workspaceSlug } = router.query;
const { moduleIssues: moduleIssueStore, moduleIssuesFilter: moduleIssueFilterStore } = useMobxStore(); const { moduleIssues: moduleIssueStore, moduleIssuesFilter: moduleIssueFilterStore } = useMobxStore();
const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug || !moduleId) return;
await moduleIssueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue, moduleId.toString());
},
[EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug || !moduleId) return;
await moduleIssueStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id, moduleId.toString());
},
[EIssueActions.REMOVE]: async (issue: IIssue) => {
if (!workspaceSlug || !moduleId || !issue.bridge_id) return;
await moduleIssueStore.removeIssueFromModule(
workspaceSlug.toString(),
issue.project,
moduleId.toString(),
issue.id,
issue.bridge_id
);
},
};
return ( return (
<BaseGanttRoot <BaseGanttRoot
issueActions={issueActions}
issueFiltersStore={moduleIssueFilterStore} issueFiltersStore={moduleIssueFilterStore}
issueStore={moduleIssueStore} issueStore={moduleIssueStore}
viewId={moduleId?.toString()} viewId={moduleId?.toString()}

View File

@ -1,12 +1,36 @@
import React from "react"; import React from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// hooks // hooks
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { BaseGanttRoot } from "./base-gantt-root"; import { BaseGanttRoot } from "./base-gantt-root";
// types
import { EIssueActions } from "../types";
import { IIssue } from "types";
export const GanttLayout: React.FC = observer(() => { export const GanttLayout: React.FC = observer(() => {
const { projectIssues: projectIssuesStore, projectIssuesFilter: projectIssueFiltersStore } = useMobxStore(); const { projectIssues: projectIssuesStore, projectIssuesFilter: projectIssueFiltersStore } = useMobxStore();
const router = useRouter();
const { workspaceSlug } = router.query;
return <BaseGanttRoot issueFiltersStore={projectIssueFiltersStore} issueStore={projectIssuesStore} />; const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug) return;
await projectIssuesStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
},
[EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug) return;
await projectIssuesStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id);
},
};
return (
<BaseGanttRoot
issueActions={issueActions}
issueFiltersStore={projectIssueFiltersStore}
issueStore={projectIssuesStore}
/>
);
}); });

View File

@ -1,11 +1,35 @@
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { BaseGanttRoot } from "./base-gantt-root"; import { BaseGanttRoot } from "./base-gantt-root";
// types
import { EIssueActions } from "../types";
import { IIssue } from "types";
export const ProjectViewGanttLayout: React.FC = observer(() => { export const ProjectViewGanttLayout: React.FC = observer(() => {
const { viewIssues: projectIssueViewStore, viewIssuesFilter: projectIssueViewFiltersStore } = useMobxStore(); const { viewIssues: projectIssueViewStore, viewIssuesFilter: projectIssueViewFiltersStore } = useMobxStore();
const router = useRouter();
const { workspaceSlug } = router.query;
return <BaseGanttRoot issueFiltersStore={projectIssueViewFiltersStore} issueStore={projectIssueViewStore} />; const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug) return;
await projectIssueViewStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
},
[EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug) return;
await projectIssueViewStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id);
},
};
return (
<BaseGanttRoot
issueActions={issueActions}
issueFiltersStore={projectIssueViewFiltersStore}
issueStore={projectIssueViewStore}
/>
);
}); });

View File

@ -277,7 +277,13 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
workspaceSlug={workspaceSlug.toString()} workspaceSlug={workspaceSlug.toString()}
projectId={peekProjectId.toString()} projectId={peekProjectId.toString()}
issueId={peekIssueId.toString()} issueId={peekIssueId.toString()}
<<<<<<< HEAD
handleIssue={async (issueToUpdate) => await handleIssues(issueToUpdate as IIssue, EIssueActions.UPDATE)} handleIssue={async (issueToUpdate) => await handleIssues(issueToUpdate as IIssue, EIssueActions.UPDATE)}
=======
handleIssue={async (issueToUpdate, action: EIssueActions) =>
await handleIssues(sub_group_by, group_by, issueToUpdate as IIssue, action)
}
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
/> />
)} )}
</> </>

View File

@ -1,5 +1,10 @@
import { memo } from "react"; import { memo } from "react";
<<<<<<< HEAD
import { Draggable } from "@hello-pangea/dnd"; import { Draggable } from "@hello-pangea/dnd";
=======
import { Draggable, DraggableStateSnapshot } from "@hello-pangea/dnd";
import isEqual from "lodash/isEqual";
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
// components // components
import { IssueProperties } from "../properties/all-properties"; import { IssueProperties } from "../properties/all-properties";
// ui // ui
@ -47,10 +52,27 @@ interface IssueDetailsBlockProps {
handleIssues: (issue: IIssue, action: EIssueActions) => void; handleIssues: (issue: IIssue, action: EIssueActions) => void;
quickActions: (issue: IIssue) => React.ReactNode; quickActions: (issue: IIssue) => React.ReactNode;
isReadOnly: boolean; isReadOnly: boolean;
snapshot: DraggableStateSnapshot;
isDragDisabled: boolean;
} }
const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = (props) => { const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = (props) => {
<<<<<<< HEAD
const { issue, showEmptyGroup, handleIssues, quickActions, isReadOnly, issuesFilter } = props; const { issue, showEmptyGroup, handleIssues, quickActions, isReadOnly, issuesFilter } = props;
=======
const {
sub_group_id,
columnId,
issue,
showEmptyGroup,
handleIssues,
quickActions,
displayProperties,
isReadOnly,
snapshot,
isDragDisabled,
} = props;
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
const router = useRouter(); const router = useRouter();
@ -58,29 +80,43 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = (props) => {
if (issueToUpdate) handleIssues(issueToUpdate, EIssueActions.UPDATE); if (issueToUpdate) handleIssues(issueToUpdate, EIssueActions.UPDATE);
}; };
const handleIssuePeekOverview = () => { const handleIssuePeekOverview = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const { query } = router; const { query } = router;
if (event.ctrlKey || event.metaKey) {
const issueUrl = `/${issue.workspace_detail.slug}/projects/${issue.project_detail.id}/issues/${issue?.id}`;
window.open(issueUrl, "_blank"); // Open link in a new tab
} else {
router.push({ router.push({
pathname: router.pathname, pathname: router.pathname,
query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project }, query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project },
}); });
}
}; };
return ( return (
<<<<<<< HEAD
<> <>
<WithDisplayPropertiesHOC issuesFilter={issuesFilter} displayPropertyKey="key"> <WithDisplayPropertiesHOC issuesFilter={issuesFilter} displayPropertyKey="key">
<div className="relative"> <div className="relative">
<div className="line-clamp-1 text-xs text-custom-text-300"> <div className="line-clamp-1 text-xs text-custom-text-300">
=======
<div
className={`flex flex-col space-y-2 cursor-pointer rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 px-3 py-2 text-sm shadow-custom-shadow-2xs transition-all w-full ${
isDragDisabled ? "" : "hover:cursor-grab"
} ${snapshot.isDragging ? `border-custom-primary-100` : `border-transparent`}`}
onClick={handleIssuePeekOverview}
>
{displayProperties && displayProperties?.key && (
<div className="relative w-full ">
<div className="line-clamp-1 text-xs text-left text-custom-text-300">
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
{issue.project_detail.identifier}-{issue.sequence_id} {issue.project_detail.identifier}-{issue.sequence_id}
</div> </div>
<div className="absolute -top-1 right-0 hidden group-hover/kanban-block:block">{quickActions(issue)}</div> <div className="absolute -top-1 right-0 hidden group-hover/kanban-block:block">{quickActions(issue)}</div>
</div> </div>
</WithDisplayPropertiesHOC> </WithDisplayPropertiesHOC>
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}> <Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
<div className="line-clamp-2 text-sm font-medium text-custom-text-100" onClick={handleIssuePeekOverview}> <div className="line-clamp-2 text-sm font-medium text-custom-text-100">{issue.name}</div>
{issue.name}
</div>
</Tooltip> </Tooltip>
<div> <div>
<IssueProperties <IssueProperties
@ -91,7 +127,7 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = (props) => {
isReadOnly={isReadOnly} isReadOnly={isReadOnly}
/> />
</div> </div>
</> </div>
); );
}; };
@ -119,10 +155,10 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
return ( return (
<> <>
<Draggable draggableId={draggableId} index={index}> <Draggable draggableId={draggableId} index={index} isDragDisabled={!canEditIssueProperties}>
{(provided, snapshot) => ( {(provided, snapshot) => (
<div <div
className="group/kanban-block relative p-1.5 hover:cursor-default" className="group/kanban-block relative p-1.5"
{...provided.draggableProps} {...provided.draggableProps}
{...provided.dragHandleProps} {...provided.dragHandleProps}
ref={provided.innerRef} ref={provided.innerRef}
@ -130,6 +166,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
{issue.tempId !== undefined && ( {issue.tempId !== undefined && (
<div className="absolute left-0 top-0 z-[99999] h-full w-full animate-pulse bg-custom-background-100/20" /> <div className="absolute left-0 top-0 z-[99999] h-full w-full animate-pulse bg-custom-background-100/20" />
)} )}
<<<<<<< HEAD
<div <div
className={`space-y-2 rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 px-3 py-2 text-sm shadow-custom-shadow-2xs transition-all ${ className={`space-y-2 rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 px-3 py-2 text-sm shadow-custom-shadow-2xs transition-all ${
isDragDisabled ? "" : "hover:cursor-grab" isDragDisabled ? "" : "hover:cursor-grab"
@ -144,6 +181,20 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
isReadOnly={!canEditIssueProperties} isReadOnly={!canEditIssueProperties}
/> />
</div> </div>
=======
<KanbanIssueMemoBlock
sub_group_id={sub_group_id}
columnId={columnId}
issue={issue}
showEmptyGroup={showEmptyGroup}
handleIssues={handleIssues}
quickActions={quickActions}
displayProperties={displayProperties}
isReadOnly={!canEditIssueProperties}
snapshot={snapshot}
isDragDisabled={isDragDisabled}
/>
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
</div> </div>
)} )}
</Draggable> </Draggable>

View File

@ -168,7 +168,9 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
workspaceSlug={workspaceSlug.toString()} workspaceSlug={workspaceSlug.toString()}
projectId={peekProjectId.toString()} projectId={peekProjectId.toString()}
issueId={peekIssueId.toString()} issueId={peekIssueId.toString()}
handleIssue={async (issueToUpdate) => await handleIssues(issueToUpdate as IIssue, EIssueActions.UPDATE)} handleIssue={async (issueToUpdate, action: EIssueActions) =>
await handleIssues(issueToUpdate as IIssue, action)
}
/> />
)} )}
</> </>

View File

@ -25,20 +25,27 @@ export const IssueBlock: React.FC<IssueBlockProps> = (props) => {
handleIssues(issueToUpdate, EIssueActions.UPDATE); handleIssues(issueToUpdate, EIssueActions.UPDATE);
}; };
const handleIssuePeekOverview = () => { const handleIssuePeekOverview = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
const { query } = router; const { query } = router;
if (event.ctrlKey || event.metaKey) {
const issueUrl = `/${issue.workspace_detail.slug}/projects/${issue.project_detail.id}/issues/${issue?.id}`;
window.open(issueUrl, "_blank"); // Open link in a new tab
} else {
router.push({ router.push({
pathname: router.pathname, pathname: router.pathname,
query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project }, query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project },
}); });
}
}; };
const canEditIssueProperties = canEditProperties(issue.project); const canEditIssueProperties = canEditProperties(issue.project);
return ( return (
<> <>
<div className="relative flex items-center gap-3 bg-custom-background-100 p-3 text-sm"> <button
className="relative flex items-center gap-3 bg-custom-background-100 p-3 text-sm w-full"
onClick={handleIssuePeekOverview}
>
{displayProperties && displayProperties?.key && ( {displayProperties && displayProperties?.key && (
<div className="flex-shrink-0 text-xs font-medium text-custom-text-300"> <div className="flex-shrink-0 text-xs font-medium text-custom-text-300">
{issue?.project_detail?.identifier}-{issue.sequence_id} {issue?.project_detail?.identifier}-{issue.sequence_id}
@ -49,10 +56,7 @@ export const IssueBlock: React.FC<IssueBlockProps> = (props) => {
<div className="absolute left-0 top-0 z-[99999] h-full w-full animate-pulse bg-custom-background-100/20" /> <div className="absolute left-0 top-0 z-[99999] h-full w-full animate-pulse bg-custom-background-100/20" />
)} )}
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}> <Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
<div <div className="line-clamp-1 w-full cursor-pointer text-sm font-medium text-custom-text-100 text-left">
className="line-clamp-1 w-full cursor-pointer text-sm font-medium text-custom-text-100"
onClick={handleIssuePeekOverview}
>
{issue.name} {issue.name}
</div> </div>
</Tooltip> </Tooltip>
@ -75,7 +79,7 @@ export const IssueBlock: React.FC<IssueBlockProps> = (props) => {
</div> </div>
)} )}
</div> </div>
</div> </button>
</> </>
); );
}; };

View File

@ -40,11 +40,11 @@ export const ListProperties: FC<IListProperties> = observer((props) => {
handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, assignees: ids }); handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, assignees: ids });
}; };
const handleStartDate = (date: string) => { const handleStartDate = (date: string | null) => {
handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, start_date: date }); handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, start_date: date });
}; };
const handleTargetDate = (date: string) => { const handleTargetDate = (date: string | null) => {
handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, target_date: date }); handleIssues(!group_id && group_id === "null" ? null : group_id, { ...issue, target_date: date });
}; };
@ -106,7 +106,7 @@ export const ListProperties: FC<IListProperties> = observer((props) => {
{displayProperties && displayProperties?.start_date && ( {displayProperties && displayProperties?.start_date && (
<IssuePropertyDate <IssuePropertyDate
value={issue?.start_date || null} value={issue?.start_date || null}
onChange={(date: string) => handleStartDate(date)} onChange={(date) => handleStartDate(date)}
disabled={isReadonly} disabled={isReadonly}
type="start_date" type="start_date"
/> />
@ -116,7 +116,7 @@ export const ListProperties: FC<IListProperties> = observer((props) => {
{displayProperties && displayProperties?.due_date && ( {displayProperties && displayProperties?.due_date && (
<IssuePropertyDate <IssuePropertyDate
value={issue?.target_date || null} value={issue?.target_date || null}
onChange={(date: string) => handleTargetDate(date)} onChange={(date) => handleTargetDate(date)}
disabled={isReadonly} disabled={isReadonly}
type="target_date" type="target_date"
/> />

View File

@ -52,12 +52,29 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
handleIssues({ ...issue, assignees: ids }); handleIssues({ ...issue, assignees: ids });
}; };
<<<<<<< HEAD:web/components/issues/issue-layouts/properties/all-properties.tsx
const handleStartDate = (date: string) => { const handleStartDate = (date: string) => {
handleIssues({ ...issue, start_date: date }); handleIssues({ ...issue, start_date: date });
}; };
const handleTargetDate = (date: string) => { const handleTargetDate = (date: string) => {
handleIssues({ ...issue, target_date: date }); handleIssues({ ...issue, target_date: date });
=======
const handleStartDate = (date: string | null) => {
handleIssues(
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
!group_id && group_id === "null" ? null : group_id,
{ ...issue, start_date: date }
);
};
const handleTargetDate = (date: string | null) => {
handleIssues(
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
!group_id && group_id === "null" ? null : group_id,
{ ...issue, target_date: date }
);
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434:web/components/issues/issue-layouts/kanban/properties.tsx
}; };
const handleEstimate = (value: number | null) => { const handleEstimate = (value: number | null) => {
@ -106,7 +123,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
<WithDisplayPropertiesHOC issuesFilter={issuesFilter} displayPropertyKey="start_date"> <WithDisplayPropertiesHOC issuesFilter={issuesFilter} displayPropertyKey="start_date">
<IssuePropertyDate <IssuePropertyDate
value={issue?.start_date || null} value={issue?.start_date || null}
onChange={(date: string) => handleStartDate(date)} onChange={(date) => handleStartDate(date)}
disabled={isReadOnly} disabled={isReadOnly}
type="start_date" type="start_date"
/> />
@ -116,7 +133,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
<WithDisplayPropertiesHOC issuesFilter={issuesFilter} displayPropertyKey="due_date"> <WithDisplayPropertiesHOC issuesFilter={issuesFilter} displayPropertyKey="due_date">
<IssuePropertyDate <IssuePropertyDate
value={issue?.target_date || null} value={issue?.target_date || null}
onChange={(date: string) => handleTargetDate(date)} onChange={(date) => handleTargetDate(date)}
disabled={isReadOnly} disabled={isReadOnly}
type="target_date" type="target_date"
/> />

View File

@ -42,7 +42,7 @@ export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer(
// store // store
const { const {
workspace: workspaceStore, workspace: workspaceStore,
projectMember: { projectMembers: _projectMembers, fetchProjectMembers }, projectMember: { members: _members, fetchProjectMembers },
} = useMobxStore(); } = useMobxStore();
const workspaceSlug = workspaceStore?.workspaceSlug; const workspaceSlug = workspaceStore?.workspaceSlug;
// states // states
@ -51,14 +51,14 @@ export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer(
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null); const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const [isLoading, setIsLoading] = useState<Boolean>(false); const [isLoading, setIsLoading] = useState<Boolean>(false);
const getWorkspaceMembers = () => { const getProjectMembers = () => {
setIsLoading(true); setIsLoading(true);
if (workspaceSlug && projectId) fetchProjectMembers(workspaceSlug, projectId).then(() => setIsLoading(false)); if (workspaceSlug && projectId) fetchProjectMembers(workspaceSlug, projectId).then(() => setIsLoading(false));
}; };
const updatedDefaultOptions: IProjectMember[] = const updatedDefaultOptions: IProjectMember[] =
defaultOptions.map((member: any) => ({ member: { ...member } })) ?? []; defaultOptions.map((member: any) => ({ member: { ...member } })) ?? [];
const projectMembers = _projectMembers ?? updatedDefaultOptions; const projectMembers = projectId && _members[projectId] ? _members[projectId] : updatedDefaultOptions;
const options = projectMembers?.map((member) => ({ const options = projectMembers?.map((member) => ({
value: member.member.id, value: member.member.id,
@ -142,7 +142,10 @@ export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer(
className={`flex w-full items-center justify-between gap-1 text-xs ${ className={`flex w-full items-center justify-between gap-1 text-xs ${
disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer" disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer"
} ${buttonClassName}`} } ${buttonClassName}`}
onClick={() => !projectMembers && getWorkspaceMembers()} onClick={(e) => {
e.stopPropagation();
(!projectId || !_members[projectId]) && getProjectMembers();
}}
> >
{label} {label}
{!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />} {!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}
@ -168,7 +171,7 @@ export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer(
<div className={`mt-2 max-h-48 space-y-1 overflow-y-scroll`}> <div className={`mt-2 max-h-48 space-y-1 overflow-y-scroll`}>
{isLoading ? ( {isLoading ? (
<p className="text-center text-custom-text-200">Loading...</p> <p className="text-center text-custom-text-200">Loading...</p>
) : filteredOptions.length > 0 ? ( ) : filteredOptions && filteredOptions.length > 0 ? (
filteredOptions.map((option) => ( filteredOptions.map((option) => (
<Combobox.Option <Combobox.Option
key={option.value} key={option.value}
@ -178,6 +181,7 @@ export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer(
active && !selected ? "bg-custom-background-80" : "" active && !selected ? "bg-custom-background-80" : ""
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}` } ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
} }
onClick={(e) => e.stopPropagation()}
> >
{({ selected }) => ( {({ selected }) => (
<> <>

View File

@ -12,11 +12,11 @@ import { Tooltip } from "@plane/ui";
// hooks // hooks
import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown"; import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown";
// helpers // helpers
import { renderDateFormat } from "helpers/date-time.helper"; import { renderDateFormat, renderFormattedDate } from "helpers/date-time.helper";
export interface IIssuePropertyDate { export interface IIssuePropertyDate {
value: any; value: string | null;
onChange: (date: any) => void; onChange: (date: string | null) => void;
disabled?: boolean; disabled?: boolean;
type: "start_date" | "target_date"; type: "start_date" | "target_date";
} }
@ -56,7 +56,17 @@ export const IssuePropertyDate: React.FC<IIssuePropertyDate> = observer((props)
return ( return (
<> <>
<Popover.Button <Popover.Button
as="button"
type="button"
ref={dropdownBtn} ref={dropdownBtn}
className="border-none outline-none"
onClick={(e) => e.stopPropagation()}
>
<Tooltip
tooltipHeading={dateOptionDetails.placeholder}
tooltipContent={value ? renderFormattedDate(value) : "None"}
>
<div
className={`flex h-5 w-full items-center rounded border-[0.5px] border-custom-border-300 px-2.5 py-1 outline-none duration-300 ${ className={`flex h-5 w-full items-center rounded border-[0.5px] border-custom-border-300 px-2.5 py-1 outline-none duration-300 ${
disabled disabled
? "pointer-events-none cursor-not-allowed text-custom-text-200" ? "pointer-events-none cursor-not-allowed text-custom-text-200"
@ -67,10 +77,7 @@ export const IssuePropertyDate: React.FC<IIssuePropertyDate> = observer((props)
<dateOptionDetails.icon className="h-3 w-3" strokeWidth={2} /> <dateOptionDetails.icon className="h-3 w-3" strokeWidth={2} />
{value && ( {value && (
<> <>
<Tooltip tooltipHeading={dateOptionDetails.placeholder} tooltipContent={value ?? "None"}>
<div className="text-xs">{value}</div> <div className="text-xs">{value}</div>
</Tooltip>
<div <div
className="flex flex-shrink-0 items-center justify-center" className="flex flex-shrink-0 items-center justify-center"
onClick={() => { onClick={() => {
@ -82,6 +89,8 @@ export const IssuePropertyDate: React.FC<IIssuePropertyDate> = observer((props)
</> </>
)} )}
</div> </div>
</div>
</Tooltip>
</Popover.Button> </Popover.Button>
<div className={`${open ? "fixed left-0 top-0 z-20 h-full w-full cursor-auto" : ""}`}> <div className={`${open ? "fixed left-0 top-0 z-20 h-full w-full cursor-auto" : ""}`}>
@ -92,7 +101,8 @@ export const IssuePropertyDate: React.FC<IIssuePropertyDate> = observer((props)
{({ close }) => ( {({ close }) => (
<DatePicker <DatePicker
selected={value ? new Date(value) : new Date()} selected={value ? new Date(value) : new Date()}
onChange={(val: any) => { onChange={(val, e) => {
e?.stopPropagation();
if (onChange && val) { if (onChange && val) {
onChange(renderDateFormat(val)); onChange(renderDateFormat(val));
close(); close();

View File

@ -116,6 +116,7 @@ export const IssuePropertyEstimates: React.FC<IIssuePropertyEstimates> = observe
className={`flex h-5 w-full items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2.5 py-1 text-xs ${ className={`flex h-5 w-full items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2.5 py-1 text-xs ${
disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer hover:bg-custom-background-80" disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`} } ${buttonClassName}`}
onClick={(e) => e.stopPropagation()}
> >
{label} {label}
{!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />} {!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}
@ -150,6 +151,7 @@ export const IssuePropertyEstimates: React.FC<IIssuePropertyEstimates> = observe
active ? "bg-custom-background-80" : "" active ? "bg-custom-background-80" : ""
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}` } ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
} }
onClick={(e) => e.stopPropagation()}
> >
{({ selected }) => ( {({ selected }) => (
<> <>

View File

@ -106,7 +106,7 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
{projectLabels {projectLabels
?.filter((l) => value.includes(l.id)) ?.filter((l) => value.includes(l.id))
.map((label) => ( .map((label) => (
<Tooltip position="top" tooltipHeading="Labels" tooltipContent={label.name ?? ""}> <Tooltip position="top" tooltipHeading="Label" tooltipContent={label.name ?? ""}>
<div <div
key={label.id} key={label.id}
className={`flex overflow-hidden hover:bg-custom-background-80 ${ className={`flex overflow-hidden hover:bg-custom-background-80 ${
@ -144,6 +144,7 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
</div> </div>
) )
) : ( ) : (
<Tooltip position="top" tooltipHeading="Labels" tooltipContent="None">
<div <div
className={`h-full flex items-center justify-center gap-2 rounded px-2.5 py-1 text-xs hover:bg-custom-background-80 ${ className={`h-full flex items-center justify-center gap-2 rounded px-2.5 py-1 text-xs hover:bg-custom-background-80 ${
noLabelBorder ? "" : "border-[0.5px] border-custom-border-300" noLabelBorder ? "" : "border-[0.5px] border-custom-border-300"
@ -152,6 +153,7 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
<Tags className="h-3.5 w-3.5" strokeWidth={2} /> <Tags className="h-3.5 w-3.5" strokeWidth={2} />
{placeholderText} {placeholderText}
</div> </div>
</Tooltip>
)} )}
</div> </div>
); );
@ -176,7 +178,10 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
? "cursor-pointer" ? "cursor-pointer"
: "cursor-pointer hover:bg-custom-background-80" : "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`} } ${buttonClassName}`}
onClick={() => !storeLabels && fetchLabels()} onClick={(e) => {
e.stopPropagation();
!storeLabels && fetchLabels();
}}
> >
{label} {label}
{!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />} {!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}
@ -213,6 +218,7 @@ export const IssuePropertyLabels: React.FC<IIssuePropertyLabels> = observer((pro
selected ? "text-custom-text-100" : "text-custom-text-200" selected ? "text-custom-text-100" : "text-custom-text-200"
}` }`
} }
onClick={(e) => e.stopPropagation()}
> >
{({ selected }) => ( {({ selected }) => (
<> <>

View File

@ -117,7 +117,14 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
className={`flex h-5 w-full items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2.5 py-1 text-xs ${ className={`flex h-5 w-full items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2.5 py-1 text-xs ${
disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer hover:bg-custom-background-80" disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`} } ${buttonClassName}`}
<<<<<<< HEAD
onClick={() => !storeStates && handleFetchProjectStates()} onClick={() => !storeStates && handleFetchProjectStates()}
=======
onClick={(e) => {
e.stopPropagation();
!storeStates && fetchProjectStates();
}}
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
> >
{label} {label}
{!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />} {!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}
@ -153,6 +160,7 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
active ? "bg-custom-background-80" : "" active ? "bg-custom-background-80" : ""
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}` } ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
} }
onClick={(e) => e.stopPropagation()}
> >
{({ selected }) => ( {({ selected }) => (
<> <>

View File

@ -58,7 +58,12 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = (props) => {
}} }}
currentStore={EProjectStore.PROJECT} currentStore={EProjectStore.PROJECT}
/> />
<CustomMenu placement="bottom-start" customButton={customActionButton} ellipsis> <CustomMenu
placement="bottom-start"
customButton={customActionButton}
ellipsis
menuButtonOnClick={(e) => e.stopPropagation()}
>
<CustomMenu.MenuItem <CustomMenu.MenuItem
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();

View File

@ -40,7 +40,12 @@ export const ArchivedIssueQuickActions: React.FC<IQuickActionProps> = (props) =>
handleClose={() => setDeleteIssueModal(false)} handleClose={() => setDeleteIssueModal(false)}
onSubmit={handleDelete} onSubmit={handleDelete}
/> />
<CustomMenu placement="bottom-start" customButton={customActionButton} ellipsis> <CustomMenu
placement="bottom-start"
customButton={customActionButton}
ellipsis
menuButtonOnClick={(e) => e.stopPropagation()}
>
<CustomMenu.MenuItem <CustomMenu.MenuItem
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();

View File

@ -58,7 +58,12 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = (props) => {
}} }}
currentStore={EProjectStore.CYCLE} currentStore={EProjectStore.CYCLE}
/> />
<CustomMenu placement="bottom-start" customButton={customActionButton} ellipsis> <CustomMenu
placement="bottom-start"
customButton={customActionButton}
ellipsis
menuButtonOnClick={(e) => e.stopPropagation()}
>
<CustomMenu.MenuItem <CustomMenu.MenuItem
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();

View File

@ -58,7 +58,13 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = (props) => {
}} }}
currentStore={EProjectStore.MODULE} currentStore={EProjectStore.MODULE}
/> />
<CustomMenu placement="bottom-start" customButton={customActionButton} ellipsis>
<CustomMenu
placement="bottom-start"
customButton={customActionButton}
ellipsis
menuButtonOnClick={(e) => e.stopPropagation()}
>
<CustomMenu.MenuItem <CustomMenu.MenuItem
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();

View File

@ -66,7 +66,12 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = (props) =>
}} }}
currentStore={EProjectStore.PROJECT} currentStore={EProjectStore.PROJECT}
/> />
<CustomMenu placement="bottom-start" customButton={customActionButton} ellipsis> <CustomMenu
placement="bottom-start"
customButton={customActionButton}
ellipsis
menuButtonOnClick={(e) => e.stopPropagation()}
>
<CustomMenu.MenuItem <CustomMenu.MenuItem
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();

View File

@ -9,7 +9,7 @@ import { ArchivedIssueListLayout, ArchivedIssueAppliedFiltersRoot } from "compon
export const ArchivedIssueLayoutRoot: React.FC = observer(() => { export const ArchivedIssueLayoutRoot: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; const { workspaceSlug, projectId } = router.query;
const { const {
projectArchivedIssues: { getIssues, fetchIssues }, projectArchivedIssues: { getIssues, fetchIssues },
@ -18,8 +18,8 @@ export const ArchivedIssueLayoutRoot: React.FC = observer(() => {
useSWR(workspaceSlug && projectId ? `ARCHIVED_FILTERS_AND_ISSUES_${projectId.toString()}` : null, async () => { useSWR(workspaceSlug && projectId ? `ARCHIVED_FILTERS_AND_ISSUES_${projectId.toString()}` : null, async () => {
if (workspaceSlug && projectId) { if (workspaceSlug && projectId) {
await fetchFilters(workspaceSlug, projectId); await fetchFilters(workspaceSlug.toString(), projectId.toString());
await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader"); await fetchIssues(workspaceSlug.toString(), projectId.toString(), getIssues ? "mutation" : "init-loader");
} }
}); });

View File

@ -25,12 +25,17 @@ export const CycleLayoutRoot: React.FC = observer(() => {
const [transferIssuesModal, setTransferIssuesModal] = useState(false); const [transferIssuesModal, setTransferIssuesModal] = useState(false);
const router = useRouter(); const router = useRouter();
<<<<<<< HEAD
const { workspaceSlug, projectId, cycleId } = router.query as { const { workspaceSlug, projectId, cycleId } = router.query as {
workspaceSlug: string; workspaceSlug: string;
projectId: string; projectId: string;
cycleId: string; cycleId: string;
}; };
// store hooks // store hooks
=======
const { workspaceSlug, projectId, cycleId } = router.query;
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
const { const {
cycleIssues: { loader, getIssues, fetchIssues }, cycleIssues: { loader, getIssues, fetchIssues },
cycleIssuesFilter: { issueFilters, fetchFilters }, cycleIssuesFilter: { issueFilters, fetchFilters },
@ -41,8 +46,13 @@ export const CycleLayoutRoot: React.FC = observer(() => {
workspaceSlug && projectId && cycleId ? `CYCLE_ISSUES_V3_${workspaceSlug}_${projectId}_${cycleId}` : null, workspaceSlug && projectId && cycleId ? `CYCLE_ISSUES_V3_${workspaceSlug}_${projectId}_${cycleId}` : null,
async () => { async () => {
if (workspaceSlug && projectId && cycleId) { if (workspaceSlug && projectId && cycleId) {
await fetchFilters(workspaceSlug, projectId, cycleId); await fetchFilters(workspaceSlug.toString(), projectId.toString(), cycleId.toString());
await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader", cycleId); await fetchIssues(
workspaceSlug.toString(),
projectId.toString(),
getIssues ? "mutation" : "init-loader",
cycleId.toString()
);
} }
} }
); );
@ -70,7 +80,11 @@ export const CycleLayoutRoot: React.FC = observer(() => {
) : ( ) : (
<> <>
{Object.keys(getIssues ?? {}).length == 0 ? ( {Object.keys(getIssues ?? {}).length == 0 ? (
<CycleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} cycleId={cycleId} /> <CycleEmptyState
workspaceSlug={workspaceSlug?.toString()}
projectId={projectId?.toString()}
cycleId={cycleId?.toString()}
/>
) : ( ) : (
<div className="h-full w-full overflow-auto"> <div className="h-full w-full overflow-auto">
{activeLayout === "list" ? ( {activeLayout === "list" ? (

View File

@ -11,7 +11,7 @@ import { DraftKanBanLayout } from "../kanban/roots/draft-issue-root";
export const DraftIssueLayoutRoot: React.FC = observer(() => { export const DraftIssueLayoutRoot: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; const { workspaceSlug, projectId } = router.query;
const { const {
projectDraftIssuesFilter: { issueFilters, fetchFilters }, projectDraftIssuesFilter: { issueFilters, fetchFilters },
@ -20,8 +20,8 @@ export const DraftIssueLayoutRoot: React.FC = observer(() => {
useSWR(workspaceSlug && projectId ? `DRAFT_FILTERS_AND_ISSUES_${projectId.toString()}` : null, async () => { useSWR(workspaceSlug && projectId ? `DRAFT_FILTERS_AND_ISSUES_${projectId.toString()}` : null, async () => {
if (workspaceSlug && projectId) { if (workspaceSlug && projectId) {
await fetchFilters(workspaceSlug, projectId); await fetchFilters(workspaceSlug.toString(), projectId.toString());
await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader"); await fetchIssues(workspaceSlug.toString(), projectId.toString(), getIssues ? "mutation" : "init-loader");
} }
}); });

View File

@ -20,11 +20,7 @@ import { Spinner } from "@plane/ui";
export const ModuleLayoutRoot: React.FC = observer(() => { export const ModuleLayoutRoot: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, moduleId } = router.query as { const { workspaceSlug, projectId, moduleId } = router.query;
workspaceSlug: string;
projectId: string;
moduleId: string;
};
const { const {
moduleIssues: { loader, getIssues, fetchIssues }, moduleIssues: { loader, getIssues, fetchIssues },
@ -35,8 +31,13 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
workspaceSlug && projectId && moduleId ? `MODULE_ISSUES_V3_${workspaceSlug}_${projectId}_${moduleId}` : null, workspaceSlug && projectId && moduleId ? `MODULE_ISSUES_V3_${workspaceSlug}_${projectId}_${moduleId}` : null,
async () => { async () => {
if (workspaceSlug && projectId && moduleId) { if (workspaceSlug && projectId && moduleId) {
await fetchFilters(workspaceSlug, projectId, moduleId); await fetchFilters(workspaceSlug.toString(), projectId.toString(), moduleId.toString());
await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader", moduleId); await fetchIssues(
workspaceSlug.toString(),
projectId.toString(),
getIssues ? "mutation" : "init-loader",
moduleId.toString()
);
} }
} }
); );
@ -54,7 +55,11 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
) : ( ) : (
<> <>
{Object.keys(getIssues ?? {}).length == 0 ? ( {Object.keys(getIssues ?? {}).length == 0 ? (
<ModuleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} moduleId={moduleId} /> <ModuleEmptyState
workspaceSlug={workspaceSlug?.toString()}
projectId={projectId?.toString()}
moduleId={moduleId?.toString()}
/>
) : ( ) : (
<div className="h-full w-full overflow-auto"> <div className="h-full w-full overflow-auto">
{activeLayout === "list" ? ( {activeLayout === "list" ? (

View File

@ -21,13 +21,14 @@ import { EIssuesStoreType } from "constants/issue";
export const ProjectLayoutRoot: React.FC = observer(() => { export const ProjectLayoutRoot: React.FC = observer(() => {
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; const { workspaceSlug, projectId } = router.query;
const { const {
issues: { loader, getIssues, fetchIssues }, issues: { loader, getIssues, fetchIssues },
issuesFilter: { issueFilters, fetchFilters }, issuesFilter: { issueFilters, fetchFilters },
} = useIssues(EIssuesStoreType.PROJECT); } = useIssues(EIssuesStoreType.PROJECT);
<<<<<<< HEAD
useSWR( useSWR(
workspaceSlug && projectId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, workspaceSlug && projectId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null,
async () => { async () => {
@ -61,6 +62,14 @@ export const ProjectLayoutRoot: React.FC = observer(() => {
// console.log("issueGetIssuesIds", issueGetIssuesIds); // console.log("issueGetIssuesIds", issueGetIssuesIds);
// console.log("issueGetIssues", issueGetIssues); // console.log("issueGetIssues", issueGetIssues);
// console.log("---"); // console.log("---");
=======
useSWR(workspaceSlug && projectId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, async () => {
if (workspaceSlug && projectId) {
await fetchFilters(workspaceSlug.toString(), projectId.toString());
await fetchIssues(workspaceSlug.toString(), projectId.toString(), getIssues ? "mutation" : "init-loader");
}
});
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
const activeLayout = issueFilters?.displayFilters?.layout; const activeLayout = issueFilters?.displayFilters?.layout;

View File

@ -18,11 +18,7 @@ import { Spinner } from "@plane/ui";
export const ProjectViewLayoutRoot: React.FC = observer(() => { export const ProjectViewLayoutRoot: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, viewId } = router.query as { const { workspaceSlug, projectId, viewId } = router.query;
workspaceSlug: string;
projectId: string;
viewId?: string;
};
const { const {
viewIssues: { loader, getIssues, fetchIssues }, viewIssues: { loader, getIssues, fetchIssues },
@ -31,8 +27,8 @@ export const ProjectViewLayoutRoot: React.FC = observer(() => {
useSWR(workspaceSlug && projectId && viewId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, async () => { useSWR(workspaceSlug && projectId && viewId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, async () => {
if (workspaceSlug && projectId && viewId) { if (workspaceSlug && projectId && viewId) {
await fetchFilters(workspaceSlug, projectId, viewId); await fetchFilters(workspaceSlug.toString(), projectId.toString(), viewId.toString());
await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader"); await fetchIssues(workspaceSlug.toString(), projectId.toString(), getIssues ? "mutation" : "init-loader");
} }
}); });

View File

@ -34,13 +34,17 @@ export const IssueColumn: React.FC<Props> = ({
const menuActionRef = useRef<HTMLDivElement | null>(null); const menuActionRef = useRef<HTMLDivElement | null>(null);
const handleIssuePeekOverview = (issue: IIssue) => { const handleIssuePeekOverview = (issue: IIssue, event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const { query } = router; const { query } = router;
if (event.ctrlKey || event.metaKey) {
const issueUrl = `/${issue.workspace_detail.slug}/projects/${issue.project_detail.id}/issues/${issue?.id}`;
window.open(issueUrl, "_blank"); // Open link in a new tab
} else {
router.push({ router.push({
pathname: router.pathname, pathname: router.pathname,
query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project }, query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project },
}); });
}
}; };
const paddingLeft = `${nestingLevel * 54}px`; const paddingLeft = `${nestingLevel * 54}px`;
@ -99,7 +103,7 @@ export const IssueColumn: React.FC<Props> = ({
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}> <Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
<div <div
className="h-full w-full cursor-pointer truncate px-4 py-2.5 text-left text-[0.825rem] text-custom-text-100" className="h-full w-full cursor-pointer truncate px-4 py-2.5 text-left text-[0.825rem] text-custom-text-100"
onClick={() => handleIssuePeekOverview(issue)} onClick={(e) => handleIssuePeekOverview(issue, e)}
> >
{issue.name} {issue.name}
</div> </div>

View File

@ -6,8 +6,6 @@ import { IssuePropertyState } from "../../properties";
import useSubIssue from "hooks/use-sub-issue"; import useSubIssue from "hooks/use-sub-issue";
// types // types
import { IIssue, IState } from "types"; import { IIssue, IState } from "types";
import { mutate } from "swr";
import { SUB_ISSUES } from "constants/fetch-keys";
type Props = { type Props = {
issue: IIssue; issue: IIssue;

View File

@ -194,7 +194,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
workspaceSlug={workspaceSlug.toString()} workspaceSlug={workspaceSlug.toString()}
projectId={peekProjectId.toString()} projectId={peekProjectId.toString()}
issueId={peekIssueId.toString()} issueId={peekIssueId.toString()}
handleIssue={async (issueToUpdate: any) => await handleIssues(issueToUpdate, EIssueActions.UPDATE)} handleIssue={async (issueToUpdate: any, action: EIssueActions) => await handleIssues(issueToUpdate, action)}
/> />
)} )}
</div> </div>

View File

@ -40,7 +40,7 @@ const issueService = new IssueService();
const issueCommentService = new IssueCommentService(); const issueCommentService = new IssueCommentService();
export const IssueMainContent: React.FC<Props> = observer((props) => { export const IssueMainContent: React.FC<Props> = observer((props) => {
const { issueDetails, submitChanges, uneditable = false } = props; const { issueDetails, submitChanges, uneditable } = props;
// states // states
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved"); const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
// router // router
@ -151,7 +151,13 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
); );
}; };
<<<<<<< HEAD
const isAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; const isAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
=======
const isAllowed =
(!!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER) ||
(uneditable !== undefined && !uneditable);
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
return ( return (
<> <>
@ -231,7 +237,7 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
workspaceSlug={workspaceSlug as string} workspaceSlug={workspaceSlug as string}
issue={issueDetails} issue={issueDetails}
handleFormSubmit={submitChanges} handleFormSubmit={submitChanges}
isAllowed={isAllowed || !uneditable} isAllowed={isAllowed}
/> />
{workspaceSlug && projectId && ( {workspaceSlug && projectId && (
@ -249,8 +255,8 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
<div className="flex flex-col gap-3 py-3"> <div className="flex flex-col gap-3 py-3">
<h3 className="text-lg">Attachments</h3> <h3 className="text-lg">Attachments</h3>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4"> <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
<IssueAttachmentUpload disabled={uneditable} /> <IssueAttachmentUpload disabled={!isAllowed} />
<IssueAttachments /> <IssueAttachments editable={isAllowed} />
</div> </div>
</div> </div>
<div className="space-y-5 pt-3"> <div className="space-y-5 pt-3">
@ -263,8 +269,13 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
/> />
<AddComment <AddComment
onSubmit={handleAddComment} onSubmit={handleAddComment}
<<<<<<< HEAD
disabled={uneditable} disabled={uneditable}
showAccessSpecifier={Boolean(projectDetails && projectDetails.is_deployed)} showAccessSpecifier={Boolean(projectDetails && projectDetails.is_deployed)}
=======
disabled={!isAllowed}
showAccessSpecifier={projectDetails && projectDetails.is_deployed}
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
/> />
</div> </div>
</> </>

View File

@ -153,10 +153,12 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
debouncedFormSave(); debouncedFormSave();
}} }}
required={true} required={true}
className="min-h-10 block w-full resize-none overflow-hidden rounded border-none bg-transparent !p-0 text-xl outline-none ring-0 focus:!px-3 focus:!py-2 focus:ring-1 focus:ring-custom-primary" className={`min-h-10 block w-full resize-none overflow-hidden rounded border-none bg-transparent !p-0 text-xl outline-none ring-0 focus:!px-3 focus:!py-2 focus:ring-1 focus:ring-custom-primary ${
!isAllowed ? "hover:cursor-not-allowed" : ""
}`}
hasError={Boolean(errors?.description)} hasError={Boolean(errors?.description)}
role="textbox" role="textbox"
disabled={!true} disabled={!isAllowed}
/> />
)} )}
/> />
@ -188,7 +190,9 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
setShouldShowAlert={setShowAlert} setShouldShowAlert={setShowAlert}
setIsSubmitting={setIsSubmitting} setIsSubmitting={setIsSubmitting}
dragDropEnabled dragDropEnabled
customClassName={isAllowed ? "min-h-[150px] shadow-sm" : "!p-0 !pt-2 text-custom-text-200"} customClassName={
isAllowed ? "min-h-[150px] shadow-sm" : "!p-0 !pt-2 text-custom-text-200 pointer-events-none"
}
noBorder={!isAllowed} noBorder={!isAllowed}
onChange={(description: Object, description_html: string) => { onChange={(description: Object, description_html: string) => {
setShowAlert(true); setShowAlert(true);

View File

@ -49,7 +49,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
const { getProjectById } = useProject(); const { getProjectById } = useProject();
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, peekProjectId: projectId } = router.query;
const handleState = (_state: string) => { const handleState = (_state: string) => {
issueUpdate({ ...issue, state: _state }); issueUpdate({ ...issue, state: _state });
@ -118,7 +118,12 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
<p>State</p> <p>State</p>
</div> </div>
<div> <div>
<SidebarStateSelect value={issue?.state || ""} onChange={handleState} disabled={disableUserActions} /> <SidebarStateSelect
value={issue?.state || ""}
projectId={projectId as string}
onChange={handleState}
disabled={disableUserActions}
/>
</div> </div>
</div> </div>
@ -131,6 +136,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
<div> <div>
<SidebarAssigneeSelect <SidebarAssigneeSelect
value={issue.assignees || []} value={issue.assignees || []}
projectId={projectId as string}
onChange={handleAssignee} onChange={handleAssignee}
disabled={disableUserActions} disabled={disableUserActions}
/> />
@ -212,7 +218,12 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
<p>Parent</p> <p>Parent</p>
</div> </div>
<div> <div>
<SidebarParentSelect onChange={handleParent} issueDetails={issue} disabled={disableUserActions} /> <SidebarParentSelect
onChange={handleParent}
issueDetails={issue}
projectId={projectId as string}
disabled={disableUserActions}
/>
</div> </div>
</div> </div>
</div> </div>
@ -228,6 +239,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
<div> <div>
<SidebarCycleSelect <SidebarCycleSelect
issueDetail={issue} issueDetail={issue}
projectId={projectId as string}
disabled={disableUserActions} disabled={disableUserActions}
handleIssueUpdate={handleCycleOrModuleChange} handleIssueUpdate={handleCycleOrModuleChange}
/> />
@ -242,6 +254,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
<div> <div>
<SidebarModuleSelect <SidebarModuleSelect
issueDetail={issue} issueDetail={issue}
projectId={projectId as string}
disabled={disableUserActions} disabled={disableUserActions}
handleIssueUpdate={handleCycleOrModuleChange} handleIssueUpdate={handleCycleOrModuleChange}
/> />
@ -255,6 +268,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
<div className="flex w-full flex-col gap-3"> <div className="flex w-full flex-col gap-3">
<SidebarLabelSelect <SidebarLabelSelect
issueDetails={issue} issueDetails={issue}
projectId={projectId as string}
labelList={issue.labels} labelList={issue.labels}
submitChanges={handleLabels} submitChanges={handleLabels}
isNotAllowed={disableUserActions} isNotAllowed={disableUserActions}

View File

@ -11,6 +11,7 @@ import { IssueView } from "components/issues";
import { copyUrlToClipboard } from "helpers/string.helper"; import { copyUrlToClipboard } from "helpers/string.helper";
// types // types
import { IIssue, IIssueLink } from "types"; import { IIssue, IIssueLink } from "types";
import { EIssueActions } from "../issue-layouts/types";
// constants // constants
import { EUserProjectRoles } from "constants/project"; import { EUserProjectRoles } from "constants/project";
@ -18,7 +19,7 @@ interface IIssuePeekOverview {
workspaceSlug: string; workspaceSlug: string;
projectId: string; projectId: string;
issueId: string; issueId: string;
handleIssue: (issue: Partial<IIssue>) => void; handleIssue: (issue: Partial<IIssue>, action: EIssueActions) => Promise<void>;
isArchived?: boolean; isArchived?: boolean;
children?: ReactNode; children?: ReactNode;
} }
@ -30,8 +31,6 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
const { peekIssueId } = router.query; const { peekIssueId } = router.query;
const { const {
user: { currentProjectRole },
issue: { removeIssueFromStructure },
issueDetail: { issueDetail: {
createIssueComment, createIssueComment,
updateIssueComment, updateIssueComment,
@ -58,6 +57,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
}, },
archivedIssues: { deleteArchivedIssue }, archivedIssues: { deleteArchivedIssue },
project: { currentProjectDetails }, project: { currentProjectDetails },
workspaceMember: { currentWorkspaceUserProjectsRole },
} = useMobxStore(); } = useMobxStore();
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
@ -98,7 +98,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
const issueUpdate = async (_data: Partial<IIssue>) => { const issueUpdate = async (_data: Partial<IIssue>) => {
if (handleIssue) { if (handleIssue) {
await handleIssue(_data); await handleIssue(_data, EIssueActions.UPDATE);
fetchIssueActivity(workspaceSlug, projectId, issueId); fetchIssueActivity(workspaceSlug, projectId, issueId);
} }
}; };
@ -133,7 +133,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
const handleDeleteIssue = async () => { const handleDeleteIssue = async () => {
if (isArchived) await deleteArchivedIssue(workspaceSlug, projectId, issue!); if (isArchived) await deleteArchivedIssue(workspaceSlug, projectId, issue!);
else removeIssueFromStructure(workspaceSlug, projectId, issue!); else await handleIssue(issue!, EIssueActions.DELETE);
const { query } = router; const { query } = router;
if (query.peekIssueId) { if (query.peekIssueId) {
setPeekId(null); setPeekId(null);
@ -146,7 +146,12 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
} }
}; };
<<<<<<< HEAD
const userRole = currentProjectRole ?? EUserProjectRoles.GUEST; const userRole = currentProjectRole ?? EUserProjectRoles.GUEST;
=======
const userRole =
(currentWorkspaceUserProjectsRole && currentWorkspaceUserProjectsRole[projectId]) ?? EUserWorkspaceRoles.GUEST;
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
return ( return (
<Fragment> <Fragment>

View File

@ -1,4 +1,4 @@
import { FC, ReactNode, useState } from "react"; import { FC, ReactNode, useRef, useState } 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 useSWR from "swr"; import useSWR from "swr";
@ -14,6 +14,8 @@ import {
PeekOverviewIssueDetails, PeekOverviewIssueDetails,
PeekOverviewProperties, PeekOverviewProperties,
} from "components/issues"; } from "components/issues";
// hooks
import useOutsideClickDetector from "hooks/use-outside-click-detector";
// ui // ui
import { Button, CenterPanelIcon, CustomSelect, FullScreenPanelIcon, SidePanelIcon, Spinner } from "@plane/ui"; import { Button, CenterPanelIcon, CustomSelect, FullScreenPanelIcon, SidePanelIcon, Spinner } from "@plane/ui";
// types // types
@ -107,6 +109,8 @@ export const IssueView: FC<IIssueView> = observer((props) => {
const [peekMode, setPeekMode] = useState<TPeekModes>("side-peek"); const [peekMode, setPeekMode] = useState<TPeekModes>("side-peek");
const [deleteIssueModal, setDeleteIssueModal] = useState(false); const [deleteIssueModal, setDeleteIssueModal] = useState(false);
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved"); const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
// ref
const issuePeekOverviewRef = useRef<HTMLDivElement>(null);
const updateRoutePeekId = () => { const updateRoutePeekId = () => {
if (issueId != peekIssueId) { if (issueId != peekIssueId) {
@ -151,6 +155,8 @@ export const IssueView: FC<IIssueView> = observer((props) => {
const currentMode = PEEK_OPTIONS.find((m) => m.key === peekMode); const currentMode = PEEK_OPTIONS.find((m) => m.key === peekMode);
useOutsideClickDetector(issuePeekOverviewRef, () => removeRoutePeekId());
return ( return (
<> <>
{issue && !isArchived && ( {issue && !isArchived && (
@ -178,6 +184,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
{issueId === peekIssueId && ( {issueId === peekIssueId && (
<div <div
ref={issuePeekOverviewRef}
className={`fixed z-20 flex flex-col overflow-hidden rounded border border-custom-border-200 bg-custom-background-100 transition-all duration-300 className={`fixed z-20 flex flex-col overflow-hidden rounded border border-custom-border-200 bg-custom-background-100 transition-all duration-300
${peekMode === "side-peek" ? `bottom-0 right-0 top-0 w-full md:w-[50%]` : ``} ${peekMode === "side-peek" ? `bottom-0 right-0 top-0 w-full md:w-[50%]` : ``}
${peekMode === "modal" ? `left-[50%] top-[50%] h-5/6 w-5/6 -translate-x-[50%] -translate-y-[50%]` : ``} ${peekMode === "modal" ? `left-[50%] top-[50%] h-5/6 w-5/6 -translate-x-[50%] -translate-y-[50%]` : ``}

View File

@ -34,7 +34,11 @@ export const IssueCycleSelect: React.FC<IssueCycleSelectProps> = observer((props
if (workspaceSlug && projectId) fetchAllCycles(workspaceSlug, projectId); if (workspaceSlug && projectId) fetchAllCycles(workspaceSlug, projectId);
}; };
<<<<<<< HEAD
const selectedCycle = value ? getCycleById(value) : null; const selectedCycle = value ? getCycleById(value) : null;
=======
const cycles = cycleStore.cycles?.[projectId]?.["all"] ?? [];
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
const options = projectAllCycles?.map((cycleId) => { const options = projectAllCycles?.map((cycleId) => {
const cycleDetail = getCycleById(cycleId); const cycleDetail = getCycleById(cycleId);

View File

@ -10,6 +10,7 @@ import { PROJECT_MEMBERS } from "constants/fetch-keys";
type Props = { type Props = {
value: string[]; value: string[];
projectId: string;
onChange: (val: string[]) => void; onChange: (val: string[]) => void;
disabled?: boolean; disabled?: boolean;
}; };
@ -17,9 +18,9 @@ type Props = {
// services // services
const projectMemberService = new ProjectMemberService(); const projectMemberService = new ProjectMemberService();
export const SidebarAssigneeSelect: React.FC<Props> = ({ value, onChange, disabled = false }) => { export const SidebarAssigneeSelect: React.FC<Props> = ({ value, projectId, onChange, disabled = false }) => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug } = router.query;
const { data: members } = useSWR( const { data: members } = useSWR(
workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null, workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null,

View File

@ -14,6 +14,7 @@ import { CYCLE_ISSUES, INCOMPLETE_CYCLES_LIST, ISSUE_DETAILS } from "constants/f
type Props = { type Props = {
issueDetail: IIssue | undefined; issueDetail: IIssue | undefined;
projectId: string;
handleCycleChange?: (cycleId: string) => void; handleCycleChange?: (cycleId: string) => void;
disabled?: boolean; disabled?: boolean;
handleIssueUpdate?: () => void; handleIssueUpdate?: () => void;
@ -26,7 +27,7 @@ export const SidebarCycleSelect: React.FC<Props> = (props) => {
const { issueDetail, disabled = false, handleIssueUpdate, handleCycleChange } = props; const { issueDetail, disabled = false, handleIssueUpdate, handleCycleChange } = props;
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId: _projectId, peekProjectId } = router.query;
// mobx store // mobx store
const { const {
cycleIssues: { removeIssueFromCycle, addIssueToCycle }, cycleIssues: { removeIssueFromCycle, addIssueToCycle },
@ -34,6 +35,8 @@ export const SidebarCycleSelect: React.FC<Props> = (props) => {
const [isUpdating, setIsUpdating] = useState(false); const [isUpdating, setIsUpdating] = useState(false);
const projectId = _projectId ?? peekProjectId;
const { data: incompleteCycles } = useSWR( const { data: incompleteCycles } = useSWR(
workspaceSlug && projectId ? INCOMPLETE_CYCLES_LIST(projectId as string) : null, workspaceSlug && projectId ? INCOMPLETE_CYCLES_LIST(projectId as string) : null,
workspaceSlug && projectId workspaceSlug && projectId

View File

@ -16,6 +16,7 @@ import { IIssue, IIssueLabel } from "types";
type Props = { type Props = {
issueDetails: IIssue | undefined; issueDetails: IIssue | undefined;
projectId: string;
labelList: string[]; labelList: string[];
submitChanges: (formData: any) => void; submitChanges: (formData: any) => void;
isNotAllowed: boolean; isNotAllowed: boolean;
@ -28,12 +29,12 @@ const defaultValues: Partial<IIssueLabel> = {
}; };
export const SidebarLabelSelect: React.FC<Props> = observer((props) => { export const SidebarLabelSelect: React.FC<Props> = observer((props) => {
const { issueDetails, labelList, submitChanges, isNotAllowed, uneditable } = props; const { issueDetails, projectId, labelList, submitChanges, isNotAllowed, uneditable } = props;
// states // states
const [createLabelForm, setCreateLabelForm] = useState(false); const [createLabelForm, setCreateLabelForm] = useState(false);
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug } = router.query;
// toast // toast
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
// mobx store // mobx store

View File

@ -1,29 +1,40 @@
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 { observer } from "mobx-react-lite";
<<<<<<< HEAD
import { mutate } from "swr"; import { mutate } from "swr";
// hooks // hooks
import { useModule } from "hooks/store"; import { useModule } from "hooks/store";
=======
import useSWR, { mutate } from "swr";
// mobx store
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// ui // ui
import { CustomSearchSelect, DiceIcon, Spinner, Tooltip } from "@plane/ui"; import { CustomSearchSelect, DiceIcon, Spinner, Tooltip } from "@plane/ui";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
// fetch-keys // fetch-keys
import { ISSUE_DETAILS, MODULE_ISSUES } from "constants/fetch-keys"; import { ISSUE_DETAILS, MODULE_ISSUES, MODULE_LIST } from "constants/fetch-keys";
// services
import { ModuleService } from "services/module.service";
type Props = { type Props = {
issueDetail: IIssue | undefined; issueDetail: IIssue | undefined;
projectId: string;
handleModuleChange?: (moduleId: string) => void; handleModuleChange?: (moduleId: string) => void;
disabled?: boolean; disabled?: boolean;
handleIssueUpdate?: () => void; handleIssueUpdate?: () => void;
}; };
// services
const moduleService = new ModuleService();
export const SidebarModuleSelect: React.FC<Props> = observer((props) => { export const SidebarModuleSelect: React.FC<Props> = observer((props) => {
const { issueDetail, disabled = false, handleIssueUpdate, handleModuleChange } = props; const { issueDetail, projectId, disabled = false, handleIssueUpdate, handleModuleChange } = props;
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug } = router.query;
// mobx store // mobx store
const { const {
moduleIssues: { removeIssueFromModule, addIssueToModule }, moduleIssues: { removeIssueFromModule, addIssueToModule },
@ -32,6 +43,13 @@ export const SidebarModuleSelect: React.FC<Props> = observer((props) => {
const [isUpdating, setIsUpdating] = useState(false); const [isUpdating, setIsUpdating] = useState(false);
const { data: projectModules } = useSWR(
workspaceSlug && projectId ? MODULE_LIST(projectId as string) : null,
workspaceSlug && projectId
? () => moduleService.getModules(workspaceSlug as string, projectId as string)
: null
);
const handleModuleStoreChange = async (moduleId: string) => { const handleModuleStoreChange = async (moduleId: string) => {
if (!workspaceSlug || !issueDetail || !moduleId) return; if (!workspaceSlug || !issueDetail || !moduleId) return;

View File

@ -12,15 +12,16 @@ import { IIssue, ISearchIssueResponse } from "types";
type Props = { type Props = {
onChange: (value: string) => void; onChange: (value: string) => void;
issueDetails: IIssue | undefined; issueDetails: IIssue | undefined;
projectId: string;
disabled?: boolean; disabled?: boolean;
}; };
export const SidebarParentSelect: React.FC<Props> = ({ onChange, issueDetails, disabled = false }) => { export const SidebarParentSelect: React.FC<Props> = ({ onChange, issueDetails, projectId, disabled = false }) => {
const [isParentModalOpen, setIsParentModalOpen] = useState(false); const [isParentModalOpen, setIsParentModalOpen] = useState(false);
const [selectedParentIssue, setSelectedParentIssue] = useState<ISearchIssueResponse | null>(null); const [selectedParentIssue, setSelectedParentIssue] = useState<ISearchIssueResponse | null>(null);
const router = useRouter(); const router = useRouter();
const { projectId, issueId } = router.query; const { issueId } = router.query;
return ( return (
<> <>

View File

@ -15,6 +15,7 @@ import { STATES_LIST } from "constants/fetch-keys";
type Props = { type Props = {
value: string; value: string;
projectId: string;
onChange: (val: string) => void; onChange: (val: string) => void;
disabled?: boolean; disabled?: boolean;
}; };
@ -22,9 +23,9 @@ type Props = {
// services // services
const stateService = new ProjectStateService(); const stateService = new ProjectStateService();
export const SidebarStateSelect: React.FC<Props> = ({ value, onChange, disabled = false }) => { export const SidebarStateSelect: React.FC<Props> = ({ value, projectId, onChange, disabled = false }) => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, inboxIssueId } = router.query; const { workspaceSlug, inboxIssueId } = router.query;
const { data: states } = useSWR( const { data: states } = useSWR(
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null, workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,

View File

@ -289,6 +289,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
render={({ field: { value } }) => ( render={({ field: { value } }) => (
<SidebarStateSelect <SidebarStateSelect
value={value} value={value}
projectId={projectId as string}
onChange={(val: string) => submitChanges({ state: val })} onChange={(val: string) => submitChanges({ state: val })}
disabled={!isAllowed || uneditable} disabled={!isAllowed || uneditable}
/> />
@ -310,6 +311,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
render={({ field: { value } }) => ( render={({ field: { value } }) => (
<SidebarAssigneeSelect <SidebarAssigneeSelect
value={value} value={value}
projectId={projectId as string}
onChange={(val: string[]) => submitChanges({ assignees: val })} onChange={(val: string[]) => submitChanges({ assignees: val })}
disabled={!isAllowed || uneditable} disabled={!isAllowed || uneditable}
/> />
@ -382,6 +384,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
onChange(val); onChange(val);
}} }}
issueDetails={issueDetail} issueDetails={issueDetail}
projectId={projectId as string}
disabled={!isAllowed || uneditable} disabled={!isAllowed || uneditable}
/> />
)} )}
@ -536,6 +539,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
<div className="space-y-1"> <div className="space-y-1">
<SidebarCycleSelect <SidebarCycleSelect
issueDetail={issueDetail} issueDetail={issueDetail}
projectId={projectId as string}
handleCycleChange={handleCycleChange} handleCycleChange={handleCycleChange}
disabled={!isAllowed || uneditable} disabled={!isAllowed || uneditable}
/> />
@ -551,6 +555,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
<div className="space-y-1"> <div className="space-y-1">
<SidebarModuleSelect <SidebarModuleSelect
issueDetail={issueDetail} issueDetail={issueDetail}
projectId={projectId as string}
handleModuleChange={handleModuleChange} handleModuleChange={handleModuleChange}
disabled={!isAllowed || uneditable} disabled={!isAllowed || uneditable}
/> />
@ -569,6 +574,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
<div className="space-y-1 sm:w-1/2"> <div className="space-y-1 sm:w-1/2">
<SidebarLabelSelect <SidebarLabelSelect
issueDetails={issueDetail} issueDetails={issueDetail}
projectId={projectId as string}
labelList={issueDetail?.labels ?? []} labelList={issueDetail?.labels ?? []}
submitChanges={submitChanges} submitChanges={submitChanges}
isNotAllowed={!isAllowed} isNotAllowed={!isAllowed}

View File

@ -10,6 +10,7 @@ import { CustomMenu, Tooltip } from "@plane/ui";
// types // types
import { IUser, IIssue } from "types"; import { IUser, IIssue } from "types";
import { ISubIssuesRootLoaders, ISubIssuesRootLoadersHandler } from "./root"; import { ISubIssuesRootLoaders, ISubIssuesRootLoadersHandler } from "./root";
import { EIssueActions } from "../issue-layouts/types";
export interface ISubIssues { export interface ISubIssues {
workspaceSlug: string; workspaceSlug: string;
@ -29,6 +30,7 @@ export interface ISubIssues {
issue?: IIssue | null issue?: IIssue | null
) => void; ) => void;
handleUpdateIssue: (issue: IIssue, data: Partial<IIssue>) => void; handleUpdateIssue: (issue: IIssue, data: Partial<IIssue>) => void;
handleDeleteIssue: (issue: IIssue) => Promise<void>;
} }
export const SubIssues: React.FC<ISubIssues> = ({ export const SubIssues: React.FC<ISubIssues> = ({
@ -45,17 +47,22 @@ export const SubIssues: React.FC<ISubIssues> = ({
copyText, copyText,
handleIssueCrudOperation, handleIssueCrudOperation,
handleUpdateIssue, handleUpdateIssue,
handleDeleteIssue,
}) => { }) => {
const router = useRouter(); const router = useRouter();
const { peekProjectId, peekIssueId } = router.query; const { peekProjectId, peekIssueId } = router.query;
const handleIssuePeekOverview = () => { const handleIssuePeekOverview = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const { query } = router; const { query } = router;
if (event.ctrlKey || event.metaKey) {
const issueUrl = `/${issue.workspace_detail.slug}/projects/${issue.project_detail.id}/issues/${issue?.id}`;
window.open(issueUrl, "_blank"); // Open link in a new tab
} else {
router.push({ router.push({
pathname: router.pathname, pathname: router.pathname,
query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project }, query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project },
}); });
}
}; };
return ( return (
@ -65,7 +72,13 @@ export const SubIssues: React.FC<ISubIssues> = ({
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
projectId={peekProjectId.toString()} projectId={peekProjectId.toString()}
issueId={peekIssueId.toString()} issueId={peekIssueId.toString()}
handleIssue={async (issueToUpdate) => await handleUpdateIssue(issue, { ...issue, ...issueToUpdate })} handleIssue={async (issueToUpdate, action) => {
if (action === EIssueActions.UPDATE) {
await handleUpdateIssue(issue, { ...issue, ...issueToUpdate });
} else if (action === EIssueActions.DELETE) {
await handleDeleteIssue(issue);
}
}}
/> />
)} )}
<div> <div>
@ -176,6 +189,7 @@ export const SubIssues: React.FC<ISubIssues> = ({
{issuesLoader.visibility.includes(issue?.id) && issue?.sub_issues_count > 0 && ( {issuesLoader.visibility.includes(issue?.id) && issue?.sub_issues_count > 0 && (
<SubIssuesRootList <SubIssuesRootList
handleDeleteIssue={handleDeleteIssue}
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
projectId={projectId} projectId={projectId}
parentIssue={issue} parentIssue={issue}

View File

@ -27,6 +27,7 @@ export interface ISubIssuesRootList {
issue?: IIssue | null issue?: IIssue | null
) => void; ) => void;
handleUpdateIssue: (issue: IIssue, data: Partial<IIssue>) => void; handleUpdateIssue: (issue: IIssue, data: Partial<IIssue>) => void;
handleDeleteIssue: (issue: IIssue) => Promise<void>
} }
const issueService = new IssueService(); const issueService = new IssueService();
@ -44,6 +45,7 @@ export const SubIssuesRootList: React.FC<ISubIssuesRootList> = ({
copyText, copyText,
handleIssueCrudOperation, handleIssueCrudOperation,
handleUpdateIssue, handleUpdateIssue,
handleDeleteIssue
}) => { }) => {
const { data: issues, isLoading } = useSWR( const { data: issues, isLoading } = useSWR(
workspaceSlug && projectId && parentIssue && parentIssue?.id ? SUB_ISSUES(parentIssue?.id) : null, workspaceSlug && projectId && parentIssue && parentIssue?.id ? SUB_ISSUES(parentIssue?.id) : null,
@ -70,6 +72,7 @@ export const SubIssuesRootList: React.FC<ISubIssuesRootList> = ({
issues.sub_issues.length > 0 && issues.sub_issues.length > 0 &&
issues.sub_issues.map((issue: IIssue) => ( issues.sub_issues.map((issue: IIssue) => (
<SubIssues <SubIssues
handleDeleteIssue={handleDeleteIssue}
key={`${issue?.id}`} key={`${issue?.id}`}
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
projectId={projectId} projectId={projectId}

View File

@ -179,7 +179,21 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => {
[updateIssueStructure, projectId, updateIssue, user, workspaceSlug] [updateIssueStructure, projectId, updateIssue, user, workspaceSlug]
); );
<<<<<<< HEAD
const isEditable = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; const isEditable = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
=======
const handleDeleteIssue = useCallback(
async (issue: IIssue) => {
if (!workspaceSlug || !projectId || !user) return;
await removeIssue(workspaceSlug.toString(), projectId.toString(), issue.id);
await mutate(SUB_ISSUES(parentIssue?.id));
},
[removeIssue, projectId, user, workspaceSlug, parentIssue?.id]
);
const isEditable = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
const mutateSubIssues = (parentIssueId: string | null) => { const mutateSubIssues = (parentIssueId: string | null) => {
if (parentIssueId) mutate(SUB_ISSUES(parentIssueId)); if (parentIssueId) mutate(SUB_ISSUES(parentIssueId));
@ -239,6 +253,7 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => {
{issuesLoader.visibility.includes(parentIssue?.id) && workspaceSlug && projectId && ( {issuesLoader.visibility.includes(parentIssue?.id) && workspaceSlug && projectId && (
<div className="border border-b-0 border-custom-border-100"> <div className="border border-b-0 border-custom-border-100">
<SubIssuesRootList <SubIssuesRootList
handleDeleteIssue={handleDeleteIssue}
workspaceSlug={workspaceSlug.toString()} workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()} projectId={projectId.toString()}
parentIssue={parentIssue} parentIssue={parentIssue}

View File

@ -94,11 +94,15 @@ export const ModulesListView: React.FC = observer(() => {
description: description:
"A cart module, a chassis module, and a warehouse module are all good example of this grouping.", "A cart module, a chassis module, and a warehouse module are all good example of this grouping.",
}} }}
primaryButton={{ primaryButton={
isEditingAllowed
? {
icon: <Plus className="h-4 w-4" />, icon: <Plus className="h-4 w-4" />,
text: "Build your first module", text: "Build your first module",
onClick: () => commandPaletteStore.toggleCreateModuleModal(true), onClick: () => commandPaletteStore.toggleCreateModuleModal(true),
}} }
: null
}
disabled={!isEditingAllowed} disabled={!isEditingAllowed}
/> />
)} )}

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } 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 { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
@ -25,7 +25,8 @@ import {
} from "helpers/date-time.helper"; } from "helpers/date-time.helper";
import { copyUrlToClipboard } from "helpers/string.helper"; import { copyUrlToClipboard } from "helpers/string.helper";
// types // types
import { ILinkDetails, IModule, ModuleLink } from "types"; import { IIssueFilterOptions, ILinkDetails, IModule, ModuleLink } from "types";
import { EFilterType } from "store/issues/types";
// constant // constant
import { MODULE_STATUS } from "constants/module"; import { MODULE_STATUS } from "constants/module";
import { EUserProjectRoles } from "constants/project"; import { EUserProjectRoles } from "constants/project";
@ -55,9 +56,22 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId, peekModule } = router.query; const { workspaceSlug, projectId, peekModule } = router.query;
// store hooks // store hooks
const { const {
<<<<<<< HEAD
membership: { currentProjectRole }, membership: { currentProjectRole },
} = useUser(); } = useUser();
const { getModuleById, updateModuleDetails, createModuleLink, updateModuleLink, deleteModuleLink } = useModule(); const { getModuleById, updateModuleDetails, createModuleLink, updateModuleLink, deleteModuleLink } = useModule();
=======
module: {
moduleDetails: _moduleDetails,
updateModuleDetails,
createModuleLink,
updateModuleLink,
deleteModuleLink,
},
moduleIssuesFilter: { issueFilters, updateFilters },
user: userStore,
} = useMobxStore();
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
const moduleDetails = getModuleById(moduleId); const moduleDetails = getModuleById(moduleId);
@ -204,6 +218,25 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
} }
}; };
const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !projectId) return;
const newValues = issueFilters?.filters?.[key] ?? [];
if (Array.isArray(value)) {
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
});
} else {
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
}
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, { [key]: newValues }, moduleId);
},
[workspaceSlug, projectId, moduleId, issueFilters, updateFilters]
);
useEffect(() => { useEffect(() => {
if (moduleDetails) if (moduleDetails)
reset({ reset({
@ -537,6 +570,8 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
totalIssues={moduleDetails.total_issues} totalIssues={moduleDetails.total_issues}
module={moduleDetails} module={moduleDetails}
isPeekView={Boolean(peekModule)} isPeekView={Boolean(peekModule)}
filters={issueFilters?.filters}
handleFiltersUpdate={handleFiltersUpdate}
/> />
</div> </div>
)} )}
@ -596,6 +631,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
<Info className="h-3.5 w-3.5 stroke-[1.5] text-custom-text-300" /> <Info className="h-3.5 w-3.5 stroke-[1.5] text-custom-text-300" />
<span className="p-0.5 text-xs text-custom-text-300">No links added yet</span> <span className="p-0.5 text-xs text-custom-text-300">No links added yet</span>
</div> </div>
{isEditingAllowed && (
<button <button
className="flex items-center gap-1.5 text-sm font-medium text-custom-primary-100" className="flex items-center gap-1.5 text-sm font-medium text-custom-primary-100"
onClick={() => setModuleLinkModal(true)} onClick={() => setModuleLinkModal(true)}
@ -603,6 +639,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
<Plus className="h-3 w-3" /> <Plus className="h-3 w-3" />
Add link Add link
</button> </button>
)}
</div> </div>
)} )}
</div> </div>

View File

@ -93,13 +93,17 @@ export const WorkspaceDashboardView = observer(() => {
direction: "right", direction: "right",
description: "A project could be a products roadmap, a marketing campaign, or launching a new car.", description: "A project could be a products roadmap, a marketing campaign, or launching a new car.",
}} }}
primaryButton={{ primaryButton={
isEditingAllowed
? {
text: "Build your first project", text: "Build your first project",
onClick: () => { onClick: () => {
setTrackElement("DASHBOARD_PAGE"); setTrackElement("DASHBOARD_PAGE");
commandPaletteStore.toggleCreateProjectModal(true); commandPaletteStore.toggleCreateProjectModal(true);
}, },
}} }
: null
}
disabled={!isEditingAllowed} disabled={!isEditingAllowed}
/> />
) )

View File

@ -59,11 +59,15 @@ export const PagesListView: FC<IPagesListView> = observer((props) => {
"We wrote Parth and Meeras love story. You could write your projects mission, goals, and eventual vision.", "We wrote Parth and Meeras love story. You could write your projects mission, goals, and eventual vision.",
direction: "right", direction: "right",
}} }}
primaryButton={{ primaryButton={
isEditingAllowed
? {
icon: <Plus className="h-4 w-4" />, icon: <Plus className="h-4 w-4" />,
text: "Create your first page", text: "Create your first page",
onClick: () => toggleCreatePageModal(true), onClick: () => toggleCreatePageModal(true),
}} }
: null
}
disabled={!isEditingAllowed} disabled={!isEditingAllowed}
/> />
)} )}

View File

@ -66,11 +66,15 @@ export const RecentPagesList: FC = observer(() => {
"We wrote Parth and Meeras love story. You could write your projects mission, goals, and eventual vision.", "We wrote Parth and Meeras love story. You could write your projects mission, goals, and eventual vision.",
direction: "right", direction: "right",
}} }}
primaryButton={{ primaryButton={
isEditingAllowed
? {
icon: <Plus className="h-4 w-4" />, icon: <Plus className="h-4 w-4" />,
text: "Create your first page", text: "Create your first page",
onClick: () => commandPaletteStore.toggleCreatePageModal(true), onClick: () => commandPaletteStore.toggleCreatePageModal(true),
}} }
: null
}
disabled={!isEditingAllowed} disabled={!isEditingAllowed}
/> />
</> </>

View File

@ -64,13 +64,17 @@ export const ProjectCardList = observer(() => {
direction: "right", direction: "right",
description: "A project could be a products roadmap, a marketing campaign, or launching a new car.", description: "A project could be a products roadmap, a marketing campaign, or launching a new car.",
}} }}
primaryButton={{ primaryButton={
isEditingAllowed
? {
text: "Start your first project", text: "Start your first project",
onClick: () => { onClick: () => {
setTrackElement("PROJECTS_EMPTY_STATE"); setTrackElement("PROJECTS_EMPTY_STATE");
commandPaletteStore.toggleCreateProjectModal(true); commandPaletteStore.toggleCreateProjectModal(true);
}, },
}} }
: null
}
disabled={!isEditingAllowed} disabled={!isEditingAllowed}
/> />
)} )}

View File

@ -308,8 +308,8 @@ export const CreateProjectModal: FC<Props> = observer((props) => {
message: "Identifier must at least be of 1 character", message: "Identifier must at least be of 1 character",
}, },
maxLength: { maxLength: {
value: 12, value: 6,
message: "Identifier must at most be of 12 characters", message: "Identifier must at most be of 6 characters",
}, },
}} }}
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (

View File

@ -231,7 +231,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
/> />
</div> </div>
<div className="flex w-full items-center justify-between gap-10"> <div className="flex w-full items-baseline justify-between gap-10">
<div className="flex w-1/2 flex-col gap-1"> <div className="flex w-1/2 flex-col gap-1">
<h4 className="text-sm">Identifier</h4> <h4 className="text-sm">Identifier</h4>
<Controller <Controller
@ -245,8 +245,8 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
message: "Identifier must at least be of 1 character", message: "Identifier must at least be of 1 character",
}, },
maxLength: { maxLength: {
value: 12, value: 6,
message: "Identifier must at most be of 5 characters", message: "Identifier must at most be of 6 characters",
}, },
}} }}
render={({ field: { value, ref } }) => ( render={({ field: { value, ref } }) => (
@ -264,6 +264,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
/> />
)} )}
/> />
<span className="text-xs text-red-500">{errors?.identifier?.message}</span>
</div> </div>
<div className="flex w-1/2 flex-col gap-1"> <div className="flex w-1/2 flex-col gap-1">

View File

@ -93,6 +93,7 @@ export const PrioritySelect: React.FC<Props> = ({
className={`flex h-full w-full items-center justify-between gap-1 ${ className={`flex h-full w-full items-center justify-between gap-1 ${
disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer" disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer"
} ${buttonClassName}`} } ${buttonClassName}`}
onClick={(e) => e.stopPropagation()}
> >
{label} {label}
{!hideDropdownArrow && !disabled && <ChevronDown className="h-2.5 w-2.5" aria-hidden="true" />} {!hideDropdownArrow && !disabled && <ChevronDown className="h-2.5 w-2.5" aria-hidden="true" />}
@ -127,6 +128,7 @@ export const PrioritySelect: React.FC<Props> = ({
active ? "bg-custom-background-80" : "" active ? "bg-custom-background-80" : ""
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}` } ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
} }
onClick={(e) => e.stopPropagation()}
> >
{({ selected }) => ( {({ selected }) => (
<> <>

View File

@ -73,11 +73,23 @@ export const ProjectViewsList = observer(() => {
description: "You can create a view from here with as many properties as filters as you see fit.", description: "You can create a view from here with as many properties as filters as you see fit.",
direction: "right", direction: "right",
}} }}
<<<<<<< HEAD
primaryButton={{ primaryButton={{
icon: <Plus size={14} strokeWidth={2} />, icon: <Plus size={14} strokeWidth={2} />,
text: "Build your first view", text: "Build your first view",
onClick: () => toggleCreateViewModal(true), onClick: () => toggleCreateViewModal(true),
}} }}
=======
primaryButton={
isEditingAllowed
? {
icon: <Plus size={14} strokeWidth={2} />,
text: "Build your first view",
onClick: () => commandPaletteStore.toggleCreateViewModal(true),
}
: null
}
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
disabled={!isEditingAllowed} disabled={!isEditingAllowed}
/> />
)} )}

View File

@ -9,7 +9,7 @@ import { CreateUpdateWorkspaceViewModal } from "components/workspace";
// icon // icon
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
// constants // constants
import { DEFAULT_GLOBAL_VIEWS_LIST } from "constants/workspace"; import { DEFAULT_GLOBAL_VIEWS_LIST, EUserWorkspaceRoles } from "constants/workspace";
export const GlobalViewsHeader: React.FC = observer(() => { export const GlobalViewsHeader: React.FC = observer(() => {
const [createViewModal, setCreateViewModal] = useState(false); const [createViewModal, setCreateViewModal] = useState(false);
@ -17,7 +17,10 @@ export const GlobalViewsHeader: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, globalViewId } = router.query; const { workspaceSlug, globalViewId } = router.query;
const { globalViews: globalViewsStore } = useMobxStore(); const {
globalViews: globalViewsStore,
user: { currentWorkspaceRole },
} = useMobxStore();
// bring the active view to the centre of the header // bring the active view to the centre of the header
useEffect(() => { useEffect(() => {
@ -28,11 +31,13 @@ export const GlobalViewsHeader: React.FC = observer(() => {
if (activeTabElement) activeTabElement.scrollIntoView({ behavior: "smooth", inline: "center" }); if (activeTabElement) activeTabElement.scrollIntoView({ behavior: "smooth", inline: "center" });
}, [globalViewId]); }, [globalViewId]);
const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
const isTabSelected = (tabKey: string) => router.pathname.includes(tabKey); const isTabSelected = (tabKey: string) => router.pathname.includes(tabKey);
return ( return (
<> <>
<CreateUpdateWorkspaceViewModal isOpen={createViewModal} onClose={() => setCreateViewModal(false)} /> <CreateUpdateWorkspaceViewModal isOpen={createViewModal} onClose={() => setCreateViewModal(false)} />
<div className="group relative flex w-full items-center overflow-x-scroll border-b border-custom-border-200 px-4"> <div className="group relative flex w-full items-center overflow-x-scroll border-b border-custom-border-200 px-4 py-2">
{DEFAULT_GLOBAL_VIEWS_LIST.map((tab) => ( {DEFAULT_GLOBAL_VIEWS_LIST.map((tab) => (
<Link key={tab.key} href={`/${workspaceSlug}/workspace-views/${tab.key}`}> <Link key={tab.key} href={`/${workspaceSlug}/workspace-views/${tab.key}`}>
<span <span
@ -62,13 +67,15 @@ export const GlobalViewsHeader: React.FC = observer(() => {
</Link> </Link>
))} ))}
{isAuthorizedUser && (
<button <button
type="button" type="button"
className="sticky -right-4 flex w-12 flex-shrink-0 items-center justify-center border-transparent bg-custom-background-100 py-3 hover:border-custom-border-200 hover:text-custom-text-400" className="sticky -right-4 flex w-12 flex-shrink-0 items-center justify-center border-transparent bg-custom-background-100 hover:border-custom-border-200 hover:text-custom-text-400"
onClick={() => setCreateViewModal(true)} onClick={() => setCreateViewModal(true)}
> >
<Plus className="h-4 w-4 text-custom-primary-200" /> <Plus className="h-4 w-4 text-custom-primary-200" />
</button> </button>
)}
</div> </div>
</> </>
); );

View File

@ -84,7 +84,7 @@ export const ISSUE_ORDER_BY_OPTIONS: {
{ key: "-updated_at", title: "Last Updated" }, { key: "-updated_at", title: "Last Updated" },
{ key: "start_date", title: "Start Date" }, { key: "start_date", title: "Start Date" },
{ key: "target_date", title: "Due Date" }, { key: "target_date", title: "Due Date" },
{ key: "priority", title: "Priority" }, { key: "-priority", title: "Priority" },
]; ];
export const ISSUE_FILTER_OPTIONS: { export const ISSUE_FILTER_OPTIONS: {
@ -233,7 +233,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
display_properties: true, display_properties: true,
display_filters: { display_filters: {
group_by: ["state_detail.group", "priority", "project", "labels", null], group_by: ["state_detail.group", "priority", "project", "labels", null],
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"], order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
type: [null, "active", "backlog"], type: [null, "active", "backlog"],
}, },
extra_options: { extra_options: {
@ -246,7 +246,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
display_properties: true, display_properties: true,
display_filters: { display_filters: {
group_by: ["state_detail.group", "priority", "project", "labels"], group_by: ["state_detail.group", "priority", "project", "labels"],
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"], order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
type: [null, "active", "backlog"], type: [null, "active", "backlog"],
}, },
extra_options: { extra_options: {
@ -261,7 +261,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
display_properties: true, display_properties: true,
display_filters: { display_filters: {
group_by: ["state", "state_detail.group", "priority", "labels", "assignees", "created_by", null], group_by: ["state", "state_detail.group", "priority", "labels", "assignees", "created_by", null],
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"], order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
type: [null, "active", "backlog"], type: [null, "active", "backlog"],
}, },
extra_options: { extra_options: {
@ -276,7 +276,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
display_properties: true, display_properties: true,
display_filters: { display_filters: {
group_by: ["state_detail.group", "priority", "project", "labels", null], group_by: ["state_detail.group", "priority", "project", "labels", null],
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"], order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
type: [null, "active", "backlog"], type: [null, "active", "backlog"],
}, },
extra_options: { extra_options: {
@ -289,7 +289,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
display_properties: true, display_properties: true,
display_filters: { display_filters: {
group_by: ["state_detail.group", "priority", "project", "labels"], group_by: ["state_detail.group", "priority", "project", "labels"],
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"], order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
type: [null, "active", "backlog"], type: [null, "active", "backlog"],
}, },
extra_options: { extra_options: {
@ -317,7 +317,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
display_properties: true, display_properties: true,
display_filters: { display_filters: {
group_by: ["state", "priority", "labels", "assignees", "created_by", null], group_by: ["state", "priority", "labels", "assignees", "created_by", null],
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"], order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
type: [null, "active", "backlog"], type: [null, "active", "backlog"],
}, },
extra_options: { extra_options: {
@ -331,7 +331,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
display_filters: { display_filters: {
group_by: ["state", "priority", "labels", "assignees", "created_by"], group_by: ["state", "priority", "labels", "assignees", "created_by"],
sub_group_by: ["state", "priority", "labels", "assignees", "created_by", null], sub_group_by: ["state", "priority", "labels", "assignees", "created_by", null],
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority", "target_date"], order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority", "target_date"],
type: [null, "active", "backlog"], type: [null, "active", "backlog"],
}, },
extra_options: { extra_options: {
@ -354,7 +354,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
filters: ["priority", "state", "assignees", "mentions", "created_by", "labels", "start_date", "target_date"], filters: ["priority", "state", "assignees", "mentions", "created_by", "labels", "start_date", "target_date"],
display_properties: true, display_properties: true,
display_filters: { display_filters: {
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"], order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
type: [null, "active", "backlog"], type: [null, "active", "backlog"],
}, },
extra_options: { extra_options: {
@ -366,7 +366,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
filters: ["priority", "state", "assignees", "mentions", "created_by", "labels", "start_date", "target_date"], filters: ["priority", "state", "assignees", "mentions", "created_by", "labels", "start_date", "target_date"],
display_properties: false, display_properties: false,
display_filters: { display_filters: {
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "priority"], order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
type: [null, "active", "backlog"], type: [null, "active", "backlog"],
}, },
extra_options: { extra_options: {

View File

@ -31,6 +31,18 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
const { children } = props; const { children } = props;
// store // store
const { const {
<<<<<<< HEAD
=======
user: { fetchUserProjectInfo, projectMemberInfo, hasPermissionToCurrentProject },
project: { fetchProjectDetails, workspaceProjects },
projectLabel: { fetchProjectLabels },
projectMember: { fetchProjectMembers },
projectState: { fetchProjectStates },
projectEstimates: { fetchProjectEstimates },
cycle: { fetchCycles },
module: { fetchModules },
projectViews: { fetchAllViews },
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
inbox: { fetchInboxesList, isInboxEnabled }, inbox: { fetchInboxesList, isInboxEnabled },
} = useMobxStore(); } = useMobxStore();
const { const {
@ -67,43 +79,77 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
); );
// fetching project labels // fetching project labels
useSWR( useSWR(
workspaceSlug && projectId ? `PROJECT_LABELS_${workspaceSlug}_${projectId}` : null, workspaceSlug && projectId && hasPermissionToCurrentProject ? `PROJECT_LABELS_${workspaceSlug}_${projectId}` : null,
workspaceSlug && projectId ? () => fetchProjectLabels(workspaceSlug.toString(), projectId.toString()) : null workspaceSlug && projectId && hasPermissionToCurrentProject
? () => fetchProjectLabels(workspaceSlug.toString(), projectId.toString())
: null
); );
// fetching project members // fetching project members
useSWR( useSWR(
workspaceSlug && projectId ? `PROJECT_MEMBERS_${workspaceSlug}_${projectId}` : null, workspaceSlug && projectId && hasPermissionToCurrentProject
workspaceSlug && projectId ? () => fetchProjectMembers(workspaceSlug.toString(), projectId.toString()) : null ? `PROJECT_MEMBERS_${workspaceSlug}_${projectId}`
: null,
workspaceSlug && projectId && hasPermissionToCurrentProject
? () => fetchProjectMembers(workspaceSlug.toString(), projectId.toString())
: null
); );
// fetching project states // fetching project states
useSWR( useSWR(
workspaceSlug && projectId ? `PROJECT_STATES_${workspaceSlug}_${projectId}` : null, workspaceSlug && projectId && hasPermissionToCurrentProject ? `PROJECT_STATES_${workspaceSlug}_${projectId}` : null,
workspaceSlug && projectId ? () => fetchProjectStates(workspaceSlug.toString(), projectId.toString()) : null workspaceSlug && projectId && hasPermissionToCurrentProject
? () => fetchProjectStates(workspaceSlug.toString(), projectId.toString())
: null
); );
// fetching project estimates // fetching project estimates
useSWR( useSWR(
workspaceSlug && projectId ? `PROJECT_ESTIMATES_${workspaceSlug}_${projectId}` : null, workspaceSlug && projectId && hasPermissionToCurrentProject
workspaceSlug && projectId ? () => fetchProjectEstimates(workspaceSlug.toString(), projectId.toString()) : null ? `PROJECT_ESTIMATES_${workspaceSlug}_${projectId}`
: null,
workspaceSlug && projectId && hasPermissionToCurrentProject
? () => fetchProjectEstimates(workspaceSlug.toString(), projectId.toString())
: null
); );
// fetching project cycles // fetching project cycles
useSWR( useSWR(
<<<<<<< HEAD
workspaceSlug && projectId ? `PROJECT_ALL_CYCLES_${workspaceSlug}_${projectId}` : null, workspaceSlug && projectId ? `PROJECT_ALL_CYCLES_${workspaceSlug}_${projectId}` : null,
workspaceSlug && projectId ? () => fetchAllCycles(workspaceSlug.toString(), projectId.toString()) : null workspaceSlug && projectId ? () => fetchAllCycles(workspaceSlug.toString(), projectId.toString()) : null
=======
workspaceSlug && projectId && hasPermissionToCurrentProject
? `PROJECT_ALL_CYCLES_${workspaceSlug}_${projectId}`
: null,
workspaceSlug && projectId && hasPermissionToCurrentProject
? () => fetchCycles(workspaceSlug.toString(), projectId.toString(), "all")
: null
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
); );
// fetching project modules // fetching project modules
useSWR( useSWR(
workspaceSlug && projectId ? `PROJECT_MODULES_${workspaceSlug}_${projectId}` : null, workspaceSlug && projectId && hasPermissionToCurrentProject
workspaceSlug && projectId ? () => fetchModules(workspaceSlug.toString(), projectId.toString()) : null ? `PROJECT_MODULES_${workspaceSlug}_${projectId}`
: null,
workspaceSlug && projectId && hasPermissionToCurrentProject
? () => fetchModules(workspaceSlug.toString(), projectId.toString())
: null
); );
// fetching project views // fetching project views
useSWR( useSWR(
<<<<<<< HEAD
workspaceSlug && projectId ? `PROJECT_VIEWS_${workspaceSlug}_${projectId}` : null, workspaceSlug && projectId ? `PROJECT_VIEWS_${workspaceSlug}_${projectId}` : null,
workspaceSlug && projectId ? () => fetchViews(workspaceSlug.toString(), projectId.toString()) : null workspaceSlug && projectId ? () => fetchViews(workspaceSlug.toString(), projectId.toString()) : null
=======
workspaceSlug && projectId && hasPermissionToCurrentProject ? `PROJECT_VIEWS_${workspaceSlug}_${projectId}` : null,
workspaceSlug && projectId && hasPermissionToCurrentProject
? () => fetchAllViews(workspaceSlug.toString(), projectId.toString())
: null
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
); );
// fetching project inboxes if inbox is enabled // fetching project inboxes if inbox is enabled
useSWR( useSWR(
workspaceSlug && projectId && isInboxEnabled ? `PROJECT_INBOXES_${workspaceSlug}_${projectId}` : null, workspaceSlug && projectId && hasPermissionToCurrentProject && isInboxEnabled
workspaceSlug && projectId && isInboxEnabled ? `PROJECT_INBOXES_${workspaceSlug}_${projectId}`
: null,
workspaceSlug && projectId && hasPermissionToCurrentProject && isInboxEnabled
? () => fetchInboxesList(workspaceSlug.toString(), projectId.toString()) ? () => fetchInboxesList(workspaceSlug.toString(), projectId.toString())
: null, : null,
{ {
@ -115,7 +161,7 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
const projectExists = projectId ? getProjectById(projectId.toString()) : null; const projectExists = projectId ? getProjectById(projectId.toString()) : null;
// check if the project member apis is loading // check if the project member apis is loading
if (!projectMemberInfo && projectId && hasPermissionToProject[projectId.toString()] === null) if (!projectMemberInfo && projectId && hasPermissionToCurrentProject === null)
return ( return (
<div className="grid h-screen place-items-center bg-custom-background-100 p-4"> <div className="grid h-screen place-items-center bg-custom-background-100 p-4">
<div className="flex flex-col items-center gap-3 text-center"> <div className="flex flex-col items-center gap-3 text-center">
@ -125,10 +171,10 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
); );
// check if the user don't have permission to access the project // check if the user don't have permission to access the project
if (projectExists && projectId && hasPermissionToProject[projectId.toString()] === false) return <JoinProject />; if (projectExists && projectId && hasPermissionToCurrentProject === false) return <JoinProject />;
// check if the project info is not found. // check if the project info is not found.
if (!projectExists && projectId && hasPermissionToProject[projectId.toString()] === false) if (!projectExists && projectId && hasPermissionToCurrentProject === false)
return ( return (
<div className="container grid h-screen place-items-center bg-custom-background-100"> <div className="container grid h-screen place-items-center bg-custom-background-100">
<EmptyState <EmptyState

View File

@ -2,8 +2,12 @@ import { FC, ReactNode } 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 useSWR from "swr"; import useSWR from "swr";
<<<<<<< HEAD
// hooks // hooks
import { useUser, useWorkspace } from "hooks/store"; import { useUser, useWorkspace } from "hooks/store";
=======
import useSWRImmutable from "swr/immutable";
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
// ui // ui
import { Spinner } from "@plane/ui"; import { Spinner } from "@plane/ui";
@ -29,7 +33,7 @@ export const UserAuthWrapper: FC<IUserAuthWrapper> = observer((props) => {
shouldRetryOnError: false, shouldRetryOnError: false,
}); });
// fetching current user instance admin status // fetching current user instance admin status
useSWR("CURRENT_USER_INSTANCE_ADMIN_STATUS", () => fetchCurrentUserInstanceAdminStatus(), { useSWRImmutable("CURRENT_USER_INSTANCE_ADMIN_STATUS", () => fetchCurrentUserInstanceAdminStatus(), {
shouldRetryOnError: false, shouldRetryOnError: false,
}); });
// fetching user settings // fetching user settings

View File

@ -33,23 +33,30 @@ export const WorkspaceAuthWrapper: FC<IWorkspaceAuthWrapper> = observer((props)
); );
// fetching workspace projects // fetching workspace projects
useSWR( useSWR(
workspaceSlug ? `WORKSPACE_PROJECTS_${workspaceSlug}` : null, workspaceSlug && hasPermissionToCurrentWorkspace ? `WORKSPACE_PROJECTS_${workspaceSlug}` : null,
workspaceSlug ? () => fetchProjects(workspaceSlug.toString()) : null workspaceSlug && hasPermissionToCurrentWorkspace ? () => fetchProjects(workspaceSlug.toString()) : null
); );
// fetch workspace members // fetch workspace members
useSWR( useSWR(
workspaceSlug ? `WORKSPACE_MEMBERS_${workspaceSlug}` : null, workspaceSlug && hasPermissionToCurrentWorkspace ? `WORKSPACE_MEMBERS_${workspaceSlug}` : null,
workspaceSlug ? () => fetchWorkspaceMembers(workspaceSlug.toString()) : null workspaceSlug && hasPermissionToCurrentWorkspace ? () => fetchWorkspaceMembers(workspaceSlug.toString()) : null
); );
// fetch workspace labels // fetch workspace labels
useSWR( useSWR(
workspaceSlug ? `WORKSPACE_LABELS_${workspaceSlug}` : null, workspaceSlug && hasPermissionToCurrentWorkspace ? `WORKSPACE_LABELS_${workspaceSlug}` : null,
workspaceSlug ? () => fetchWorkspaceLabels(workspaceSlug.toString()) : null workspaceSlug && hasPermissionToCurrentWorkspace ? () => fetchWorkspaceLabels(workspaceSlug.toString()) : null
); );
// fetch workspace user projects role // fetch workspace user projects role
useSWR( useSWR(
<<<<<<< HEAD
workspaceSlug ? `WORKSPACE_PROJECTS_ROLE_${workspaceSlug}` : null, workspaceSlug ? `WORKSPACE_PROJECTS_ROLE_${workspaceSlug}` : null,
workspaceSlug ? () => membership.fetchUserWorkspaceProjectsRole(workspaceSlug.toString()) : null workspaceSlug ? () => membership.fetchUserWorkspaceProjectsRole(workspaceSlug.toString()) : null
=======
workspaceSlug && hasPermissionToCurrentWorkspace ? `WORKSPACE_PROJECTS_ROLE_${workspaceSlug}` : null,
workspaceSlug && hasPermissionToCurrentWorkspace
? () => fetchWorkspaceUserProjectsRole(workspaceSlug.toString())
: null
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
); );
// while data is being loaded // while data is being loaded

View File

@ -78,13 +78,17 @@ const ProjectCyclesPage: NextPageWithLayout = observer(() => {
description: description:
"A sprint, an iteration, and or any other term you use for weekly or fortnightly tracking of work is a cycle.", "A sprint, an iteration, and or any other term you use for weekly or fortnightly tracking of work is a cycle.",
}} }}
primaryButton={{ primaryButton={
isEditingAllowed
? {
icon: <Plus className="h-4 w-4" />, icon: <Plus className="h-4 w-4" />,
text: "Set your first cycle", text: "Set your first cycle",
onClick: () => { onClick: () => {
setCreateModal(true); setCreateModal(true);
}, },
}} }
: null
}
disabled={!isEditingAllowed} disabled={!isEditingAllowed}
/> />
</div> </div>

View File

@ -503,7 +503,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
projectId={projectId as string} projectId={projectId as string}
issueId={peekIssueId ? (peekIssueId as string) : ""} issueId={peekIssueId ? (peekIssueId as string) : ""}
isArchived={false} isArchived={false}
handleIssue={(issueToUpdate) => { handleIssue={async (issueToUpdate, action) => {
if (peekIssueId && typeof peekIssueId === "string") { if (peekIssueId && typeof peekIssueId === "string") {
handleUpdateIssue(peekIssueId, issueToUpdate); handleUpdateIssue(peekIssueId, issueToUpdate);
} }

View File

@ -25,7 +25,7 @@ const ProfileActivityPage: NextPageWithLayout = () => {
const { data: userActivity } = useSWR(USER_ACTIVITY, () => userService.getUserActivity()); const { data: userActivity } = useSWR(USER_ACTIVITY, () => userService.getUserActivity());
return ( return (
<section className="mx-auto mt-16 flex h-full w-full flex-col overflow-hidden px-8 pb-8 lg:w-3/5"> <section className="mx-auto pt-16 flex h-full w-full flex-col overflow-hidden px-8 pb-8 lg:w-3/5">
<div className="flex items-center border-b border-custom-border-100 pb-3.5"> <div className="flex items-center border-b border-custom-border-100 pb-3.5">
<h3 className="text-xl font-medium">Activity</h3> <h3 className="text-xl font-medium">Activity</h3>
</div> </div>
@ -180,7 +180,7 @@ const ProfileActivityPage: NextPageWithLayout = () => {
</ul> </ul>
</div> </div>
) : ( ) : (
<Loader className="space-y-5"> <Loader className="space-y-5 mt-5">
<Loader.Item height="40px" /> <Loader.Item height="40px" />
<Loader.Item height="40px" /> <Loader.Item height="40px" />
<Loader.Item height="40px" /> <Loader.Item height="40px" />

View File

@ -88,7 +88,7 @@ const ChangePasswordPage: NextPageWithLayout = observer(() => {
return ( return (
<form <form
onSubmit={handleSubmit(handleChangePassword)} onSubmit={handleSubmit(handleChangePassword)}
className="mx-auto mt-16 flex h-full w-full flex-col gap-8 px-8 pb-8 lg:w-3/5" className="mx-auto pt-16 flex h-full w-full flex-col gap-8 px-8 pb-8 lg:w-3/5"
> >
<h3 className="text-xl font-medium">Change password</h3> <h3 className="text-xl font-medium">Change password</h3>
<div className="grid-col grid w-full grid-cols-1 items-center justify-between gap-10 xl:grid-cols-2 2xl:grid-cols-3"> <div className="grid-col grid w-full grid-cols-1 items-center justify-between gap-10 xl:grid-cols-2 2xl:grid-cols-3">

View File

@ -168,7 +168,7 @@ const ProfileSettingsPage: NextPageWithLayout = () => {
)} )}
/> />
<DeactivateAccountModal isOpen={deactivateAccountModal} onClose={() => setDeactivateAccountModal(false)} /> <DeactivateAccountModal isOpen={deactivateAccountModal} onClose={() => setDeactivateAccountModal(false)} />
<div className="mx-auto mt-16 flex h-full w-full flex-col space-y-10 overflow-y-auto px-8 pb-8 lg:w-3/5"> <div className="mx-auto flex h-full w-full flex-col space-y-10 overflow-y-auto pt-16 px-8 pb-8 lg:w-3/5">
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="flex w-full flex-col gap-8"> <div className="flex w-full flex-col gap-8">
<div className="relative h-44 w-full"> <div className="relative h-44 w-full">

View File

@ -48,7 +48,7 @@ const ProfilePreferencesPage: NextPageWithLayout = observer(() => {
return ( return (
<> <>
{currentUser ? ( {currentUser ? (
<div className="mx-auto mt-16 h-full w-full overflow-y-auto px-8 pb-8 lg:w-3/5"> <div className="mx-auto pt-16 h-full w-full overflow-y-auto px-8 pb-8 lg:w-3/5">
<div className="flex items-center border-b border-custom-border-100 pb-3.5"> <div className="flex items-center border-b border-custom-border-100 pb-3.5">
<h3 className="text-xl font-medium">Preferences</h3> <h3 className="text-xl font-medium">Preferences</h3>
</div> </div>

View File

@ -28,19 +28,149 @@ export class IssueStore implements IIssueStore {
// observable // observable
allIssues: observable, allIssues: observable,
// actions // actions
<<<<<<< HEAD
addIssue: action, addIssue: action,
updateIssue: action, updateIssue: action,
removeIssue: action, removeIssue: action,
=======
fetchIssues: action,
updateIssueStructure: action,
removeIssueFromStructure: action,
updateGanttIssueStructure: action,
});
this.rootStore = _rootStore;
this.issueService = new IssueService();
autorun(() => {
const workspaceSlug = this.rootStore.workspace.workspaceSlug;
const projectId = this.rootStore.project.projectId;
const hasPermissionToCurrentProject = this.rootStore.user.hasPermissionToCurrentProject;
if (
workspaceSlug &&
projectId &&
hasPermissionToCurrentProject &&
this.rootStore.issueFilter.userFilters &&
this.rootStore.issueFilter.userDisplayFilters
)
this.fetchIssues(workspaceSlug, projectId, "mutation");
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
}); });
} }
addIssue = (issues: IIssue[]) => { addIssue = (issues: IIssue[]) => {
if (issues && issues.length <= 0) return; if (issues && issues.length <= 0) return;
<<<<<<< HEAD
const _issues = { ...this.allIssues }; const _issues = { ...this.allIssues };
issues.forEach((issue) => { issues.forEach((issue) => {
_issues[issue.id] = issue; _issues[issue.id] = issue;
}); });
=======
const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null;
const issueGroup = this.rootStore?.issueFilter?.userDisplayFilters?.group_by || null;
const issueSubGroup = this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by || null;
if (!issueLayout) return null;
const _issueState = groupedLayouts.includes(issueLayout)
? issueGroup
? issueSubGroup
? "groupWithSubGroups"
: "grouped"
: "ungrouped"
: ungroupedLayouts.includes(issueLayout)
? "ungrouped"
: null;
return _issueState || null;
}
get getIssues() {
const projectId: string | null = this.rootStore?.project?.projectId;
const issueType = this.getIssueType;
if (!projectId || !issueType) return null;
return this.issues?.[projectId]?.[issueType] || null;
}
get getIssuesCount() {
const issueType = this.getIssueType;
let issuesCount = 0;
if (issueType === "grouped") {
const issues = this.getIssues as IIssueGroupedStructure;
if (!issues) return 0;
Object.keys(issues).map((group_id) => {
issuesCount += issues[group_id].length;
});
}
if (issueType === "groupWithSubGroups") {
const issues = this.getIssues as IIssueGroupWithSubGroupsStructure;
if (!issues) return 0;
Object.keys(issues).map((sub_group_id) => {
Object.keys(issues[sub_group_id]).map((group_id) => {
issuesCount += issues[sub_group_id][group_id].length;
});
});
}
if (issueType === "ungrouped") {
const issues = this.getIssues as IIssueUnGroupedStructure;
if (!issues) return 0;
issuesCount = issues.length;
}
return issuesCount;
}
updateIssueStructure = async (group_id: string | null, sub_group_id: string | null, issue: IIssue) => {
const projectId: string | null = issue?.project;
const issueType = this.getIssueType;
if (!projectId || !issueType) return null;
let issues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null =
this.getIssues;
if (!issues) return null;
if (issueType === "grouped" && group_id) {
issues = issues as IIssueGroupedStructure;
const _currentIssueId = issues?.[group_id]?.find((_i) => _i?.id === issue.id);
issues = {
...issues,
[group_id]: _currentIssueId
? issues[group_id]?.map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i))
: [...(issues?.[group_id] ?? []), issue],
};
}
if (issueType === "groupWithSubGroups" && group_id && sub_group_id) {
issues = issues as IIssueGroupWithSubGroupsStructure;
const _currentIssueId = issues?.[sub_group_id]?.[group_id]?.find((_i) => _i?.id === issue.id);
issues = {
...issues,
[sub_group_id]: {
...issues[sub_group_id],
[group_id]: _currentIssueId
? issues?.[sub_group_id]?.[group_id]?.map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i))
: [...(issues?.[sub_group_id]?.[group_id] ?? []), issue],
},
};
}
if (issueType === "ungrouped") {
issues = issues as IIssueUnGroupedStructure;
const _currentIssueId = issues?.find((_i) => _i?.id === issue.id);
issues = _currentIssueId
? issues?.map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i))
: [...(issues ?? []), issue];
}
>>>>>>> a86dafc11c3e52699f4050e9d9c97393e29f0434
runInAction(() => { runInAction(() => {
this.allIssues = _issues; this.allIssues = _issues;

View File

@ -87,10 +87,12 @@ export class ArchivedIssueStore implements IArchivedIssueStore {
autorun(() => { autorun(() => {
const workspaceSlug = this.rootStore.workspace.workspaceSlug; const workspaceSlug = this.rootStore.workspace.workspaceSlug;
const projectId = this.rootStore.project.projectId; const projectId = this.rootStore.project.projectId;
const hasPermissionToCurrentProject = this.rootStore.user.hasPermissionToCurrentProject;
if ( if (
workspaceSlug && workspaceSlug &&
projectId && projectId &&
hasPermissionToCurrentProject &&
this.rootStore.archivedIssueFilters.userDisplayFilters && this.rootStore.archivedIssueFilters.userDisplayFilters &&
this.rootStore.archivedIssueFilters.userFilters this.rootStore.archivedIssueFilters.userFilters
) )

View File

@ -89,10 +89,12 @@ export class CycleIssueStore implements ICycleIssueStore {
const workspaceSlug = this.rootStore.workspace.workspaceSlug; const workspaceSlug = this.rootStore.workspace.workspaceSlug;
const projectId = this.rootStore.project.projectId; const projectId = this.rootStore.project.projectId;
const cycleId = this.rootStore.cycle.cycleId; const cycleId = this.rootStore.cycle.cycleId;
const hasPermissionToCurrentProject = this.rootStore.user.hasPermissionToCurrentProject;
if ( if (
workspaceSlug && workspaceSlug &&
projectId && projectId &&
hasPermissionToCurrentProject &&
cycleId && cycleId &&
this.rootStore.cycleIssueFilter.cycleFilters && this.rootStore.cycleIssueFilter.cycleFilters &&
this.rootStore.issueFilter.userDisplayFilters this.rootStore.issueFilter.userDisplayFilters

View File

@ -182,6 +182,12 @@ export class GlobalIssuesStore extends IssueBaseStore implements IGlobalIssuesSt
const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data); const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data);
runInAction(() => {
_issues = { ...this.issues };
_issues[workspaceViewId][issueId] = { ..._issues[workspaceViewId][issueId], ...response };
this.issues = _issues;
});
return response; return response;
} catch (error) { } catch (error) {
this.fetchIssues(workspaceSlug, workspaceViewId, "mutation"); this.fetchIssues(workspaceSlug, workspaceViewId, "mutation");

View File

@ -60,7 +60,8 @@ export class ProjectArchivedIssuesStore extends IssueBaseStore implements IProje
autorun(() => { autorun(() => {
const workspaceSlug = this.rootStore.workspace.workspaceSlug; const workspaceSlug = this.rootStore.workspace.workspaceSlug;
const projectId = this.rootStore.project.projectId; const projectId = this.rootStore.project.projectId;
if (!workspaceSlug || !projectId) return; const hasPermissionToCurrentProject = this.rootStore.user.hasPermissionToCurrentProject;
if (!workspaceSlug || !projectId || !hasPermissionToCurrentProject) return;
const userFilters = this.rootStore?.projectArchivedIssuesFilter?.issueFilters?.filters; const userFilters = this.rootStore?.projectArchivedIssuesFilter?.issueFilters?.filters;
if (userFilters) this.fetchIssues(workspaceSlug, projectId, "mutation"); if (userFilters) this.fetchIssues(workspaceSlug, projectId, "mutation");

View File

@ -119,7 +119,8 @@ export class CycleIssuesStore extends IssueBaseStore implements ICycleIssuesStor
const workspaceSlug = this.rootStore.workspace.workspaceSlug; const workspaceSlug = this.rootStore.workspace.workspaceSlug;
const projectId = this.rootStore.project.projectId; const projectId = this.rootStore.project.projectId;
const cycleId = this.rootStore.cycle.cycleId; const cycleId = this.rootStore.cycle.cycleId;
if (!workspaceSlug || !projectId || !cycleId) return; const hasPermissionToCurrentProject = this.rootStore.user.hasPermissionToCurrentProject;
if (!workspaceSlug || !projectId || !hasPermissionToCurrentProject || !cycleId) return;
const userFilters = this.rootStore?.cycleIssuesFilter?.issueFilters?.filters; const userFilters = this.rootStore?.cycleIssuesFilter?.issueFilters?.filters;
if (userFilters) this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); if (userFilters) this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);
@ -238,6 +239,12 @@ export class CycleIssuesStore extends IssueBaseStore implements ICycleIssuesStor
const response = await this.rootStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); const response = await this.rootStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data);
runInAction(() => {
_issues = { ...this.issues };
_issues[cycleId][issueId] = { ..._issues[cycleId][issueId], ...response };
this.issues = _issues;
});
return response; return response;
} catch (error) { } catch (error) {
this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId);

View File

@ -63,7 +63,8 @@ export class ProjectDraftIssuesStore extends IssueBaseStore implements IProjectD
autorun(() => { autorun(() => {
const workspaceSlug = this.rootStore.workspace.workspaceSlug; const workspaceSlug = this.rootStore.workspace.workspaceSlug;
const projectId = this.rootStore.project.projectId; const projectId = this.rootStore.project.projectId;
if (!workspaceSlug || !projectId) return; const hasPermissionToCurrentProject = this.rootStore.user.hasPermissionToCurrentProject;
if (!workspaceSlug || !projectId || !hasPermissionToCurrentProject) return;
const userFilters = this.rootStore?.projectDraftIssuesFilter?.issueFilters?.filters; const userFilters = this.rootStore?.projectDraftIssuesFilter?.issueFilters?.filters;
if (userFilters) this.fetchIssues(workspaceSlug, projectId, "mutation"); if (userFilters) this.fetchIssues(workspaceSlug, projectId, "mutation");
@ -157,6 +158,12 @@ export class ProjectDraftIssuesStore extends IssueBaseStore implements IProjectD
const response = await this.issueDraftService.updateDraftIssue(workspaceSlug, projectId, issueId, data); const response = await this.issueDraftService.updateDraftIssue(workspaceSlug, projectId, issueId, data);
runInAction(() => {
_issues = { ...this.issues };
_issues[projectId][issueId] = { ..._issues[projectId][issueId], ...response };
this.issues = _issues;
});
return response; return response;
} catch (error) { } catch (error) {
this.fetchIssues(workspaceSlug, projectId, "mutation"); this.fetchIssues(workspaceSlug, projectId, "mutation");

View File

@ -111,7 +111,8 @@ export class ModuleIssuesStore extends IssueBaseStore implements IModuleIssuesSt
const workspaceSlug = this.rootStore.workspace.workspaceSlug; const workspaceSlug = this.rootStore.workspace.workspaceSlug;
const projectId = this.rootStore.project.projectId; const projectId = this.rootStore.project.projectId;
const moduleId = this.rootStore.module.moduleId; const moduleId = this.rootStore.module.moduleId;
if (!workspaceSlug || !projectId || !moduleId) return; const hasPermissionToCurrentProject = this.rootStore.user.hasPermissionToCurrentProject;
if (!workspaceSlug || !projectId || !hasPermissionToCurrentProject || !moduleId) return;
const userFilters = this.rootStore?.moduleIssuesFilter?.issueFilters?.filters; const userFilters = this.rootStore?.moduleIssuesFilter?.issueFilters?.filters;
if (userFilters) this.fetchIssues(workspaceSlug, projectId, "mutation", moduleId); if (userFilters) this.fetchIssues(workspaceSlug, projectId, "mutation", moduleId);
@ -230,6 +231,12 @@ export class ModuleIssuesStore extends IssueBaseStore implements IModuleIssuesSt
const response = await this.rootStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); const response = await this.rootStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data);
runInAction(() => {
_issues = { ...this.issues };
_issues[moduleId][issueId] = { ..._issues[moduleId][issueId], ...response };
this.issues = _issues;
});
return response; return response;
} catch (error) { } catch (error) {
this.fetchIssues(workspaceSlug, projectId, "mutation", moduleId); this.fetchIssues(workspaceSlug, projectId, "mutation", moduleId);

View File

@ -65,7 +65,8 @@ export class ViewIssuesStore extends IssueBaseStore implements IViewIssuesStore
autorun(() => { autorun(() => {
const workspaceSlug = this.rootStore.workspace.workspaceSlug; const workspaceSlug = this.rootStore.workspace.workspaceSlug;
const projectId = this.rootStore.project.projectId; const projectId = this.rootStore.project.projectId;
if (!workspaceSlug || !projectId) return; const hasPermissionToCurrentProject = this.rootStore.user.hasPermissionToCurrentProject;
if (!workspaceSlug || !projectId || !hasPermissionToCurrentProject) return;
const userFilters = this.rootStore?.viewIssuesFilter?.issueFilters?.filters; const userFilters = this.rootStore?.viewIssuesFilter?.issueFilters?.filters;
if (userFilters) this.fetchIssues(workspaceSlug, projectId, "mutation"); if (userFilters) this.fetchIssues(workspaceSlug, projectId, "mutation");
@ -162,6 +163,12 @@ export class ViewIssuesStore extends IssueBaseStore implements IViewIssuesStore
const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data); const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data);
runInAction(() => {
_issues = { ...this.issues };
_issues[projectId][issueId] = { ..._issues[projectId][issueId], ...response };
this.issues = _issues;
});
return response; return response;
} catch (error) { } catch (error) {
this.fetchIssues(workspaceSlug, projectId, "mutation"); this.fetchIssues(workspaceSlug, projectId, "mutation");

View File

@ -65,7 +65,8 @@ export class ProjectIssuesStore extends IssueBaseStore implements IProjectIssues
autorun(() => { autorun(() => {
const workspaceSlug = this.rootStore.workspace.workspaceSlug; const workspaceSlug = this.rootStore.workspace.workspaceSlug;
const projectId = this.rootStore.project.projectId; const projectId = this.rootStore.project.projectId;
if (!workspaceSlug || !projectId) return; const hasPermissionToCurrentProject = this.rootStore.user.hasPermissionToCurrentProject;
if (!workspaceSlug || !projectId || !hasPermissionToCurrentProject) return;
const userFilters = this.rootStore?.projectIssuesFilter?.issueFilters?.filters; const userFilters = this.rootStore?.projectIssuesFilter?.issueFilters?.filters;
if (userFilters) this.fetchIssues(workspaceSlug, projectId, "mutation"); if (userFilters) this.fetchIssues(workspaceSlug, projectId, "mutation");
@ -162,6 +163,12 @@ export class ProjectIssuesStore extends IssueBaseStore implements IProjectIssues
const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data); const response = await this.issueService.patchIssue(workspaceSlug, projectId, issueId, data);
runInAction(() => {
_issues = { ...this.issues };
_issues[projectId][issueId] = { ..._issues[projectId][issueId], ...response };
this.issues = _issues;
});
return response; return response;
} catch (error) { } catch (error) {
this.fetchIssues(workspaceSlug, projectId, "mutation"); this.fetchIssues(workspaceSlug, projectId, "mutation");

View File

@ -91,10 +91,12 @@ export class ModuleIssueStore implements IModuleIssueStore {
const workspaceSlug = this.rootStore.workspace.workspaceSlug; const workspaceSlug = this.rootStore.workspace.workspaceSlug;
const projectId = this.rootStore.project.projectId; const projectId = this.rootStore.project.projectId;
const moduleId = this.rootStore.module.moduleId; const moduleId = this.rootStore.module.moduleId;
const hasPermissionToCurrentProject = this.rootStore.user.hasPermissionToCurrentProject;
if ( if (
workspaceSlug && workspaceSlug &&
projectId && projectId &&
hasPermissionToCurrentProject &&
moduleId && moduleId &&
this.rootStore.moduleFilter.moduleFilters && this.rootStore.moduleFilter.moduleFilters &&
this.rootStore.issueFilter.userDisplayFilters this.rootStore.issueFilter.userDisplayFilters

View File

@ -68,7 +68,6 @@ export class ProfileIssueFilterStore implements IProfileIssueFilterStore {
const workspaceSlug = this.rootStore.workspace.workspaceSlug; const workspaceSlug = this.rootStore.workspace.workspaceSlug;
const userId = this.rootStore.profileIssues?.userId; const userId = this.rootStore.profileIssues?.userId;
if (workspaceSlug && userId && this.rootStore.profileIssues.currentProfileTab && this.appliedFilters) { if (workspaceSlug && userId && this.rootStore.profileIssues.currentProfileTab && this.appliedFilters) {
console.log("autorun triggered");
this.rootStore.profileIssues.fetchIssues( this.rootStore.profileIssues.fetchIssues(
workspaceSlug, workspaceSlug,
userId, userId,

View File

@ -420,6 +420,7 @@ body {
.vertical-lr { .vertical-lr {
-webkit-writing-mode: vertical-lr; -webkit-writing-mode: vertical-lr;
-ms-writing-mode: vertical-lr; -ms-writing-mode: vertical-lr;
writing-mode: vertical-lr;
} }
div.web-view-spinner { div.web-view-spinner {