mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge branch 'develop' of github.com:makeplane/plane into feat/notifications
This commit is contained in:
commit
9fd245a53c
@ -1,102 +1,33 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
import { mutate } from "swr";
|
|
||||||
|
|
||||||
// headless ui
|
// headless ui
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
|
||||||
import inboxServices from "services/inbox.service";
|
|
||||||
// hooks
|
|
||||||
import useToast from "hooks/use-toast";
|
|
||||||
import useInboxView from "hooks/use-inbox-view";
|
|
||||||
import useUser from "hooks/use-user";
|
|
||||||
// icons
|
// icons
|
||||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
||||||
// ui
|
// ui
|
||||||
import { SecondaryButton, DangerButton } from "components/ui";
|
import { SecondaryButton, DangerButton } from "components/ui";
|
||||||
// types
|
// types
|
||||||
import type { IInboxIssue, ICurrentUserResponse, IInboxIssueDetail } from "types";
|
import type { IInboxIssue } from "types";
|
||||||
// fetch-keys
|
|
||||||
import { INBOX_ISSUES, INBOX_ISSUE_DETAILS } from "constants/fetch-keys";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
data: IInboxIssue | undefined;
|
data: IInboxIssue | undefined;
|
||||||
|
onSubmit: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DeclineIssueModal: React.FC<Props> = ({ isOpen, handleClose, data }) => {
|
export const DeclineIssueModal: React.FC<Props> = ({ isOpen, handleClose, data, onSubmit }) => {
|
||||||
const [isDeclining, setIsDeclining] = useState(false);
|
const [isDeclining, setIsDeclining] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const { workspaceSlug, projectId, inboxId } = router.query;
|
|
||||||
|
|
||||||
const { user } = useUser();
|
|
||||||
const { setToastAlert } = useToast();
|
|
||||||
const { params } = useInboxView();
|
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
setIsDeclining(false);
|
setIsDeclining(false);
|
||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDecline = () => {
|
const handleDecline = () => {
|
||||||
if (!workspaceSlug || !projectId || !inboxId || !data) return;
|
|
||||||
|
|
||||||
setIsDeclining(true);
|
setIsDeclining(true);
|
||||||
|
|
||||||
inboxServices
|
onSubmit().finally(() => setIsDeclining(false));
|
||||||
.markInboxStatus(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId.toString(),
|
|
||||||
inboxId.toString(),
|
|
||||||
data.bridge_id,
|
|
||||||
{
|
|
||||||
status: -1,
|
|
||||||
},
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
mutate<IInboxIssueDetail>(
|
|
||||||
INBOX_ISSUE_DETAILS(inboxId.toString(), data.bridge_id),
|
|
||||||
(prevData) => {
|
|
||||||
if (!prevData) return prevData;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...prevData,
|
|
||||||
issue_inbox: [{ ...prevData.issue_inbox[0], status: -1 }],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
mutate<IInboxIssue[]>(
|
|
||||||
INBOX_ISSUES(inboxId.toString(), params),
|
|
||||||
(prevData) =>
|
|
||||||
prevData?.map((i) =>
|
|
||||||
i.bridge_id === data.bridge_id
|
|
||||||
? { ...i, issue_inbox: [{ ...i.issue_inbox[0], status: -1 }] }
|
|
||||||
: i
|
|
||||||
),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
setToastAlert({
|
|
||||||
type: "success",
|
|
||||||
title: "Success!",
|
|
||||||
message: "Issue declined successfully.",
|
|
||||||
});
|
|
||||||
onClose();
|
|
||||||
})
|
|
||||||
.catch(() =>
|
|
||||||
setToastAlert({
|
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message: "Issue could not be declined. Please try again.",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.finally(() => setIsDeclining(false));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -130,7 +130,8 @@ export const DeleteIssueModal: React.FC<Props> = ({ isOpen, handleClose, data })
|
|||||||
<span className="break-all font-medium text-brand-base">
|
<span className="break-all font-medium text-brand-base">
|
||||||
{data?.project_detail?.identifier}-{data?.sequence_id}
|
{data?.project_detail?.identifier}-{data?.sequence_id}
|
||||||
</span>
|
</span>
|
||||||
{""}? This action cannot be undone.
|
{""}? The issue will only be deleted from the inbox and this action cannot be
|
||||||
|
undone.
|
||||||
</p>
|
</p>
|
||||||
</span>
|
</span>
|
||||||
<div className="flex justify-end gap-2">
|
<div className="flex justify-end gap-2">
|
||||||
|
@ -6,7 +6,7 @@ import { getPriorityIcon } from "components/icons";
|
|||||||
import { IInboxFilterOptions } from "types";
|
import { IInboxFilterOptions } from "types";
|
||||||
// constants
|
// constants
|
||||||
import { PRIORITIES } from "constants/project";
|
import { PRIORITIES } from "constants/project";
|
||||||
import { STATUS } from "constants/inbox";
|
import { INBOX_STATUS } from "constants/inbox";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
filters: Partial<IInboxFilterOptions>;
|
filters: Partial<IInboxFilterOptions>;
|
||||||
@ -45,16 +45,16 @@ export const FiltersDropdown: React.FC<Props> = ({ filters, onSelect, direction,
|
|||||||
{
|
{
|
||||||
id: "inbox_status",
|
id: "inbox_status",
|
||||||
label: "Status",
|
label: "Status",
|
||||||
value: Object.values(STATUS),
|
value: INBOX_STATUS.map((status) => status.value),
|
||||||
children: [
|
children: [
|
||||||
...Object.keys(STATUS).map((status) => ({
|
...INBOX_STATUS.map((status) => ({
|
||||||
id: status,
|
id: status.key,
|
||||||
label: status,
|
label: status.label,
|
||||||
value: {
|
value: {
|
||||||
key: "inbox_status",
|
key: "inbox_status",
|
||||||
value: STATUS[status],
|
value: status.value,
|
||||||
},
|
},
|
||||||
selected: filters?.inbox_status?.includes(STATUS[status]),
|
selected: filters?.inbox_status?.includes(status.value),
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
126
apps/app/components/inbox/filters-list.tsx
Normal file
126
apps/app/components/inbox/filters-list.tsx
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
// hooks
|
||||||
|
import useInboxView from "hooks/use-inbox-view";
|
||||||
|
// icons
|
||||||
|
import { XMarkIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { getPriorityIcon } from "components/icons";
|
||||||
|
// helpers
|
||||||
|
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
||||||
|
// constants
|
||||||
|
import { INBOX_STATUS } from "constants/inbox";
|
||||||
|
|
||||||
|
export const InboxFiltersList = () => {
|
||||||
|
const { filters, setFilters, clearAllFilters, filtersLength } = useInboxView();
|
||||||
|
|
||||||
|
if (filtersLength <= 0) return <></>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2 flex-wrap text-[0.65rem] p-3">
|
||||||
|
{Object.keys(filters).map((key) => {
|
||||||
|
const filterKey = key as keyof typeof filters;
|
||||||
|
|
||||||
|
if (filters[filterKey] !== null)
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={key}
|
||||||
|
className="flex items-center gap-x-2 rounded-full border border-brand-base bg-brand-surface-2 px-2 py-1"
|
||||||
|
>
|
||||||
|
<span className="capitalize text-brand-secondary">
|
||||||
|
{replaceUnderscoreIfSnakeCase(key)}:
|
||||||
|
</span>
|
||||||
|
{filters[filterKey] === null || (filters[filterKey]?.length ?? 0) <= 0 ? (
|
||||||
|
<span className="inline-flex items-center px-2 py-0.5 font-medium">None</span>
|
||||||
|
) : (
|
||||||
|
<div className="space-x-2">
|
||||||
|
{filterKey === "priority" ? (
|
||||||
|
<div className="flex flex-wrap items-center gap-1">
|
||||||
|
{filters.priority?.map((priority) => (
|
||||||
|
<div
|
||||||
|
key={priority}
|
||||||
|
className={`inline-flex items-center gap-x-1 rounded-full px-2 py-0.5 capitalize ${
|
||||||
|
priority === "urgent"
|
||||||
|
? "bg-red-500/20 text-red-500"
|
||||||
|
: priority === "high"
|
||||||
|
? "bg-orange-500/20 text-orange-500"
|
||||||
|
: priority === "medium"
|
||||||
|
? "bg-yellow-500/20 text-yellow-500"
|
||||||
|
: priority === "low"
|
||||||
|
? "bg-green-500/20 text-green-500"
|
||||||
|
: "bg-brand-surface-1 text-brand-secondary"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span>{getPriorityIcon(priority)}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() =>
|
||||||
|
setFilters({
|
||||||
|
priority: filters.priority?.filter((p) => p !== priority),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<XMarkIcon className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setFilters({
|
||||||
|
priority: null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<XMarkIcon className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : filterKey === "inbox_status" ? (
|
||||||
|
<div className="flex flex-wrap items-center gap-1">
|
||||||
|
{filters.inbox_status?.map((status) => (
|
||||||
|
<div
|
||||||
|
key={status}
|
||||||
|
className="inline-flex items-center gap-x-1 rounded-full px-2 py-0.5 capitalize bg-brand-surface-1 text-brand-secondary"
|
||||||
|
>
|
||||||
|
<span>{INBOX_STATUS.find((s) => s.value === status)?.label}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() =>
|
||||||
|
setFilters({
|
||||||
|
inbox_status: filters.inbox_status?.filter((p) => p !== status),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<XMarkIcon className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setFilters({
|
||||||
|
priority: null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<XMarkIcon className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
(filters[filterKey] as any)?.join(", ")
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={clearAllFilters}
|
||||||
|
className="flex items-center gap-x-1 rounded-full border border-brand-base bg-brand-surface-2 px-3 py-1.5 text-brand-secondary hover:text-brand-base"
|
||||||
|
>
|
||||||
|
<span>Clear all</span>
|
||||||
|
<XMarkIcon className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -76,6 +76,11 @@ export const InboxActionHeader: React.FC<Props> = (props) => {
|
|||||||
const issueStatus = issue?.issue_inbox[0].status;
|
const issueStatus = issue?.issue_inbox[0].status;
|
||||||
const isAllowed = memberRole.isMember || memberRole.isOwner;
|
const isAllowed = memberRole.isMember || memberRole.isOwner;
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
const tomorrow = new Date(today);
|
||||||
|
|
||||||
|
tomorrow.setDate(today.getDate() + 1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-4 border-b border-brand-base divide-x divide-brand-base">
|
<div className="grid grid-cols-4 border-b border-brand-base divide-x divide-brand-base">
|
||||||
<div className="col-span-1 flex justify-between p-4">
|
<div className="col-span-1 flex justify-between p-4">
|
||||||
@ -142,12 +147,20 @@ export const InboxActionHeader: React.FC<Props> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3 flex-wrap">
|
<div className="flex items-center gap-3 flex-wrap">
|
||||||
{isAllowed && (
|
{isAllowed && (
|
||||||
<div className={`flex gap-3 flex-wrap ${issueStatus !== -2 ? "opacity-70" : ""}`}>
|
<div
|
||||||
|
className={`flex-shrink-0 ${
|
||||||
|
issueStatus === 0 || issueStatus === -2 ? "" : "opacity-70"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<Popover className="relative">
|
<Popover className="relative">
|
||||||
<Popover.Button as="button" type="button" disabled={issueStatus !== -2}>
|
<Popover.Button
|
||||||
|
as="button"
|
||||||
|
type="button"
|
||||||
|
disabled={!(issueStatus === 0 || issueStatus === -2)}
|
||||||
|
>
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
className={`flex gap-x-1 items-center ${
|
className={`flex gap-x-1 items-center ${
|
||||||
issueStatus !== -2 ? "cursor-not-allowed" : ""
|
issueStatus === 0 || issueStatus === -2 ? "" : "cursor-not-allowed"
|
||||||
}`}
|
}`}
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
@ -165,6 +178,7 @@ export const InboxActionHeader: React.FC<Props> = (props) => {
|
|||||||
setDate(val);
|
setDate(val);
|
||||||
}}
|
}}
|
||||||
dateFormat="dd-MM-yyyy"
|
dateFormat="dd-MM-yyyy"
|
||||||
|
minDate={tomorrow}
|
||||||
inline
|
inline
|
||||||
/>
|
/>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
@ -180,6 +194,10 @@ export const InboxActionHeader: React.FC<Props> = (props) => {
|
|||||||
)}
|
)}
|
||||||
</Popover.Panel>
|
</Popover.Panel>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isAllowed && (
|
||||||
|
<div className={`flex gap-3 flex-wrap ${issueStatus !== -2 ? "opacity-70" : ""}`}>
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
size="sm"
|
size="sm"
|
||||||
className="flex gap-2 items-center"
|
className="flex gap-2 items-center"
|
||||||
|
@ -101,7 +101,13 @@ export const InboxIssueCard: React.FC<Props> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{issue.issue_inbox[0].snoozed_till && (
|
{issue.issue_inbox[0].snoozed_till && (
|
||||||
<div className="text-xs flex items-center gap-1 text-brand-accent">
|
<div
|
||||||
|
className={`text-xs flex items-center gap-1 ${
|
||||||
|
new Date(issue.issue_inbox[0].snoozed_till ?? "") < new Date()
|
||||||
|
? "text-red-500"
|
||||||
|
: "text-blue-500"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<ClockIcon className="h-3.5 w-3.5" />
|
<ClockIcon className="h-3.5 w-3.5" />
|
||||||
<span>
|
<span>
|
||||||
Snoozed till {renderShortNumericDateFormat(issue.issue_inbox[0].snoozed_till)}
|
Snoozed till {renderShortNumericDateFormat(issue.issue_inbox[0].snoozed_till)}
|
||||||
|
@ -36,7 +36,7 @@ import { renderShortNumericDateFormat } from "helpers/date-time.helper";
|
|||||||
// types
|
// types
|
||||||
import type { IInboxIssue, IIssue } from "types";
|
import type { IInboxIssue, IIssue } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { INBOX_ISSUES, INBOX_ISSUE_DETAILS } from "constants/fetch-keys";
|
import { INBOX_ISSUES, INBOX_ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
|
||||||
|
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
name: "",
|
name: "",
|
||||||
@ -129,6 +129,7 @@ export const InboxMainContent: React.FC = () => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
mutateIssueDetails();
|
mutateIssueDetails();
|
||||||
mutate(INBOX_ISSUES(inboxId.toString(), params));
|
mutate(INBOX_ISSUES(inboxId.toString(), params));
|
||||||
|
mutate(PROJECT_ISSUES_ACTIVITY(issueDetails.id));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
@ -157,7 +158,9 @@ export const InboxMainContent: React.FC = () => {
|
|||||||
: issueStatus === -1
|
: issueStatus === -1
|
||||||
? "text-red-500 border-red-500 bg-red-500/10"
|
? "text-red-500 border-red-500 bg-red-500/10"
|
||||||
: issueStatus === 0
|
: issueStatus === 0
|
||||||
? "text-blue-500 border-blue-500 bg-blue-500/10"
|
? new Date(issueDetails.issue_inbox[0].snoozed_till ?? "") < new Date()
|
||||||
|
? "text-red-500 border-red-500 bg-red-500/10"
|
||||||
|
: "text-blue-500 border-blue-500 bg-blue-500/10"
|
||||||
: issueStatus === 1
|
: issueStatus === 1
|
||||||
? "text-green-500 border-green-500 bg-green-500/10"
|
? "text-green-500 border-green-500 bg-green-500/10"
|
||||||
: issueStatus === 2
|
: issueStatus === 2
|
||||||
@ -178,10 +181,19 @@ export const InboxMainContent: React.FC = () => {
|
|||||||
) : issueStatus === 0 ? (
|
) : issueStatus === 0 ? (
|
||||||
<>
|
<>
|
||||||
<ClockIcon className="h-5 w-5" />
|
<ClockIcon className="h-5 w-5" />
|
||||||
<p>
|
{new Date(issueDetails.issue_inbox[0].snoozed_till ?? "") < new Date() ? (
|
||||||
This issue has been snoozed till{" "}
|
<p>
|
||||||
{renderShortNumericDateFormat(issueDetails.issue_inbox[0].snoozed_till ?? "")}.
|
This issue was snoozed till{" "}
|
||||||
</p>
|
{renderShortNumericDateFormat(issueDetails.issue_inbox[0].snoozed_till ?? "")}
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p>
|
||||||
|
This issue has been snoozed till{" "}
|
||||||
|
{renderShortNumericDateFormat(issueDetails.issue_inbox[0].snoozed_till ?? "")}
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : issueStatus === 1 ? (
|
) : issueStatus === 1 ? (
|
||||||
<>
|
<>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
export * from "./decline-issue-modal";
|
export * from "./decline-issue-modal";
|
||||||
export * from "./delete-issue-modal";
|
export * from "./delete-issue-modal";
|
||||||
export * from "./filters-dropdown";
|
export * from "./filters-dropdown";
|
||||||
|
export * from "./filters-list";
|
||||||
export * from "./inbox-action-headers";
|
export * from "./inbox-action-headers";
|
||||||
export * from "./inbox-issue-card";
|
export * from "./inbox-issue-card";
|
||||||
export * from "./inbox-main-content";
|
export * from "./inbox-main-content";
|
||||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
|||||||
// hooks
|
// hooks
|
||||||
import useInboxView from "hooks/use-inbox-view";
|
import useInboxView from "hooks/use-inbox-view";
|
||||||
// components
|
// components
|
||||||
import { InboxIssueCard } from "components/inbox";
|
import { InboxIssueCard, InboxFiltersList } from "components/inbox";
|
||||||
// ui
|
// ui
|
||||||
import { Loader } from "components/ui";
|
import { Loader } from "components/ui";
|
||||||
|
|
||||||
@ -14,10 +14,11 @@ export const IssuesListSidebar = () => {
|
|||||||
const { issues: inboxIssues } = useInboxView();
|
const { issues: inboxIssues } = useInboxView();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="h-full flex flex-col overflow-hidden">
|
||||||
|
<InboxFiltersList />
|
||||||
{inboxIssues ? (
|
{inboxIssues ? (
|
||||||
inboxIssues.length > 0 ? (
|
inboxIssues.length > 0 ? (
|
||||||
<div className="divide-y divide-brand-base overflow-auto h-full pb-10">
|
<div className="divide-y divide-brand-base overflow-auto h-full">
|
||||||
{inboxIssues.map((issue) => (
|
{inboxIssues.map((issue) => (
|
||||||
<InboxIssueCard
|
<InboxIssueCard
|
||||||
key={issue.id}
|
key={issue.id}
|
||||||
@ -39,6 +40,6 @@ export const IssuesListSidebar = () => {
|
|||||||
<Loader.Item height="50px" />
|
<Loader.Item height="50px" />
|
||||||
</Loader>
|
</Loader>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -76,7 +76,7 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={isOpen} as={React.Fragment} afterLeave={() => setQuery("")} appear>
|
<Transition.Root show={isOpen} as={React.Fragment} afterLeave={() => setQuery("")} appear>
|
||||||
<div className="flex flex-wrap items-start py-2">
|
<div className="flex flex-wrap items-start">
|
||||||
<div className="space-y-1 sm:basis-1/2">
|
<div className="space-y-1 sm:basis-1/2">
|
||||||
<Dialog as="div" className="relative z-20" onClose={handleClose}>
|
<Dialog as="div" className="relative z-20" onClose={handleClose}>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
|
@ -287,13 +287,15 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
{issueDetail?.project_detail?.identifier}-{issueDetail?.sequence_id}
|
{issueDetail?.project_detail?.identifier}-{issueDetail?.sequence_id}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<button
|
{(fieldsToShow.includes("all") || fieldsToShow.includes("link")) && (
|
||||||
type="button"
|
<button
|
||||||
className="rounded-md border border-brand-base p-2 shadow-sm duration-300 hover:bg-brand-surface-1 focus:border-brand-accent focus:outline-none focus:ring-1 focus:ring-brand-accent"
|
type="button"
|
||||||
onClick={handleCopyText}
|
className="rounded-md border border-brand-base p-2 shadow-sm duration-300 hover:bg-brand-surface-1 focus:border-brand-accent focus:outline-none focus:ring-1 focus:ring-brand-accent"
|
||||||
>
|
onClick={handleCopyText}
|
||||||
<LinkIcon className="h-3.5 w-3.5" />
|
>
|
||||||
</button>
|
<LinkIcon className="h-3.5 w-3.5" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
{!isNotAllowed && (fieldsToShow.includes("all") || fieldsToShow.includes("delete")) && (
|
{!isNotAllowed && (fieldsToShow.includes("all") || fieldsToShow.includes("delete")) && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -1,9 +1,29 @@
|
|||||||
export const STATUS: { [key: string]: number } = {
|
export const INBOX_STATUS = [
|
||||||
Pending: -2,
|
{
|
||||||
Declined: -1,
|
key: "pending",
|
||||||
Snoozed: 0,
|
label: "Pending",
|
||||||
Accepted: 1,
|
value: -2,
|
||||||
Duplicate: 2,
|
},
|
||||||
};
|
{
|
||||||
|
key: "declined",
|
||||||
|
label: "Declined",
|
||||||
|
value: -1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "snoozed",
|
||||||
|
label: "Snoozed",
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "accepted",
|
||||||
|
label: "Accepted",
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "duplicate",
|
||||||
|
label: "Duplicate",
|
||||||
|
value: 2,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export const INBOX_ISSUE_SOURCE = "in-app";
|
export const INBOX_ISSUE_SOURCE = "in-app";
|
||||||
|
@ -26,6 +26,7 @@ type ReducerActionType = {
|
|||||||
|
|
||||||
type ContextType = InboxViewProps & {
|
type ContextType = InboxViewProps & {
|
||||||
setFilters: (filters: Partial<IInboxFilterOptions>) => void;
|
setFilters: (filters: Partial<IInboxFilterOptions>) => void;
|
||||||
|
clearAllFilters: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateType = {
|
type StateType = {
|
||||||
@ -53,7 +54,7 @@ export const reducer: ReducerFunctionType = (state, action) => {
|
|||||||
...state,
|
...state,
|
||||||
filters: {
|
filters: {
|
||||||
...state.filters,
|
...state.filters,
|
||||||
...payload,
|
...payload?.filters,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -140,6 +141,38 @@ export const InboxViewContextProvider: React.FC<{ children: React.ReactNode }> =
|
|||||||
[workspaceSlug, projectId, inboxId, mutateInboxDetails, state]
|
[workspaceSlug, projectId, inboxId, mutateInboxDetails, state]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const clearAllFilters = useCallback(() => {
|
||||||
|
dispatch({
|
||||||
|
type: "SET_FILTERS",
|
||||||
|
payload: {
|
||||||
|
filters: { ...initialState.filters },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!workspaceSlug || !projectId || !inboxId) return;
|
||||||
|
|
||||||
|
const newViewProps = {
|
||||||
|
...state,
|
||||||
|
filters: { ...initialState.filters },
|
||||||
|
};
|
||||||
|
|
||||||
|
mutateInboxDetails((prevData) => {
|
||||||
|
if (!prevData) return prevData;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prevData,
|
||||||
|
view_props: newViewProps,
|
||||||
|
};
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
saveDataToServer(
|
||||||
|
workspaceSlug.toString(),
|
||||||
|
projectId.toString(),
|
||||||
|
inboxId.toString(),
|
||||||
|
newViewProps
|
||||||
|
);
|
||||||
|
}, [inboxId, mutateInboxDetails, projectId, state, workspaceSlug]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "REHYDRATE_THEME",
|
type: "REHYDRATE_THEME",
|
||||||
@ -154,6 +187,7 @@ export const InboxViewContextProvider: React.FC<{ children: React.ReactNode }> =
|
|||||||
value={{
|
value={{
|
||||||
filters: state.filters,
|
filters: state.filters,
|
||||||
setFilters,
|
setFilters,
|
||||||
|
clearAllFilters,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ToastAlert />
|
<ToastAlert />
|
||||||
|
@ -168,7 +168,7 @@ export const reducer: ReducerFunctionType = (state, action) => {
|
|||||||
...state,
|
...state,
|
||||||
filters: {
|
filters: {
|
||||||
...state.filters,
|
...state.filters,
|
||||||
...payload,
|
...payload?.filters,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import { IInboxQueryParams } from "types";
|
|||||||
import { INBOX_ISSUES } from "constants/fetch-keys";
|
import { INBOX_ISSUES } from "constants/fetch-keys";
|
||||||
|
|
||||||
const useInboxView = () => {
|
const useInboxView = () => {
|
||||||
const { filters, setFilters } = useContext(inboxViewContext);
|
const { filters, setFilters, clearAllFilters } = useContext(inboxViewContext);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, inboxId } = router.query;
|
const { workspaceSlug, projectId, inboxId } = router.query;
|
||||||
@ -50,10 +50,11 @@ const useInboxView = () => {
|
|||||||
return {
|
return {
|
||||||
filters,
|
filters,
|
||||||
setFilters,
|
setFilters,
|
||||||
|
clearAllFilters,
|
||||||
|
filtersLength,
|
||||||
params,
|
params,
|
||||||
issues: inboxIssues,
|
issues: inboxIssues,
|
||||||
mutate: mutateInboxIssues,
|
mutate: mutateInboxIssues,
|
||||||
filtersLength,
|
|
||||||
} as const;
|
} as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import projectService from "services/project.service";
|
|||||||
// hooks
|
// hooks
|
||||||
import useInboxView from "hooks/use-inbox-view";
|
import useInboxView from "hooks/use-inbox-view";
|
||||||
import useUserAuth from "hooks/use-user-auth";
|
import useUserAuth from "hooks/use-user-auth";
|
||||||
|
import useToast from "hooks/use-toast";
|
||||||
// layouts
|
// layouts
|
||||||
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
import { ProjectAuthorizationWrapper } from "layouts/auth-layout";
|
||||||
// contexts
|
// contexts
|
||||||
@ -47,6 +48,7 @@ const ProjectInbox: NextPage = () => {
|
|||||||
|
|
||||||
const { user } = useUserAuth();
|
const { user } = useUserAuth();
|
||||||
const { issues: inboxIssues, mutate: mutateInboxIssues } = useInboxView();
|
const { issues: inboxIssues, mutate: mutateInboxIssues } = useInboxView();
|
||||||
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { data: projectDetails } = useSWR(
|
const { data: projectDetails } = useSWR(
|
||||||
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
|
||||||
@ -103,6 +105,28 @@ const ProjectInbox: NextPage = () => {
|
|||||||
const markInboxStatus = async (data: TInboxStatus) => {
|
const markInboxStatus = async (data: TInboxStatus) => {
|
||||||
if (!workspaceSlug || !projectId || !inboxId || !inboxIssueId) return;
|
if (!workspaceSlug || !projectId || !inboxId || !inboxIssueId) return;
|
||||||
|
|
||||||
|
mutate<IInboxIssueDetail>(
|
||||||
|
INBOX_ISSUE_DETAILS(inboxId as string, inboxIssueId as string),
|
||||||
|
(prevData) => {
|
||||||
|
if (!prevData) return prevData;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prevData,
|
||||||
|
issue_inbox: [{ ...prevData.issue_inbox[0], ...data }],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
mutateInboxIssues(
|
||||||
|
(prevData) =>
|
||||||
|
(prevData ?? []).map((i) =>
|
||||||
|
i.bridge_id === inboxIssueId
|
||||||
|
? { ...i, issue_inbox: [{ ...i.issue_inbox[0], ...data }] }
|
||||||
|
: i
|
||||||
|
),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
await inboxServices
|
await inboxServices
|
||||||
.markInboxStatus(
|
.markInboxStatus(
|
||||||
workspaceSlug.toString(),
|
workspaceSlug.toString(),
|
||||||
@ -112,28 +136,16 @@ const ProjectInbox: NextPage = () => {
|
|||||||
data,
|
data,
|
||||||
user
|
user
|
||||||
)
|
)
|
||||||
.then(() => {
|
.catch(() =>
|
||||||
mutate<IInboxIssueDetail>(
|
setToastAlert({
|
||||||
INBOX_ISSUE_DETAILS(inboxId as string, inboxIssueId as string),
|
type: "error",
|
||||||
(prevData) => {
|
title: "Error!",
|
||||||
if (!prevData) return prevData;
|
message: "Something went wrong while updating inbox status. Please try again.",
|
||||||
|
})
|
||||||
return {
|
)
|
||||||
...prevData,
|
.finally(() => {
|
||||||
issue_inbox: [{ ...prevData.issue_inbox[0], ...data }],
|
mutate(INBOX_ISSUE_DETAILS(inboxId as string, inboxIssueId as string));
|
||||||
};
|
mutateInboxIssues();
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
mutateInboxIssues(
|
|
||||||
(prevData) =>
|
|
||||||
(prevData ?? []).map((i) =>
|
|
||||||
i.bridge_id === inboxIssueId
|
|
||||||
? { ...i, issue_inbox: [{ ...i.issue_inbox[0], ...data }] }
|
|
||||||
: i
|
|
||||||
),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -182,6 +194,11 @@ const ProjectInbox: NextPage = () => {
|
|||||||
isOpen={declineIssueModal}
|
isOpen={declineIssueModal}
|
||||||
handleClose={() => setDeclineIssueModal(false)}
|
handleClose={() => setDeclineIssueModal(false)}
|
||||||
data={inboxIssues?.find((i) => i.bridge_id === inboxIssueId)}
|
data={inboxIssues?.find((i) => i.bridge_id === inboxIssueId)}
|
||||||
|
onSubmit={async () => {
|
||||||
|
await markInboxStatus({
|
||||||
|
status: -1,
|
||||||
|
}).finally(() => setDeclineIssueModal(false));
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<DeleteIssueModal
|
<DeleteIssueModal
|
||||||
isOpen={deleteIssueModal}
|
isOpen={deleteIssueModal}
|
||||||
@ -210,7 +227,7 @@ const ProjectInbox: NextPage = () => {
|
|||||||
}}
|
}}
|
||||||
onDelete={() => setDeleteIssueModal(true)}
|
onDelete={() => setDeleteIssueModal(true)}
|
||||||
/>
|
/>
|
||||||
<div className="grid grid-cols-4 flex-1 overflow-auto divide-x divide-brand-base">
|
<div className="grid grid-cols-4 flex-1 divide-x divide-brand-base overflow-hidden">
|
||||||
<IssuesListSidebar />
|
<IssuesListSidebar />
|
||||||
<div className="col-span-3 h-full overflow-auto">
|
<div className="col-span-3 h-full overflow-auto">
|
||||||
{inboxIssueId ? (
|
{inboxIssueId ? (
|
||||||
|
@ -104,11 +104,19 @@
|
|||||||
color: rgba(var(--color-text-base)) !important;
|
color: rgba(var(--color-text-base)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-datepicker__day--selected {
|
.react-datepicker__day--selected,
|
||||||
|
.react-datepicker__day--selected:hover {
|
||||||
background-color: #216ba5 !important;
|
background-color: #216ba5 !important;
|
||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.react-datepicker__day--disabled,
|
||||||
|
.react-datepicker__day--disabled:hover {
|
||||||
|
background: transparent !important;
|
||||||
|
color: rgba(var(--color-text-secondary)) !important;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
.react-datepicker__day--today {
|
.react-datepicker__day--today {
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user