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

This commit is contained in:
pablohashescobar 2023-06-22 10:43:05 +05:30
commit 9fd245a53c
17 changed files with 317 additions and 139 deletions

View File

@ -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 (

View File

@ -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">

View File

@ -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),
})), })),
], ],
}, },

View 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>
);
};

View File

@ -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"

View File

@ -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)}

View File

@ -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 ? (
<> <>

View File

@ -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";

View File

@ -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>
); );
}; };

View File

@ -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

View File

@ -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"

View File

@ -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";

View File

@ -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 />

View File

@ -168,7 +168,7 @@ export const reducer: ReducerFunctionType = (state, action) => {
...state, ...state,
filters: { filters: {
...state.filters, ...state.filters,
...payload, ...payload?.filters,
}, },
}; };

View File

@ -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;
}; };

View File

@ -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 ? (

View File

@ -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;
} }