From 524d1b6574d663e25de276ec35bedaf9b4c6eb90 Mon Sep 17 00:00:00 2001
From: viown <48097677+viown@users.noreply.github.com>
Date: Sat, 22 Feb 2025 16:45:48 +0300
Subject: [PATCH] Migrate tasks edit page to react
---
.../scheduledtasks/scheduledtask.html | 84 -------
.../scheduledtasks/scheduledtask.js | 236 ------------------
.../api/useStartTask.ts | 0
.../api/useStopTask.ts | 0
.../dashboard/features/tasks/api/useTask.ts | 35 +++
.../{scheduledtasks => tasks}/api/useTasks.ts | 0
.../features/tasks/api/useUpdateTask.ts | 22 ++
.../tasks/components/NewTriggerForm.tsx | 169 +++++++++++++
.../components/Task.tsx | 0
.../components/TaskLastRan.tsx | 0
.../components/TaskProgress.tsx | 0
.../tasks/components/TaskTriggerCell.tsx | 34 +++
.../components/Tasks.tsx | 0
.../types/taskProps.ts | 0
.../dashboard/features/tasks/utils/edit.ts | 64 +++++
.../{scheduledtasks => tasks}/utils/tasks.ts | 0
src/apps/dashboard/routes/_asyncRoutes.ts | 1 +
src/apps/dashboard/routes/_legacyRoutes.ts | 7 -
src/apps/dashboard/routes/tasks/edit.tsx | 173 +++++++++++++
src/apps/dashboard/routes/tasks/index.tsx | 6 +-
20 files changed, 501 insertions(+), 330 deletions(-)
delete mode 100644 src/apps/dashboard/controllers/scheduledtasks/scheduledtask.html
delete mode 100644 src/apps/dashboard/controllers/scheduledtasks/scheduledtask.js
rename src/apps/dashboard/features/{scheduledtasks => tasks}/api/useStartTask.ts (100%)
rename src/apps/dashboard/features/{scheduledtasks => tasks}/api/useStopTask.ts (100%)
create mode 100644 src/apps/dashboard/features/tasks/api/useTask.ts
rename src/apps/dashboard/features/{scheduledtasks => tasks}/api/useTasks.ts (100%)
create mode 100644 src/apps/dashboard/features/tasks/api/useUpdateTask.ts
create mode 100644 src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx
rename src/apps/dashboard/features/{scheduledtasks => tasks}/components/Task.tsx (100%)
rename src/apps/dashboard/features/{scheduledtasks => tasks}/components/TaskLastRan.tsx (100%)
rename src/apps/dashboard/features/{scheduledtasks => tasks}/components/TaskProgress.tsx (100%)
create mode 100644 src/apps/dashboard/features/tasks/components/TaskTriggerCell.tsx
rename src/apps/dashboard/features/{scheduledtasks => tasks}/components/Tasks.tsx (100%)
rename src/apps/dashboard/features/{scheduledtasks => tasks}/types/taskProps.ts (100%)
create mode 100644 src/apps/dashboard/features/tasks/utils/edit.ts
rename src/apps/dashboard/features/{scheduledtasks => tasks}/utils/tasks.ts (100%)
create mode 100644 src/apps/dashboard/routes/tasks/edit.tsx
diff --git a/src/apps/dashboard/controllers/scheduledtasks/scheduledtask.html b/src/apps/dashboard/controllers/scheduledtasks/scheduledtask.html
deleted file mode 100644
index a2352e6470..0000000000
--- a/src/apps/dashboard/controllers/scheduledtasks/scheduledtask.html
+++ /dev/null
@@ -1,84 +0,0 @@
-
';
-
- for (let i = 0, length = task.Triggers.length; i < length; i++) {
- const trigger = task.Triggers[i];
-
- html += '
';
- html += '
';
- if (trigger.MaxRuntimeTicks) {
- html += '
';
- } else {
- html += '
';
- }
- html += "
" + ScheduledTaskPage.getTriggerFriendlyName(trigger) + '
';
- if (trigger.MaxRuntimeTicks) {
- html += '
';
- const hours = trigger.MaxRuntimeTicks / 36e9;
- if (hours == 1) {
- html += globalize.translate('ValueTimeLimitSingleHour');
- } else {
- html += globalize.translate('ValueTimeLimitMultiHour', hours);
- }
- html += '
';
- }
-
- html += '
';
- html += '
';
- html += '
';
- }
-
- html += '
';
- context.querySelector('.taskTriggers').innerHTML = html;
- },
- // TODO: Replace this mess with date-fns and remove datetime completely
- getTriggerFriendlyName: function (trigger) {
- if (trigger.Type == 'DailyTrigger') {
- return globalize.translate('DailyAt', ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks));
- }
-
- if (trigger.Type == 'WeeklyTrigger') {
- // TODO: The day of week isn't localised as well
- return globalize.translate('WeeklyAt', trigger.DayOfWeek, ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks));
- }
-
- if (trigger.Type == 'SystemEventTrigger' && trigger.SystemEvent == 'WakeFromSleep') {
- return globalize.translate('OnWakeFromSleep');
- }
-
- if (trigger.Type == 'IntervalTrigger') {
- const hours = trigger.IntervalTicks / 36e9;
-
- if (hours == 0.25) {
- return globalize.translate('EveryXMinutes', '15');
- }
- if (hours == 0.5) {
- return globalize.translate('EveryXMinutes', '30');
- }
- if (hours == 0.75) {
- return globalize.translate('EveryXMinutes', '45');
- }
- if (hours == 1) {
- return globalize.translate('EveryHour');
- }
-
- return globalize.translate('EveryXHours', hours);
- }
-
- if (trigger.Type == 'StartupTrigger') {
- return globalize.translate('OnApplicationStartup');
- }
-
- return trigger.Type;
- },
- getDisplayTime: function (ticks) {
- const ms = ticks / 1e4;
- const now = new Date();
- now.setHours(0, 0, 0, 0);
- now.setTime(now.getTime() + ms);
- return datetime.getDisplayTime(now);
- },
- showAddTriggerPopup: function (view) {
- view.querySelector('#selectTriggerType').value = 'DailyTrigger';
- view.querySelector('#selectTriggerType').dispatchEvent(new CustomEvent('change', {}));
- view.querySelector('#popupAddTrigger').classList.remove('hide');
- },
- confirmDeleteTrigger: function (view, index) {
- confirm(globalize.translate('MessageDeleteTaskTrigger'), globalize.translate('HeaderDeleteTaskTrigger')).then(function () {
- ScheduledTaskPage.deleteTrigger(view, index);
- });
- },
- deleteTrigger: function (view, index) {
- loading.show();
- const id = getParameterByName('id');
- ApiClient.getScheduledTask(id).then(function (task) {
- task.Triggers.splice(index, 1);
- ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).then(function () {
- ScheduledTaskPage.refreshScheduledTask(view);
- });
- });
- },
- refreshTriggerFields: function (page, triggerType) {
- if (triggerType == 'DailyTrigger') {
- page.querySelector('#fldTimeOfDay').classList.remove('hide');
- page.querySelector('#fldDayOfWeek').classList.add('hide');
- page.querySelector('#fldSelectSystemEvent').classList.add('hide');
- page.querySelector('#fldSelectInterval').classList.add('hide');
- page.querySelector('#selectTimeOfDay').setAttribute('required', 'required');
- } else if (triggerType == 'WeeklyTrigger') {
- page.querySelector('#fldTimeOfDay').classList.remove('hide');
- page.querySelector('#fldDayOfWeek').classList.remove('hide');
- page.querySelector('#fldSelectSystemEvent').classList.add('hide');
- page.querySelector('#fldSelectInterval').classList.add('hide');
- page.querySelector('#selectTimeOfDay').setAttribute('required', 'required');
- } else if (triggerType == 'SystemEventTrigger') {
- page.querySelector('#fldTimeOfDay').classList.add('hide');
- page.querySelector('#fldDayOfWeek').classList.add('hide');
- page.querySelector('#fldSelectSystemEvent').classList.remove('hide');
- page.querySelector('#fldSelectInterval').classList.add('hide');
- page.querySelector('#selectTimeOfDay').removeAttribute('required');
- } else if (triggerType == 'IntervalTrigger') {
- page.querySelector('#fldTimeOfDay').classList.add('hide');
- page.querySelector('#fldDayOfWeek').classList.add('hide');
- page.querySelector('#fldSelectSystemEvent').classList.add('hide');
- page.querySelector('#fldSelectInterval').classList.remove('hide');
- page.querySelector('#selectTimeOfDay').removeAttribute('required');
- } else if (triggerType == 'StartupTrigger') {
- page.querySelector('#fldTimeOfDay').classList.add('hide');
- page.querySelector('#fldDayOfWeek').classList.add('hide');
- page.querySelector('#fldSelectSystemEvent').classList.add('hide');
- page.querySelector('#fldSelectInterval').classList.add('hide');
- page.querySelector('#selectTimeOfDay').removeAttribute('required');
- }
- },
- getTriggerToAdd: function (page) {
- const trigger = {
- Type: page.querySelector('#selectTriggerType').value
- };
-
- if (trigger.Type == 'DailyTrigger') {
- trigger.TimeOfDayTicks = page.querySelector('#selectTimeOfDay').value;
- } else if (trigger.Type == 'WeeklyTrigger') {
- trigger.DayOfWeek = page.querySelector('#selectDayOfWeek').value;
- trigger.TimeOfDayTicks = page.querySelector('#selectTimeOfDay').value;
- } else if (trigger.Type == 'SystemEventTrigger') {
- trigger.SystemEvent = page.querySelector('#selectSystemEvent').value;
- } else if (trigger.Type == 'IntervalTrigger') {
- trigger.IntervalTicks = page.querySelector('#selectInterval').value;
- }
-
- let timeLimit = page.querySelector('#txtTimeLimit').value || '0';
- timeLimit = parseFloat(timeLimit) * 3600000;
-
- trigger.MaxRuntimeTicks = timeLimit * 1e4 || null;
-
- return trigger;
- }
-};
-export default function (view) {
- function onSubmit(e) {
- loading.show();
- const id = getParameterByName('id');
- ApiClient.getScheduledTask(id).then(function (task) {
- task.Triggers.push(ScheduledTaskPage.getTriggerToAdd(view));
- ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).then(function () {
- document.querySelector('#popupAddTrigger').classList.add('hide');
- ScheduledTaskPage.refreshScheduledTask(view);
- });
- });
- e.preventDefault();
- }
-
- view.querySelector('.addTriggerForm').addEventListener('submit', onSubmit);
- fillTimeOfDay(view.querySelector('#selectTimeOfDay'));
- view.querySelector('#popupAddTrigger').parentNode.trigger(new Event('create'));
- view.querySelector('.selectTriggerType').addEventListener('change', function () {
- ScheduledTaskPage.refreshTriggerFields(view, this.value);
- });
- view.querySelector('.btnAddTrigger').addEventListener('click', function () {
- ScheduledTaskPage.showAddTriggerPopup(view);
- });
- view.addEventListener('click', function (e) {
- const btnDeleteTrigger = dom.parentWithClass(e.target, 'btnDeleteTrigger');
-
- if (btnDeleteTrigger) {
- ScheduledTaskPage.confirmDeleteTrigger(view, parseInt(btnDeleteTrigger.getAttribute('data-index'), 10));
- }
- });
- view.addEventListener('viewshow', function () {
- ScheduledTaskPage.refreshScheduledTask(view);
- });
-}
-
diff --git a/src/apps/dashboard/features/scheduledtasks/api/useStartTask.ts b/src/apps/dashboard/features/tasks/api/useStartTask.ts
similarity index 100%
rename from src/apps/dashboard/features/scheduledtasks/api/useStartTask.ts
rename to src/apps/dashboard/features/tasks/api/useStartTask.ts
diff --git a/src/apps/dashboard/features/scheduledtasks/api/useStopTask.ts b/src/apps/dashboard/features/tasks/api/useStopTask.ts
similarity index 100%
rename from src/apps/dashboard/features/scheduledtasks/api/useStopTask.ts
rename to src/apps/dashboard/features/tasks/api/useStopTask.ts
diff --git a/src/apps/dashboard/features/tasks/api/useTask.ts b/src/apps/dashboard/features/tasks/api/useTask.ts
new file mode 100644
index 0000000000..98800420b4
--- /dev/null
+++ b/src/apps/dashboard/features/tasks/api/useTask.ts
@@ -0,0 +1,35 @@
+import type { ScheduledTasksApiGetTaskRequest } from '@jellyfin/sdk/lib/generated-client/api/scheduled-tasks-api';
+import type { AxiosRequestConfig } from 'axios';
+import type { Api } from '@jellyfin/sdk';
+import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-tasks-api';
+import { useQuery } from '@tanstack/react-query';
+
+import { useApi } from 'hooks/useApi';
+
+export const QUERY_KEY = 'Task';
+
+const fetchTask = async (
+ api: Api,
+ params: ScheduledTasksApiGetTaskRequest,
+ options?: AxiosRequestConfig
+) => {
+ if (!api) {
+ console.warn('[fetchTasks] No API instance available');
+ return;
+ }
+
+ const response = await getScheduledTasksApi(api).getTask(params, options);
+
+ return response.data;
+};
+
+export const useTask = (params: ScheduledTasksApiGetTaskRequest) => {
+ const { api } = useApi();
+
+ return useQuery({
+ queryKey: [ QUERY_KEY, params.taskId ],
+ queryFn: ({ signal }) =>
+ fetchTask(api!, params, { signal }),
+ enabled: !!api
+ });
+};
diff --git a/src/apps/dashboard/features/scheduledtasks/api/useTasks.ts b/src/apps/dashboard/features/tasks/api/useTasks.ts
similarity index 100%
rename from src/apps/dashboard/features/scheduledtasks/api/useTasks.ts
rename to src/apps/dashboard/features/tasks/api/useTasks.ts
diff --git a/src/apps/dashboard/features/tasks/api/useUpdateTask.ts b/src/apps/dashboard/features/tasks/api/useUpdateTask.ts
new file mode 100644
index 0000000000..8f34c0df38
--- /dev/null
+++ b/src/apps/dashboard/features/tasks/api/useUpdateTask.ts
@@ -0,0 +1,22 @@
+import { ScheduledTasksApiUpdateTaskRequest } from '@jellyfin/sdk/lib/generated-client/api/scheduled-tasks-api';
+import { getScheduledTasksApi } from '@jellyfin/sdk/lib/utils/api/scheduled-tasks-api';
+import { useMutation } from '@tanstack/react-query';
+import { useApi } from 'hooks/useApi';
+import { queryClient } from 'utils/query/queryClient';
+import { QUERY_KEY } from './useTasks';
+
+export const useUpdateTask = () => {
+ const { api } = useApi();
+
+ return useMutation({
+ mutationFn: (params: ScheduledTasksApiUpdateTaskRequest) => (
+ getScheduledTasksApi(api!)
+ .updateTask(params)
+ ),
+ onSuccess: () => {
+ void queryClient.invalidateQueries({
+ queryKey: [ QUERY_KEY ]
+ });
+ }
+ });
+};
diff --git a/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx b/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx
new file mode 100644
index 0000000000..53404885fc
--- /dev/null
+++ b/src/apps/dashboard/features/tasks/components/NewTriggerForm.tsx
@@ -0,0 +1,169 @@
+import React, { FunctionComponent, useCallback, useMemo, useState } from 'react';
+import Dialog from '@mui/material/Dialog';
+import Button from '@mui/material/Button';
+import DialogActions from '@mui/material/DialogActions';
+import DialogContent from '@mui/material/DialogContent';
+import DialogTitle from '@mui/material/DialogTitle';
+import MenuItem from '@mui/material/MenuItem';
+import Stack from '@mui/material/Stack';
+import TextField from '@mui/material/TextField';
+import type { TaskTriggerInfo } from '@jellyfin/sdk/lib/generated-client/models/task-trigger-info';
+import { TaskTriggerInfoType } from '@jellyfin/sdk/lib/generated-client/models/task-trigger-info-type';
+import { DayOfWeek } from '@jellyfin/sdk/lib/generated-client/models/day-of-week';
+import globalize from 'lib/globalize';
+import { getTimeOfDayOptions } from '../utils/edit';
+import { useLocale } from 'hooks/useLocale';
+
+type IProps = {
+ open: boolean,
+ title: string,
+ onClose?: () => void,
+ onSubmit?: (trigger: TaskTriggerInfo) => void
+};
+
+const NewTriggerForm: FunctionComponent
= ({ open, title, onClose, onSubmit }: IProps) => {
+ const { dateFnsLocale } = useLocale();
+ const [triggerType, setTriggerType] = useState('DailyTrigger');
+
+ const timeOfDayOptions = useMemo(() => getTimeOfDayOptions(dateFnsLocale), [dateFnsLocale]);
+
+ const onTriggerTypeChange = useCallback((e: React.ChangeEvent) => {
+ setTriggerType(e.target.value);
+ }, []);
+
+ return (
+
+ );
+};
+
+export default NewTriggerForm;
diff --git a/src/apps/dashboard/features/scheduledtasks/components/Task.tsx b/src/apps/dashboard/features/tasks/components/Task.tsx
similarity index 100%
rename from src/apps/dashboard/features/scheduledtasks/components/Task.tsx
rename to src/apps/dashboard/features/tasks/components/Task.tsx
diff --git a/src/apps/dashboard/features/scheduledtasks/components/TaskLastRan.tsx b/src/apps/dashboard/features/tasks/components/TaskLastRan.tsx
similarity index 100%
rename from src/apps/dashboard/features/scheduledtasks/components/TaskLastRan.tsx
rename to src/apps/dashboard/features/tasks/components/TaskLastRan.tsx
diff --git a/src/apps/dashboard/features/scheduledtasks/components/TaskProgress.tsx b/src/apps/dashboard/features/tasks/components/TaskProgress.tsx
similarity index 100%
rename from src/apps/dashboard/features/scheduledtasks/components/TaskProgress.tsx
rename to src/apps/dashboard/features/tasks/components/TaskProgress.tsx
diff --git a/src/apps/dashboard/features/tasks/components/TaskTriggerCell.tsx b/src/apps/dashboard/features/tasks/components/TaskTriggerCell.tsx
new file mode 100644
index 0000000000..9f4f8502c9
--- /dev/null
+++ b/src/apps/dashboard/features/tasks/components/TaskTriggerCell.tsx
@@ -0,0 +1,34 @@
+import React, { FC } from 'react';
+import type { MRT_Cell, MRT_RowData } from 'material-react-table';
+import { useLocale } from 'hooks/useLocale';
+import Box from '@mui/material/Box';
+import Typography from '@mui/material/Typography';
+import { getTriggerFriendlyName } from '../utils/edit';
+import type { TaskTriggerInfo } from '@jellyfin/sdk/lib/generated-client/models/task-trigger-info';
+import globalize from 'lib/globalize';
+
+interface CellProps {
+ cell: MRT_Cell
+}
+
+const TaskTriggerCell: FC = ({ cell }) => {
+ const { dateFnsLocale } = useLocale();
+ const trigger = cell.getValue();
+
+ const timeLimitHours = trigger.MaxRuntimeTicks ? trigger.MaxRuntimeTicks / 36e9 : false;
+
+ return (
+
+ {getTriggerFriendlyName(trigger, dateFnsLocale)}
+ {timeLimitHours && (
+
+ {timeLimitHours == 1 ?
+ globalize.translate('ValueTimeLimitSingleHour') :
+ globalize.translate('ValueTimeLimitMultiHour', timeLimitHours)}
+
+ )}
+
+ );
+};
+
+export default TaskTriggerCell;
diff --git a/src/apps/dashboard/features/scheduledtasks/components/Tasks.tsx b/src/apps/dashboard/features/tasks/components/Tasks.tsx
similarity index 100%
rename from src/apps/dashboard/features/scheduledtasks/components/Tasks.tsx
rename to src/apps/dashboard/features/tasks/components/Tasks.tsx
diff --git a/src/apps/dashboard/features/scheduledtasks/types/taskProps.ts b/src/apps/dashboard/features/tasks/types/taskProps.ts
similarity index 100%
rename from src/apps/dashboard/features/scheduledtasks/types/taskProps.ts
rename to src/apps/dashboard/features/tasks/types/taskProps.ts
diff --git a/src/apps/dashboard/features/tasks/utils/edit.ts b/src/apps/dashboard/features/tasks/utils/edit.ts
new file mode 100644
index 0000000000..2637465710
--- /dev/null
+++ b/src/apps/dashboard/features/tasks/utils/edit.ts
@@ -0,0 +1,64 @@
+import type { TaskTriggerInfo } from '@jellyfin/sdk/lib/generated-client/models/task-trigger-info';
+import { format, Locale, parse } from 'date-fns';
+import globalize from 'lib/globalize';
+
+function getDisplayTime(ticks: number, locale: Locale) {
+ const ms = ticks / 1e4;
+ const now = new Date();
+ now.setHours(0, 0, 0, 0);
+ now.setTime(now.getTime() + ms);
+ return format(now, 'p', { locale: locale });
+}
+
+export function getTimeOfDayOptions(locale: Locale) {
+ const options = [];
+
+ for (let i = 0; i < 86400000; i += 900000) {
+ options.push({
+ name: getDisplayTime(i * 10000, locale),
+ value: i * 10000
+ });
+ }
+
+ return options;
+}
+
+function getIntervalTriggerTime(ticks: number) {
+ const hours = ticks / 36e9;
+
+ switch (hours) {
+ case 0.25:
+ return globalize.translate('EveryXMinutes', '15');
+ case 0.5:
+ return globalize.translate('EveryXMinutes', '30');
+ case 0.75:
+ return globalize.translate('EveryXMinutes', '45');
+ case 1:
+ return globalize.translate('EveryHour');
+ default:
+ return globalize.translate('EveryXHours', hours);
+ }
+}
+
+function localizeDayOfWeek(dayOfWeek: string | null | undefined, locale: Locale) {
+ if (!dayOfWeek) return '';
+
+ const parsedDayOfWeek = parse(dayOfWeek, 'cccc', new Date());
+
+ return format(parsedDayOfWeek, 'cccc', { locale: locale });
+}
+
+export function getTriggerFriendlyName(trigger: TaskTriggerInfo, locale: Locale) {
+ switch (trigger.Type) {
+ case 'DailyTrigger':
+ return globalize.translate('DailyAt', getDisplayTime(trigger.TimeOfDayTicks || 0, locale));
+ case 'WeeklyTrigger':
+ return globalize.translate('WeeklyAt', localizeDayOfWeek(trigger.DayOfWeek, locale), getDisplayTime(trigger.TimeOfDayTicks || 0, locale));
+ case 'IntervalTrigger':
+ return getIntervalTriggerTime(trigger.IntervalTicks || 0);
+ case 'StartupTrigger':
+ return globalize.translate('OnApplicationStartup');
+ default:
+ return trigger.Type;
+ }
+}
diff --git a/src/apps/dashboard/features/scheduledtasks/utils/tasks.ts b/src/apps/dashboard/features/tasks/utils/tasks.ts
similarity index 100%
rename from src/apps/dashboard/features/scheduledtasks/utils/tasks.ts
rename to src/apps/dashboard/features/tasks/utils/tasks.ts
diff --git a/src/apps/dashboard/routes/_asyncRoutes.ts b/src/apps/dashboard/routes/_asyncRoutes.ts
index 5a7398da39..d66c4ed13d 100644
--- a/src/apps/dashboard/routes/_asyncRoutes.ts
+++ b/src/apps/dashboard/routes/_asyncRoutes.ts
@@ -15,6 +15,7 @@ export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [
{ path: 'playback/trickplay', type: AppType.Dashboard },
{ path: 'plugins/:pluginId', page: 'plugins/plugin', type: AppType.Dashboard },
{ path: 'tasks', type: AppType.Dashboard },
+ { path: 'tasks/edit', type: AppType.Dashboard },
{ path: 'users', type: AppType.Dashboard },
{ path: 'users/access', type: AppType.Dashboard },
{ path: 'users/add', type: AppType.Dashboard },
diff --git a/src/apps/dashboard/routes/_legacyRoutes.ts b/src/apps/dashboard/routes/_legacyRoutes.ts
index b690b45440..6cd162d88d 100644
--- a/src/apps/dashboard/routes/_legacyRoutes.ts
+++ b/src/apps/dashboard/routes/_legacyRoutes.ts
@@ -93,12 +93,5 @@ export const LEGACY_ADMIN_ROUTES: LegacyRoute[] = [
controller: 'plugins/installed/index',
view: 'plugins/installed/index.html'
}
- }, {
- path: 'tasks/edit',
- pageProps: {
- appType: AppType.Dashboard,
- controller: 'scheduledtasks/scheduledtask',
- view: 'scheduledtasks/scheduledtask.html'
- }
}
];
diff --git a/src/apps/dashboard/routes/tasks/edit.tsx b/src/apps/dashboard/routes/tasks/edit.tsx
new file mode 100644
index 0000000000..5120f80f05
--- /dev/null
+++ b/src/apps/dashboard/routes/tasks/edit.tsx
@@ -0,0 +1,173 @@
+import React, { useCallback, useMemo, useState } from 'react';
+import Page from 'components/Page';
+import { useSearchParams } from 'react-router-dom';
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+import Stack from '@mui/material/Stack';
+import Typography from '@mui/material/Typography';
+import AddIcon from '@mui/icons-material/Add';
+import IconButton from '@mui/material/IconButton';
+import Tooltip from '@mui/material/Tooltip';
+import RemoveCircleIcon from '@mui/icons-material/RemoveCircle';
+import Loading from 'components/loading/LoadingComponent';
+import { MRT_ColumnDef, MRT_Table, useMaterialReactTable } from 'material-react-table';
+import type { TaskTriggerInfo } from '@jellyfin/sdk/lib/generated-client/models/task-trigger-info';
+import globalize from '../../../../lib/globalize';
+import { useTask } from 'apps/dashboard/features/tasks/api/useTask';
+import { useUpdateTask } from 'apps/dashboard/features/tasks/api/useUpdateTask';
+import ConfirmDialog from 'components/ConfirmDialog';
+import TaskTriggerCell from 'apps/dashboard/features/tasks/components/TaskTriggerCell';
+import NewTriggerForm from 'apps/dashboard/features/tasks/components/NewTriggerForm';
+
+const TaskEdit = () => {
+ const [ searchParams ] = useSearchParams();
+ const updateTask = useUpdateTask();
+ const taskId = searchParams.get('id');
+ const { data: task, isLoading } = useTask({ taskId: taskId || '' });
+ const [ isAddTriggerDialogOpen, setIsAddTriggerDialogOpen ] = useState(false);
+ const [ isRemoveConfirmOpen, setIsRemoveConfirmOpen ] = useState(false);
+ const [ pendingDeleteTrigger, setPendingDeleteTrigger ] = useState(null);
+
+ const onCloseRemoveConfirmDialog = useCallback(() => {
+ setPendingDeleteTrigger(null);
+ setIsRemoveConfirmOpen(false);
+ }, []);
+
+ const onDeleteTrigger = useCallback((trigger: TaskTriggerInfo | null | undefined) => {
+ if (trigger) {
+ setPendingDeleteTrigger(trigger);
+ setIsRemoveConfirmOpen(true);
+ }
+ }, []);
+
+ const onConfirmDelete = useCallback(() => {
+ const triggersRemaining = task?.Triggers?.filter(trigger => trigger != pendingDeleteTrigger);
+
+ if (task?.Id && triggersRemaining) {
+ updateTask.mutate({
+ taskId: task.Id,
+ taskTriggerInfo: triggersRemaining
+ });
+ setIsRemoveConfirmOpen(false);
+ }
+ }, [task, pendingDeleteTrigger, updateTask]);
+
+ const showAddTriggerDialog = useCallback(() => {
+ setIsAddTriggerDialogOpen(true);
+ }, []);
+
+ const handleNewTriggerDialogClose = useCallback(() => {
+ setIsAddTriggerDialogOpen(false);
+ }, []);
+
+ const onNewTriggerSubmit = useCallback((trigger: TaskTriggerInfo) => {
+ if (task?.Triggers && task?.Id) {
+ const triggers = [...task.Triggers, trigger];
+
+ updateTask.mutate({
+ taskId: task.Id,
+ taskTriggerInfo: triggers
+ });
+ setIsAddTriggerDialogOpen(false);
+ }
+ }, [task, updateTask]);
+
+ const columns = useMemo[]>(() => [
+ {
+ id: 'TriggerTime',
+ accessorFn: row => row,
+ Cell: TaskTriggerCell,
+ header: globalize.translate('LabelTime')
+ }
+ ], []);
+
+ const table = useMaterialReactTable({
+ columns,
+ data: task?.Triggers || [],
+
+ enableSorting: false,
+ enableFilters: false,
+ enableColumnActions: false,
+ enablePagination: false,
+
+ state: {
+ isLoading
+ },
+
+ muiTableContainerProps: {
+ sx: {
+ maxHeight: 'calc(100% - 7rem)' // 2 x 3.5rem for header and footer
+ }
+ },
+
+ // Custom actions
+ enableRowActions: true,
+ positionActionsColumn: 'last',
+ displayColumnDefOptions: {
+ 'mrt-row-actions': {
+ header: ''
+ }
+ },
+ renderRowActions: ({ row }) => {
+ return (
+
+
+ onDeleteTrigger(row.original)}
+ >
+
+
+
+
+ );
+ }
+ });
+
+ if (isLoading || !task) {
+ return ;
+ }
+
+ return (
+
+
+
+
+
+
+ {task.Name}
+ {task.Description}
+ }
+ onClick={showAddTriggerDialog}
+ >{globalize.translate('ButtonAddScheduledTaskTrigger')}
+
+
+
+
+
+ );
+};
+
+export default TaskEdit;
diff --git a/src/apps/dashboard/routes/tasks/index.tsx b/src/apps/dashboard/routes/tasks/index.tsx
index f742ef8ede..232fb34950 100644
--- a/src/apps/dashboard/routes/tasks/index.tsx
+++ b/src/apps/dashboard/routes/tasks/index.tsx
@@ -3,10 +3,10 @@ import Page from 'components/Page';
import globalize from 'lib/globalize';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
-import { QUERY_KEY, useTasks } from '../../features/scheduledtasks/api/useTasks';
-import { getCategories, getTasksByCategory } from '../../features/scheduledtasks/utils/tasks';
+import { QUERY_KEY, useTasks } from '../../features/tasks/api/useTasks';
+import { getCategories, getTasksByCategory } from '../../features/tasks/utils/tasks';
import Loading from 'components/loading/LoadingComponent';
-import Tasks from '../../features/scheduledtasks/components/Tasks';
+import Tasks from '../../features/tasks/components/Tasks';
import type { TaskInfo } from '@jellyfin/sdk/lib/generated-client/models/task-info';
import { SessionMessageType } from '@jellyfin/sdk/lib/generated-client/models/session-message-type';
import serverNotifications from 'scripts/serverNotifications';