diff --git a/src/components/IssueDetails/index.tsx b/src/components/IssueDetails/index.tsx index 712d3379..171f90a8 100644 --- a/src/components/IssueDetails/index.tsx +++ b/src/components/IssueDetails/index.tsx @@ -31,7 +31,7 @@ import { useRouter } from 'next/router'; import { useState } from 'react'; import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; -import useSWR from 'swr'; +import useSWR, { mutate } from 'swr'; import * as Yup from 'yup'; const messages = defineMessages({ @@ -144,6 +144,7 @@ const IssueDetails = () => { autoDismiss: true, }); revalidateIssue(); + mutate('/api/v1/issue/count'); } catch (e) { addToast(intl.formatMessage(messages.toaststatusupdatefailed), { appearance: 'error', @@ -155,6 +156,7 @@ const IssueDetails = () => { const deleteIssue = async () => { try { await axios.delete(`/api/v1/issue/${issueData.id}`); + mutate('/api/v1/issue/count'); addToast(intl.formatMessage(messages.toastissuedeleted), { appearance: 'success', diff --git a/src/components/Layout/MobileMenu/index.tsx b/src/components/Layout/MobileMenu/index.tsx index f6c574f4..e288b826 100644 --- a/src/components/Layout/MobileMenu/index.tsx +++ b/src/components/Layout/MobileMenu/index.tsx @@ -1,3 +1,4 @@ +import Badge from '@app/components/Common/Badge'; import { menuMessages } from '@app/components/Layout/Sidebar'; import useClickOutside from '@app/hooks/useClickOutside'; import { Permission, useUser } from '@app/hooks/useUser'; @@ -27,6 +28,11 @@ import { useRouter } from 'next/router'; import { cloneElement, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; +interface MobileMenuProps { + pendingRequestsCount: number; + openIssuesCount: number; +} + interface MenuLink { href: string; svgIcon: JSX.Element; @@ -39,7 +45,10 @@ interface MenuLink { dataTestId?: string; } -const MobileMenu = () => { +const MobileMenu = ({ + pendingRequestsCount, + openIssuesCount, +}: MobileMenuProps) => { const ref = useRef(null); const intl = useIntl(); const [isOpen, setIsOpen] = useState(false); @@ -144,7 +153,7 @@ const MobileMenu = () => { return ( { @@ -159,7 +168,25 @@ const MobileMenu = () => { {cloneElement(isActive ? link.svgIconSelected : link.svgIcon, { className: 'h-5 w-5', })} - {link.content} + {link.content} + {link.href === '/requests' && + pendingRequestsCount > 0 && + hasPermission(Permission.MANAGE_REQUESTS) && ( +
+ + {pendingRequestsCount} + +
+ )} + {link.href === '/issues' && + openIssuesCount > 0 && + hasPermission(Permission.MANAGE_ISSUES) && ( +
+ + {openIssuesCount} + +
+ )}
); @@ -175,7 +202,7 @@ const MobileMenu = () => { return ( @@ -185,6 +212,21 @@ const MobileMenu = () => { className: 'h-6 w-6', } )} + {link.href === '/requests' && + pendingRequestsCount > 0 && + hasPermission(Permission.MANAGE_REQUESTS) && ( +
+ + {pendingRequestsCount} + +
+ )}
); diff --git a/src/components/Layout/Sidebar/index.tsx b/src/components/Layout/Sidebar/index.tsx index 81ebb86c..9aa2a5ee 100644 --- a/src/components/Layout/Sidebar/index.tsx +++ b/src/components/Layout/Sidebar/index.tsx @@ -1,3 +1,4 @@ +import Badge from '@app/components/Common/Badge'; import VersionStatus from '@app/components/Layout/VersionStatus'; import useClickOutside from '@app/hooks/useClickOutside'; import { Permission, useUser } from '@app/hooks/useUser'; @@ -30,6 +31,8 @@ export const menuMessages = defineMessages({ interface SidebarProps { open?: boolean; setClosed: () => void; + pendingRequestsCount: number; + openIssuesCount: number; } interface SidebarLinkProps { @@ -98,7 +101,12 @@ const SidebarLinks: SidebarLinkProps[] = [ }, ]; -const Sidebar = ({ open, setClosed }: SidebarProps) => { +const Sidebar = ({ + open, + setClosed, + pendingRequestsCount, + openIssuesCount, +}: SidebarProps) => { const navRef = useRef(null); const router = useRouter(); const intl = useIntl(); @@ -254,6 +262,40 @@ const Sidebar = ({ open, setClosed }: SidebarProps) => { {intl.formatMessage( menuMessages[sidebarLink.messagesKey] )} + {sidebarLink.messagesKey === 'requests' && + pendingRequestsCount > 0 && + hasPermission(Permission.MANAGE_REQUESTS) && ( +
+ + {pendingRequestsCount} + +
+ )} + {sidebarLink.messagesKey === 'issues' && + openIssuesCount > 0 && + hasPermission(Permission.MANAGE_ISSUES) && ( +
+ + {openIssuesCount} + +
+ )} ); diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx index 878f27b1..fd2c8c77 100644 --- a/src/components/Layout/index.tsx +++ b/src/components/Layout/index.tsx @@ -10,6 +10,7 @@ import { useUser } from '@app/hooks/useUser'; import { ArrowLeftIcon, Bars3BottomLeftIcon } from '@heroicons/react/24/solid'; import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; +import useSWR from 'swr'; type LayoutProps = { children: React.ReactNode; @@ -22,6 +23,8 @@ const Layout = ({ children }: LayoutProps) => { const router = useRouter(); const { currentSettings } = useSettings(); const { setLocale } = useLocale(); + const { data: requestResponse } = useSWR('/api/v1/request/count'); + const { data: issueResponse } = useSWR('/api/v1/issue/count'); useEffect(() => { if (setLocale && user) { @@ -55,9 +58,17 @@ const Layout = ({ children }: LayoutProps) => {
- setSidebarOpen(false)} /> + setSidebarOpen(false)} + pendingRequestsCount={requestResponse?.pending} + openIssuesCount={issueResponse?.open} + />
- +
diff --git a/src/components/RequestBlock/index.tsx b/src/components/RequestBlock/index.tsx index ed4c3ec3..d365e082 100644 --- a/src/components/RequestBlock/index.tsx +++ b/src/components/RequestBlock/index.tsx @@ -20,6 +20,7 @@ import axios from 'axios'; import Link from 'next/link'; import { useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; +import { mutate } from 'swr'; const messages = defineMessages({ seasons: '{seasonCount, plural, one {Season} other {Seasons}}', @@ -56,6 +57,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => { if (onUpdate) { onUpdate(); + mutate('/api/v1/request/count'); } setIsUpdating(false); }; @@ -66,6 +68,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => { if (onUpdate) { onUpdate(); + mutate('/api/v1/request/count'); } setIsUpdating(false); diff --git a/src/components/RequestButton/index.tsx b/src/components/RequestButton/index.tsx index 56e91810..5726df24 100644 --- a/src/components/RequestButton/index.tsx +++ b/src/components/RequestButton/index.tsx @@ -15,6 +15,7 @@ import type { MediaRequest } from '@server/entity/MediaRequest'; import axios from 'axios'; import { useMemo, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; +import { mutate } from 'swr'; const messages = defineMessages({ viewrequest: 'View Request', @@ -97,6 +98,7 @@ const RequestButton = ({ if (response) { onUpdate(); + mutate('/api/v1/request/count'); } }; @@ -115,6 +117,7 @@ const RequestButton = ({ ); onUpdate(); + mutate('/api/v1/request/count'); }; const buttons: ButtonOption[] = []; diff --git a/src/components/RequestCard/index.tsx b/src/components/RequestCard/index.tsx index 44abd555..4288af67 100644 --- a/src/components/RequestCard/index.tsx +++ b/src/components/RequestCard/index.tsx @@ -75,6 +75,7 @@ const RequestCardError = ({ requestData }: RequestCardErrorProps) => { await axios.delete(`/api/v1/media/${requestData?.media.id}`); mutate('/api/v1/media?filter=allavailable&take=20&sort=mediaAdded'); mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); + mutate('/api/v1/request/count'); }; return ( @@ -252,12 +253,14 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => { if (response) { revalidate(); + mutate('/api/v1/request/count'); } }; const deleteRequest = async () => { await axios.delete(`/api/v1/request/${request.id}`); mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); + mutate('/api/v1/request/count'); }; const retryRequest = async () => { diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx index a42483ab..ee44d877 100644 --- a/src/components/RequestList/RequestItem/index.tsx +++ b/src/components/RequestList/RequestItem/index.tsx @@ -25,7 +25,7 @@ import { useState } from 'react'; import { useInView } from 'react-intersection-observer'; import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; -import useSWR from 'swr'; +import useSWR, { mutate } from 'swr'; const messages = defineMessages({ seasons: '{seasonCount, plural, one {Season} other {Seasons}}', @@ -62,6 +62,7 @@ const RequestItemError = ({ const deleteRequest = async () => { await axios.delete(`/api/v1/media/${requestData?.media.id}`); revalidateList(); + mutate('/api/v1/request/count'); }; const { plexUrl, plexUrl4k } = useDeepLinks({ @@ -311,6 +312,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => { if (response) { revalidate(); + mutate('/api/v1/request/count'); } }; @@ -318,6 +320,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => { await axios.delete(`/api/v1/request/${request.id}`); revalidateList(); + mutate('/api/v1/request/count'); }; const retryRequest = async () => { diff --git a/src/components/RequestModal/CollectionRequestModal.tsx b/src/components/RequestModal/CollectionRequestModal.tsx index 614a00da..26dc410d 100644 --- a/src/components/RequestModal/CollectionRequestModal.tsx +++ b/src/components/RequestModal/CollectionRequestModal.tsx @@ -16,7 +16,7 @@ import axios from 'axios'; import { useCallback, useEffect, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; -import useSWR from 'swr'; +import useSWR, { mutate } from 'swr'; const messages = defineMessages({ requestadmin: 'This request will be approved automatically.', @@ -211,6 +211,7 @@ const CollectionRequestModal = ({ ? MediaStatus.UNKNOWN : MediaStatus.PARTIALLY_AVAILABLE ); + mutate('/api/v1/request/count'); } addToast( @@ -230,7 +231,16 @@ const CollectionRequestModal = ({ } finally { setIsUpdating(false); } - }, [requestOverrides, data, onComplete, addToast, intl, selectedParts, is4k]); + }, [ + requestOverrides, + data?.parts, + data?.name, + onComplete, + addToast, + intl, + selectedParts, + is4k, + ]); const hasAutoApprove = hasPermission( [ diff --git a/src/components/RequestModal/MovieRequestModal.tsx b/src/components/RequestModal/MovieRequestModal.tsx index 76a12638..cd196576 100644 --- a/src/components/RequestModal/MovieRequestModal.tsx +++ b/src/components/RequestModal/MovieRequestModal.tsx @@ -95,6 +95,7 @@ const MovieRequestModal = ({ ...overrideParams, }); mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); + mutate('/api/v1/request/count'); if (response.data) { if (onComplete) { @@ -129,7 +130,16 @@ const MovieRequestModal = ({ } finally { setIsUpdating(false); } - }, [data, onComplete, addToast, requestOverrides, hasPermission, intl, is4k]); + }, [ + requestOverrides, + data?.id, + data?.title, + is4k, + onComplete, + addToast, + intl, + hasPermission, + ]); const cancelRequest = async () => { setIsUpdating(true); @@ -139,6 +149,7 @@ const MovieRequestModal = ({ `/api/v1/request/${editRequest?.id}` ); mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); + mutate('/api/v1/request/count'); if (response.status === 204) { if (onComplete) { @@ -176,6 +187,7 @@ const MovieRequestModal = ({ await axios.post(`/api/v1/request/${editRequest?.id}/approve`); } mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); + mutate('/api/v1/request/count'); addToast( diff --git a/src/components/RequestModal/TvRequestModal.tsx b/src/components/RequestModal/TvRequestModal.tsx index 25c8fd3c..166288c7 100644 --- a/src/components/RequestModal/TvRequestModal.tsx +++ b/src/components/RequestModal/TvRequestModal.tsx @@ -106,6 +106,7 @@ const TvRequestModal = ({ if (onUpdating) { onUpdating(true); + mutate('/api/v1/request/count'); } try { @@ -128,6 +129,7 @@ const TvRequestModal = ({ await axios.delete(`/api/v1/request/${editRequest.id}`); } mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0'); + mutate('/api/v1/request/count'); addToast( @@ -176,6 +178,7 @@ const TvRequestModal = ({ if (onUpdating) { onUpdating(true); + mutate('/api/v1/request/count'); } try {