diff --git a/src/components/Collection/Group/EditGroupModal.tsx b/src/components/Collection/Group/EditGroupModal.tsx
index d0fd87333..fda84db7a 100644
--- a/src/components/Collection/Group/EditGroupModal.tsx
+++ b/src/components/Collection/Group/EditGroupModal.tsx
@@ -3,6 +3,7 @@ import { useDispatch, useSelector } from 'react-redux';
import cx from 'classnames';
import { map } from 'lodash';
+import FileActionsTab from '@/components/Collection/Group/EditGroupTabs/FileActionsTab';
import NameTab from '@/components/Collection/Group/EditGroupTabs/NameTab';
import SeriesTab from '@/components/Collection/Group/EditGroupTabs/SeriesTab';
import ModalPanel from '@/components/Panels/ModalPanel';
@@ -14,6 +15,7 @@ import type { RootState } from '@/core/store';
const tabs = {
name: 'Name',
series: 'Series',
+ file_actions: 'File Actions',
};
const renderTab = (activeTab: string, groupId: number) => {
@@ -24,6 +26,8 @@ const renderTab = (activeTab: string, groupId: number) => {
switch (activeTab) {
case 'series':
return ;
+ case 'file_actions':
+ return ;
case 'name':
default:
return ;
diff --git a/src/components/Collection/Group/EditGroupTabs/Action.tsx b/src/components/Collection/Group/EditGroupTabs/Action.tsx
new file mode 100644
index 000000000..caa31629b
--- /dev/null
+++ b/src/components/Collection/Group/EditGroupTabs/Action.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import cx from 'classnames';
+
+const Action = (
+ { description, name, onClick, scroll }: { name: string, description: string, scroll?: boolean, onClick: () => void },
+) => (
+
+);
+
+export default React.memo(Action);
diff --git a/src/components/Collection/Group/EditGroupTabs/FileActionsTab.tsx b/src/components/Collection/Group/EditGroupTabs/FileActionsTab.tsx
new file mode 100644
index 000000000..63b41836c
--- /dev/null
+++ b/src/components/Collection/Group/EditGroupTabs/FileActionsTab.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+
+import Action from '@/components/Collection/Group/EditGroupTabs/Action';
+import toast from '@/components/Toast';
+import { useRelocateGroupFilesMutation } from '@/core/react-query/group/mutations';
+import useIsFeatureSupported, { FeatureType } from '@/hooks/useIsFeatureSupported';
+
+type Props = {
+ groupId: number;
+};
+
+const FileActionsTab = ({ groupId }: Props) => {
+ const { mutate: relocateGroupFiles } = useRelocateGroupFilesMutation(groupId);
+ const showUnsupportedToast = () => {
+ toast.error(`This feature require server version >= ${FeatureType.RelocateSeriesFiles}`);
+ };
+
+ return (
+
+ );
+};
+
+export default FileActionsTab;
diff --git a/src/components/Collection/Series/EditSeriesTabs/FileActionsTab.tsx b/src/components/Collection/Series/EditSeriesTabs/FileActionsTab.tsx
index 97367e003..56bb354ed 100644
--- a/src/components/Collection/Series/EditSeriesTabs/FileActionsTab.tsx
+++ b/src/components/Collection/Series/EditSeriesTabs/FileActionsTab.tsx
@@ -1,7 +1,13 @@
import React from 'react';
import Action from '@/components/Collection/Series/EditSeriesTabs/Action';
-import { useRehashSeriesFilesMutation, useRescanSeriesFilesMutation } from '@/core/react-query/series/mutations';
+import toast from '@/components/Toast';
+import {
+ useRehashSeriesFilesMutation,
+ useRelocateSeriesFilesMutation,
+ useRescanSeriesFilesMutation,
+} from '@/core/react-query/series/mutations';
+import useIsFeatureSupported, { FeatureType } from '@/hooks/useIsFeatureSupported';
type Props = {
seriesId: number;
@@ -10,6 +16,10 @@ type Props = {
const FileActionsTab = ({ seriesId }: Props) => {
const { mutate: rehashSeriesFiles } = useRehashSeriesFilesMutation(seriesId);
const { mutate: rescanSeriesFiles } = useRescanSeriesFilesMutation(seriesId);
+ const { mutate: relocateSeriesFiles } = useRelocateSeriesFilesMutation(seriesId);
+ const showUnsupportedToast = () => {
+ toast.error(`This feature require server version >= ${FeatureType.RelocateSeriesFiles}`);
+ };
return (
@@ -23,6 +33,11 @@ const FileActionsTab = ({ seriesId }: Props) => {
description="Rehashes every file associated with the series."
onClick={rehashSeriesFiles}
/>
+
);
};
diff --git a/src/core/react-query/group/mutations.ts b/src/core/react-query/group/mutations.ts
index af12b42d1..1a861c245 100644
--- a/src/core/react-query/group/mutations.ts
+++ b/src/core/react-query/group/mutations.ts
@@ -5,6 +5,7 @@ import { axios } from '@/core/axios';
import { invalidateQueries } from '@/core/react-query/queryClient';
import type { MoveSeriesGroupRequestType, PatchGroupRequestType } from '@/core/react-query/group/types';
+import type { SeriesType } from '@/core/types/api/series';
// TODO: FIX INVALIDATIONS
@@ -57,3 +58,19 @@ export const useMoveGroupMutation = () =>
toast.success('Moved series into new group!');
},
});
+
+export const useRelocateGroupFilesMutation = (groupId: number) =>
+ useMutation({
+ mutationFn: async () => {
+ const targetSeries = await axios.get(`Group/${groupId}/Series`, {
+ params: { recursive: true },
+ });
+
+ return Promise.all(
+ targetSeries.map(
+ series => axios.post(`Series/${series.IDs.ID}/File/Relocate`),
+ ),
+ );
+ },
+ onSuccess: () => toast.success('Group files renamed/moved!'),
+ });
diff --git a/src/core/react-query/series/mutations.ts b/src/core/react-query/series/mutations.ts
index a5e051197..5ac822a2d 100644
--- a/src/core/react-query/series/mutations.ts
+++ b/src/core/react-query/series/mutations.ts
@@ -148,3 +148,9 @@ export const useSyncSeriesTraktMutation = (seriesId: number) =>
mutationFn: () => axios.post(`Series/${seriesId}/Trakt/Sync`),
onSuccess: () => toast.success('Trakt sync queued!'),
});
+
+export const useRelocateSeriesFilesMutation = (seriesId: number) =>
+ useMutation({
+ mutationFn: () => axios.post(`Series/${seriesId}/File/Relocate`),
+ onSuccess: () => toast.success('Series files renamed/moved!'),
+ });
diff --git a/src/hooks/useIsFeatureSupported.ts b/src/hooks/useIsFeatureSupported.ts
index f1be7ca01..188877949 100644
--- a/src/hooks/useIsFeatureSupported.ts
+++ b/src/hooks/useIsFeatureSupported.ts
@@ -8,6 +8,7 @@ import type { VersionType } from '@/core/types/api/init';
export enum FeatureType {
Placeholder = '5.0.0.0', // This is as a placeholder so the string conversion for `parseServerVersion` works and also serves as an example
+ RelocateSeriesFiles = '5.1.0.35',
}
const useIsFeatureSupported = (feature: FeatureType) => {