2023-05-17 05:19:14 +09:00
|
|
|
import {
|
|
|
|
|
ActionIcon,
|
|
|
|
|
Badge,
|
|
|
|
|
Card,
|
|
|
|
|
Center,
|
|
|
|
|
Flex,
|
|
|
|
|
Group,
|
|
|
|
|
Image,
|
|
|
|
|
Stack,
|
|
|
|
|
Text,
|
|
|
|
|
Tooltip,
|
|
|
|
|
} from '@mantine/core';
|
|
|
|
|
import { notifications } from '@mantine/notifications';
|
2023-06-10 16:27:44 +02:00
|
|
|
import { IconCheck, IconGitPullRequest, IconThumbDown, IconThumbUp } from '@tabler/icons-react';
|
|
|
|
|
import { useTranslation } from 'next-i18next';
|
|
|
|
|
import { api } from '~/utils/api';
|
2023-04-04 22:32:08 +02:00
|
|
|
import { defineWidget } from '../helper';
|
|
|
|
|
import { WidgetLoading } from '../loading';
|
|
|
|
|
import { IWidget } from '../widgets';
|
|
|
|
|
import { useMediaRequestQuery } from './media-request-query';
|
2023-05-17 05:19:14 +09:00
|
|
|
import { MediaRequest, MediaRequestStatus } from './media-request-types';
|
2023-06-10 16:27:44 +02:00
|
|
|
import { useConfigContext } from '~/config/provider';
|
2023-04-04 22:32:08 +02:00
|
|
|
|
|
|
|
|
const definition = defineWidget({
|
|
|
|
|
id: 'media-requests-list',
|
|
|
|
|
icon: IconGitPullRequest,
|
2023-05-16 22:30:04 +02:00
|
|
|
options: {
|
|
|
|
|
replaceLinksWithExternalHost: {
|
|
|
|
|
type: 'switch',
|
|
|
|
|
defaultValue: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-04-04 22:32:08 +02:00
|
|
|
component: MediaRequestListTile,
|
|
|
|
|
gridstack: {
|
|
|
|
|
minWidth: 3,
|
|
|
|
|
minHeight: 2,
|
|
|
|
|
maxWidth: 12,
|
|
|
|
|
maxHeight: 12,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type MediaRequestListWidget = IWidget<(typeof definition)['id'], typeof definition>;
|
|
|
|
|
|
|
|
|
|
interface MediaRequestListWidgetProps {
|
|
|
|
|
widget: MediaRequestListWidget;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-10 16:27:44 +02:00
|
|
|
type MediaRequestDecisionVariables = {
|
|
|
|
|
request: MediaRequest;
|
|
|
|
|
isApproved: boolean;
|
|
|
|
|
};
|
|
|
|
|
const useMediaRequestDecisionMutation = () => {
|
|
|
|
|
const { name: configName } = useConfigContext();
|
|
|
|
|
const utils = api.useContext();
|
|
|
|
|
const { mutateAsync } = api.overseerr.decide.useMutation({
|
|
|
|
|
onSuccess() {
|
|
|
|
|
utils.mediaRequest.all.invalidate();
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return async (variables: MediaRequestDecisionVariables) => {
|
|
|
|
|
const action = variables.isApproved ? 'Approving' : 'Declining';
|
|
|
|
|
notifications.show({
|
|
|
|
|
id: `decide-${variables.request.id}`,
|
|
|
|
|
color: 'yellow',
|
|
|
|
|
title: `${action} request...`,
|
|
|
|
|
message: undefined,
|
|
|
|
|
loading: true,
|
|
|
|
|
});
|
|
|
|
|
await mutateAsync(
|
|
|
|
|
{
|
|
|
|
|
configName: configName!,
|
|
|
|
|
id: variables.request.id,
|
|
|
|
|
isApproved: variables.isApproved,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
onSuccess(_data, variables) {
|
|
|
|
|
const title = variables.isApproved ? 'Request was approved!' : 'Request was declined!';
|
|
|
|
|
notifications.update({
|
|
|
|
|
id: `decide-${variables.id}`,
|
|
|
|
|
color: 'teal',
|
|
|
|
|
title,
|
|
|
|
|
message: undefined,
|
|
|
|
|
icon: <IconCheck size="1rem" />,
|
|
|
|
|
autoClose: 2000,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2023-04-04 22:32:08 +02:00
|
|
|
function MediaRequestListTile({ widget }: MediaRequestListWidgetProps) {
|
2023-04-06 23:11:29 +02:00
|
|
|
const { t } = useTranslation('modules/media-requests-list');
|
2023-06-10 16:27:44 +02:00
|
|
|
const { data, isLoading } = useMediaRequestQuery();
|
2023-05-17 05:19:14 +09:00
|
|
|
// Use mutation to approve or deny a pending request
|
2023-06-10 16:27:44 +02:00
|
|
|
const decideAsync = useMediaRequestDecisionMutation();
|
2023-04-04 22:32:08 +02:00
|
|
|
|
2023-05-17 05:19:14 +09:00
|
|
|
if (!data || isLoading) {
|
2023-04-04 22:32:08 +02:00
|
|
|
return <WidgetLoading />;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (data.length === 0) {
|
|
|
|
|
return (
|
|
|
|
|
<Center h="100%">
|
2023-04-06 23:11:29 +02:00
|
|
|
<Text>{t('noRequests')}</Text>
|
2023-04-04 22:32:08 +02:00
|
|
|
</Center>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const countPendingApproval = data.filter(
|
|
|
|
|
(x) => x.status === MediaRequestStatus.PendingApproval
|
|
|
|
|
).length;
|
|
|
|
|
|
2023-05-17 05:19:14 +09:00
|
|
|
// Return a sorted data by status to show pending first, then the default order
|
|
|
|
|
const sortedData = data.sort((a: MediaRequest, b: MediaRequest) => {
|
|
|
|
|
if (a.status === MediaRequestStatus.PendingApproval) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if (b.status === MediaRequestStatus.PendingApproval) {
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
});
|
|
|
|
|
|
2023-04-04 22:32:08 +02:00
|
|
|
return (
|
|
|
|
|
<Stack>
|
|
|
|
|
{countPendingApproval > 0 ? (
|
2023-04-06 23:11:29 +02:00
|
|
|
<Text>{t('pending', { countPendingApproval })}</Text>
|
2023-04-04 22:32:08 +02:00
|
|
|
) : (
|
2023-04-06 23:11:29 +02:00
|
|
|
<Text>{t('nonePending')}</Text>
|
2023-04-04 22:32:08 +02:00
|
|
|
)}
|
2023-05-17 05:19:14 +09:00
|
|
|
{sortedData.map((item) => (
|
2023-04-04 22:32:08 +02:00
|
|
|
<Card pos="relative" withBorder>
|
|
|
|
|
<Flex justify="space-between" gap="md">
|
|
|
|
|
<Flex gap="md">
|
|
|
|
|
<Image
|
|
|
|
|
src={item.posterPath}
|
|
|
|
|
width={30}
|
|
|
|
|
height={50}
|
|
|
|
|
alt="poster"
|
|
|
|
|
radius="xs"
|
|
|
|
|
withPlaceholder
|
|
|
|
|
/>
|
|
|
|
|
<Stack spacing={0}>
|
|
|
|
|
<Group spacing="xs">
|
2023-05-16 20:37:29 +02:00
|
|
|
{item.airDate && <Text>{item.airDate.split('-')[0]}</Text>}
|
2023-04-04 22:32:08 +02:00
|
|
|
<MediaRequestStatusBadge status={item.status} />
|
|
|
|
|
</Group>
|
|
|
|
|
<Text
|
|
|
|
|
sx={{ cursor: 'pointer', '&:hover': { textDecoration: 'underline' } }}
|
|
|
|
|
lineClamp={1}
|
|
|
|
|
weight="bold"
|
|
|
|
|
component="a"
|
|
|
|
|
href={item.href}
|
|
|
|
|
>
|
|
|
|
|
{item.name}
|
|
|
|
|
</Text>
|
|
|
|
|
</Stack>
|
|
|
|
|
</Flex>
|
2023-05-17 05:19:14 +09:00
|
|
|
<Stack justify="center">
|
|
|
|
|
<Flex gap="xs">
|
|
|
|
|
<Image
|
|
|
|
|
src={item.userProfilePicture}
|
|
|
|
|
width={25}
|
|
|
|
|
height={25}
|
|
|
|
|
alt="requester avatar"
|
|
|
|
|
radius="xl"
|
|
|
|
|
withPlaceholder
|
|
|
|
|
/>
|
|
|
|
|
<Text
|
|
|
|
|
component="a"
|
|
|
|
|
href={item.userLink}
|
|
|
|
|
sx={{ cursor: 'pointer', '&:hover': { textDecoration: 'underline' } }}
|
|
|
|
|
>
|
|
|
|
|
{item.userName}
|
|
|
|
|
</Text>
|
|
|
|
|
</Flex>
|
|
|
|
|
|
|
|
|
|
{item.status === MediaRequestStatus.PendingApproval && (
|
|
|
|
|
<Group>
|
|
|
|
|
<Tooltip label={t('tooltips.approve')} withArrow withinPortal>
|
|
|
|
|
<ActionIcon
|
|
|
|
|
variant="light"
|
|
|
|
|
color="green"
|
|
|
|
|
onClick={async () => {
|
|
|
|
|
notifications.show({
|
|
|
|
|
id: `approve ${item.id}`,
|
|
|
|
|
color: 'yellow',
|
|
|
|
|
title: 'Approving request...',
|
|
|
|
|
message: undefined,
|
|
|
|
|
loading: true,
|
|
|
|
|
});
|
|
|
|
|
|
2023-06-10 16:27:44 +02:00
|
|
|
await decideAsync({
|
|
|
|
|
request: item,
|
|
|
|
|
isApproved: true,
|
|
|
|
|
});
|
2023-05-17 05:19:14 +09:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<IconThumbUp />
|
|
|
|
|
</ActionIcon>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
<Tooltip label={t('tooltips.decline')} withArrow withinPortal>
|
|
|
|
|
<ActionIcon
|
|
|
|
|
variant="light"
|
|
|
|
|
color="red"
|
|
|
|
|
onClick={async () => {
|
2023-06-10 16:27:44 +02:00
|
|
|
await decideAsync({
|
|
|
|
|
request: item,
|
|
|
|
|
isApproved: false,
|
|
|
|
|
});
|
2023-05-17 05:19:14 +09:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<IconThumbDown />
|
|
|
|
|
</ActionIcon>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</Group>
|
|
|
|
|
)}
|
|
|
|
|
</Stack>
|
2023-04-04 22:32:08 +02:00
|
|
|
</Flex>
|
|
|
|
|
|
|
|
|
|
<Image
|
|
|
|
|
src={item.backdropPath}
|
|
|
|
|
pos="absolute"
|
|
|
|
|
w="100%"
|
|
|
|
|
h="100%"
|
|
|
|
|
opacity={0.1}
|
|
|
|
|
top={0}
|
|
|
|
|
left={0}
|
|
|
|
|
style={{ pointerEvents: 'none' }}
|
|
|
|
|
/>
|
|
|
|
|
</Card>
|
|
|
|
|
))}
|
|
|
|
|
</Stack>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const MediaRequestStatusBadge = ({ status }: { status: MediaRequestStatus }) => {
|
2023-04-06 23:11:29 +02:00
|
|
|
const { t } = useTranslation('modules/media-requests-list');
|
2023-04-04 22:32:08 +02:00
|
|
|
switch (status) {
|
|
|
|
|
case MediaRequestStatus.Approved:
|
2023-04-06 23:11:29 +02:00
|
|
|
return <Badge color="green">{t('state.approved')}</Badge>;
|
2023-04-04 22:32:08 +02:00
|
|
|
case MediaRequestStatus.Declined:
|
2023-04-06 23:11:29 +02:00
|
|
|
return <Badge color="red">{t('state.declined')}</Badge>;
|
2023-04-04 22:32:08 +02:00
|
|
|
case MediaRequestStatus.PendingApproval:
|
2023-04-06 23:11:29 +02:00
|
|
|
return <Badge color="orange">{t('state.pendingApproval')}</Badge>;
|
2023-04-04 22:32:08 +02:00
|
|
|
default:
|
|
|
|
|
return <></>;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default definition;
|