Files
Homarr/src/modules/overseerr/RequestModal.tsx

268 lines
7.4 KiB
TypeScript
Raw Normal View History

2023-07-21 18:08:40 +09:00
import { Alert, Button, Checkbox, Group, Modal, Stack, Table, createStyles } from '@mantine/core';
2022-08-07 12:16:29 +02:00
import { showNotification, updateNotification } from '@mantine/notifications';
import { IconAlertCircle, IconCheck, IconDownload } from '@tabler/icons-react';
2022-08-08 13:45:36 +02:00
import Consola from 'consola';
2022-08-22 09:50:54 +02:00
import { useTranslation } from 'next-i18next';
import { useState } from 'react';
import { useConfigContext } from '~/config/provider';
import { api } from '~/utils/api';
2023-07-21 18:08:40 +09:00
2022-08-07 12:16:29 +02:00
import { useColorTheme } from '../../tools/color';
import { MovieResult } from './Movie.d';
import { Result } from './SearchResult.d';
2022-08-07 12:16:29 +02:00
import { TvShowResult, TvShowResultSeason } from './TvShow.d';
2022-08-07 12:16:29 +02:00
interface RequestModalProps {
base: Result;
2022-08-08 13:45:36 +02:00
opened: boolean;
setOpened: (opened: boolean) => void;
2022-08-07 12:16:29 +02:00
}
const useStyles = createStyles((theme) => ({
rowSelected: {
backgroundColor:
theme.colorScheme === 'dark'
? theme.fn.rgba(theme.colors[theme.primaryColor][7], 0.2)
: theme.colors[theme.primaryColor][0],
},
}));
2022-08-08 13:45:36 +02:00
export function RequestModal({ base, opened, setOpened }: RequestModalProps) {
const { name: configName } = useConfigContext();
const { data: result } = api.overseerr.byId.useQuery(
{
id: base.id,
type: base.mediaType,
configName: configName!,
},
{
enabled: opened,
}
);
2022-08-22 09:50:54 +02:00
2022-08-08 13:45:36 +02:00
if (!result || !opened) {
return null;
2022-08-07 12:16:29 +02:00
}
2022-08-07 12:16:29 +02:00
return base.mediaType === 'movie' ? (
2022-08-08 13:45:36 +02:00
<MovieRequestModal result={result as MovieResult} opened={opened} setOpened={setOpened} />
2022-08-07 12:16:29 +02:00
) : (
2022-08-08 13:45:36 +02:00
<TvRequestModal result={result as TvShowResult} opened={opened} setOpened={setOpened} />
2022-08-07 12:16:29 +02:00
);
}
2022-08-08 13:45:36 +02:00
export function MovieRequestModal({
result,
opened,
setOpened,
}: {
result: MovieResult;
opened: boolean;
setOpened: (opened: boolean) => void;
}) {
2022-08-07 12:16:29 +02:00
const { secondaryColor } = useColorTheme();
const requestMediaAsync = useMediaRequestMutation();
const { t } = useTranslation('modules/overseerr');
2022-08-22 09:50:54 +02:00
2022-08-08 13:45:36 +02:00
return (
<Modal
onClose={() => setOpened(false)}
radius="lg"
size="lg"
trapFocus
zIndex={150}
withinPortal
opened={opened}
title={
<Group>
<IconDownload />
2022-08-22 09:50:54 +02:00
{t('popup.item.buttons.askFor', { title: result.title })}
2022-08-08 13:45:36 +02:00
</Group>
}
>
2022-08-07 12:16:29 +02:00
<Stack>
<Alert
icon={<IconAlertCircle size={16} />}
2022-08-22 09:50:54 +02:00
title={t('popup.item.alerts.automaticApproval.title')}
2022-08-07 12:16:29 +02:00
color={secondaryColor}
radius="md"
variant="filled"
>
2022-08-22 09:50:54 +02:00
{t('popup.item.alerts.automaticApproval.text')}
2022-08-07 12:16:29 +02:00
</Alert>
2022-08-08 13:45:36 +02:00
<Group>
2022-08-08 15:43:04 +02:00
<Button variant="outline" color="gray" onClick={() => setOpened(false)}>
2022-08-22 09:50:54 +02:00
{t('popup.item.buttons.cancel')}
2022-08-08 15:43:04 +02:00
</Button>
2022-08-08 13:45:36 +02:00
<Button
2022-08-08 15:43:04 +02:00
variant="outline"
2022-08-08 13:45:36 +02:00
onClick={() => {
requestMediaAsync('movie', result.id, result.title);
2022-08-08 13:45:36 +02:00
}}
>
2022-08-22 09:50:54 +02:00
{t('popup.item.buttons.request')}
2022-08-08 13:45:36 +02:00
</Button>
</Group>
2022-08-07 12:16:29 +02:00
</Stack>
2022-08-08 13:45:36 +02:00
</Modal>
);
2022-08-07 12:16:29 +02:00
}
2022-08-08 13:45:36 +02:00
export function TvRequestModal({
result,
opened,
setOpened,
}: {
result: TvShowResult;
opened: boolean;
setOpened: (opened: boolean) => void;
}) {
2022-08-07 12:16:29 +02:00
const [selection, setSelection] = useState<TvShowResultSeason[]>(result.seasons);
const { classes, cx } = useStyles();
const { t } = useTranslation('modules/overseerr');
const requestMediaAsync = useMediaRequestMutation();
2022-08-07 12:16:29 +02:00
const toggleRow = (container: TvShowResultSeason) =>
setSelection((current: TvShowResultSeason[]) =>
current.includes(container) ? current.filter((c) => c !== container) : [...current, container]
);
const toggleAll = () =>
setSelection((current: any) =>
current.length === result.seasons.length ? [] : result.seasons.map((c) => c)
);
const rows = result.seasons.map((element) => {
const selected = selection.includes(element);
return (
<tr key={element.id} className={cx({ [classes.rowSelected]: selected })}>
<td>
<Checkbox
key={element.id}
checked={selection.includes(element)}
onChange={() => toggleRow(element)}
transitionDuration={0}
/>
</td>
<td>{element.name}</td>
<td>{element.episodeCount}</td>
</tr>
);
});
const { secondaryColor } = useColorTheme();
2022-08-08 13:45:36 +02:00
return (
2022-08-08 15:43:04 +02:00
<Modal
onClose={() => setOpened(false)}
radius="lg"
size="lg"
opened={opened}
title={
<Group>
<IconDownload />
2022-08-22 09:50:54 +02:00
{t('popup.item.buttons.askFor', {
2022-08-18 21:46:46 +02:00
title: result.name ?? result.originalName ?? 'a TV show',
})}
2022-08-08 15:43:04 +02:00
</Group>
}
>
2022-08-07 12:16:29 +02:00
<Stack>
<Alert
icon={<IconAlertCircle size={16} />}
2022-08-22 09:50:54 +02:00
title={t('popup.item.alerts.automaticApproval.title')}
2022-08-07 12:16:29 +02:00
color={secondaryColor}
radius="md"
variant="filled"
>
2022-08-22 09:50:54 +02:00
{t('popup.item.alerts.automaticApproval.text')}
2022-08-07 12:16:29 +02:00
</Alert>
<Table captionSide="bottom" highlightOnHover>
2022-08-22 09:50:54 +02:00
<caption>{t('popup.seasonSelector.caption')}</caption>
2022-08-07 12:16:29 +02:00
<thead>
<tr>
<th>
<Checkbox
onChange={toggleAll}
checked={selection.length === result.seasons.length}
indeterminate={selection.length > 0 && selection.length !== result.seasons.length}
transitionDuration={0}
/>
</th>
2022-08-22 09:50:54 +02:00
<th>{t('popup.seasonSelector.table.header.season')}</th>
<th>{t('popup.seasonSelector.table.header.numberOfEpisodes')}</th>
2022-08-07 12:16:29 +02:00
</tr>
</thead>
<tbody>{rows}</tbody>
</Table>
2022-11-29 20:31:18 +09:00
<Group position="center">
2022-08-08 15:43:04 +02:00
<Button variant="outline" color="gray" onClick={() => setOpened(false)}>
2022-08-22 09:50:54 +02:00
{t('popup.item.buttons.cancel')}
2022-08-08 15:43:04 +02:00
</Button>
2022-08-08 13:45:36 +02:00
<Button
2022-08-08 15:43:04 +02:00
variant="outline"
2022-08-08 13:45:36 +02:00
disabled={selection.length === 0}
onClick={() => {
requestMediaAsync(
'tv',
2022-08-08 13:45:36 +02:00
result.id,
result.name,
selection.map((s) => s.seasonNumber)
);
}}
>
2022-08-22 09:50:54 +02:00
{t('popup.item.buttons.request')}
2022-08-08 13:45:36 +02:00
</Button>
</Group>
2022-08-07 12:16:29 +02:00
</Stack>
2022-08-08 13:45:36 +02:00
</Modal>
);
2022-08-07 12:16:29 +02:00
}
const useMediaRequestMutation = () => {
const { name: configName } = useConfigContext();
const { mutateAsync } = api.overseerr.request.useMutation();
return async (type: 'tv' | 'movie', id: number, name: string, seasons?: number[]) => {
Consola.info(`Requesting ${type} ${id} ${name}`);
showNotification({
title: 'Request',
id: id.toString(),
message: `Requesting media ${name}`,
color: 'orange',
loading: true,
autoClose: false,
withCloseButton: false,
icon: <IconAlertCircle />,
2022-08-07 12:16:29 +02:00
});
await mutateAsync(
{
configName: configName!,
id,
type,
seasons: seasons ?? [],
},
{
onSuccess: () => {
updateNotification({
id: id.toString(),
title: '',
color: 'green',
message: ` ${name} requested`,
icon: <IconCheck />,
autoClose: 2000,
});
},
onError: (err) => {
updateNotification({
id: id.toString(),
color: 'red',
title: 'There was an error',
message: err.message,
autoClose: 2000,
});
},
}
);
};
};