diff --git a/docs/recurring-tasks-user-guide.md b/docs/recurring-tasks-user-guide.md index 98476b64..3d91572a 100644 --- a/docs/recurring-tasks-user-guide.md +++ b/docs/recurring-tasks-user-guide.md @@ -16,24 +16,45 @@ Recurring tasks are tasks that repeat automatically on a schedule you choose. Th 5. Save the task. It will now be created automatically based on your chosen schedule. ## Schedule Options -You can choose how often your task repeats. Here are the most common options: +You can choose how often your task repeats. Here are the available options: - **Daily:** The task is created every day. -- **Weekly:** The task is created once a week. You can pick the day (e.g., every Monday). -- **Monthly:** The task is created once a month. You can pick the date (e.g., the 1st of every month). -- **Weekdays:** The task is created every Monday to Friday. -- **Custom:** Set your own schedule, such as every 2 days, every 3 weeks, or only on certain days. +- **Weekly:** The task is created once a week. You can pick one or more days (e.g., every Monday and Thursday). +- **Monthly:** The task is created once a month. You have two options: + - **On a specific date:** Choose a date from 1 to 28 (limited to 28 to ensure consistency across all months) + - **On a specific day:** Choose a week (first, second, third, fourth, or last) and a day of the week +- **Every X Days:** The task is created every specified number of days (e.g., every 3 days) +- **Every X Weeks:** The task is created every specified number of weeks (e.g., every 2 weeks) +- **Every X Months:** The task is created every specified number of months (e.g., every 3 months) ### Examples - "Send team update" every Friday (weekly) -- "Submit expense report" on the 1st of each month (monthly) +- "Submit expense report" on the 15th of each month (monthly, specific date) +- "Monthly team meeting" on the first Monday of each month (monthly, specific day) - "Check backups" every day (daily) -- "Review project status" every Monday and Thursday (custom) +- "Review project status" every Monday and Thursday (weekly, multiple days) +- "Quarterly report" every 3 months (every X months) + +## Future Task Creation +The system automatically creates tasks up to a certain point in the future to ensure timely scheduling: + +- **Daily Tasks:** Created up to 7 days in advance +- **Weekly Tasks:** Created up to 2 weeks in advance +- **Monthly Tasks:** Created up to 2 months in advance +- **Every X Days/Weeks/Months:** Created up to 2 intervals in advance + +This ensures that: +- You always have upcoming tasks visible in your schedule +- Tasks are created at appropriate intervals +- The system maintains a reasonable number of future tasks ## Tips - You can edit or stop a recurring task at any time. - Assign team members and labels to recurring tasks for better organization. - Check your task list regularly to see newly created recurring tasks. +- For monthly tasks, dates are limited to 1-28 to ensure the task occurs on the same date every month. +- Tasks are created automatically within the future limit window - you don't need to manually create them. +- If you need to see tasks further in the future, they will be created automatically as the current tasks are completed. ## Need Help? If you have questions or need help setting up recurring tasks, contact your workspace admin or support team. \ No newline at end of file diff --git a/docs/recurring-tasks.md b/docs/recurring-tasks.md index 71fd51cc..71448719 100644 --- a/docs/recurring-tasks.md +++ b/docs/recurring-tasks.md @@ -17,6 +17,51 @@ The recurring tasks cron job automates the creation of tasks based on predefined 3. Checks if a task for the next occurrence already exists. 4. Creates a new task if it does not exist and the next occurrence is within the allowed future window. +## Future Limit Logic +The system implements different future limits based on the schedule type to maintain an appropriate number of future tasks: + +```typescript +const FUTURE_LIMITS = { + daily: moment.duration(7, 'days'), + weekly: moment.duration(2, 'weeks'), + monthly: moment.duration(2, 'months'), + every_x_days: (interval: number) => moment.duration(interval * 2, 'days'), + every_x_weeks: (interval: number) => moment.duration(interval * 2, 'weeks'), + every_x_months: (interval: number) => moment.duration(interval * 2, 'months') +}; +``` + +### Implementation Details +- **Base Calculation:** + ```typescript + const futureLimit = moment(template.last_checked_at || template.created_at) + .add(getFutureLimit(schedule.schedule_type, schedule.interval), 'days'); + ``` + +- **Task Creation Rules:** + 1. Only create tasks if the next occurrence is before the future limit + 2. Skip creation if a task already exists for that date + 3. Update `last_checked_at` after processing + +- **Benefits:** + - Prevents excessive task creation + - Maintains system performance + - Ensures timely task visibility + - Allows for schedule modifications + +## Date Handling +- **Monthly Tasks:** + - Dates are limited to 1-28 to ensure consistency across all months + - This prevents issues with months having different numbers of days + - No special handling needed for February or months with 30/31 days +- **Weekly Tasks:** + - Supports multiple days of the week (0-6, where 0 is Sunday) + - Tasks are created for each selected day +- **Interval-based Tasks:** + - Every X days/weeks/months from the last task's end date + - Minimum interval is 1 day/week/month + - No maximum limit, but tasks are only created up to the future limit + ## Database Interactions - **Templates and Schedules:** - Templates are stored in `task_recurring_templates`. @@ -27,6 +72,7 @@ The recurring tasks cron job automates the creation of tasks based on predefined - Assigns team members and labels by calling appropriate functions/controllers. - **State Tracking:** - Updates `last_checked_at` and `last_created_task_end_date` in the schedule after processing. + - Maintains future limits based on schedule type. ## Task Creation Process 1. **Fetch Templates:** Retrieve all templates and their associated schedules. @@ -41,10 +87,12 @@ The recurring tasks cron job automates the creation of tasks based on predefined - **Cron Expression:** Modify the `TIME` constant in the code to change the schedule. - **Task Template Structure:** Extend the template or schedule interfaces to support additional fields. - **Task Creation Logic:** Customize the task creation process or add new assignment/labeling logic as needed. +- **Future Window:** Adjust the future limits by modifying the `FUTURE_LIMITS` configuration. ## Error Handling - Errors are logged using the `log_error` utility. - The job continues processing other templates even if one fails. +- Failed task creations are not retried automatically. ## References - Source: `src/cron_jobs/recurring-tasks.ts` diff --git a/worklenz-backend/.env.template b/worklenz-backend/.env.template index e0bea264..fdd8fe44 100644 --- a/worklenz-backend/.env.template +++ b/worklenz-backend/.env.template @@ -78,4 +78,8 @@ GOOGLE_CAPTCHA_SECRET_KEY=your_captcha_secret_key GOOGLE_CAPTCHA_PASS_SCORE=0.8 # Email Cronjobs -ENABLE_EMAIL_CRONJOBS=true \ No newline at end of file +ENABLE_EMAIL_CRONJOBS=true + +# RECURRING_JOBS +ENABLE_RECURRING_JOBS=true +RECURRING_JOBS_INTERVAL="0 11 */1 * 1-5" \ No newline at end of file diff --git a/worklenz-backend/src/controllers/tasks-controller-base.ts b/worklenz-backend/src/controllers/tasks-controller-base.ts index 1fe89210..d2524bad 100644 --- a/worklenz-backend/src/controllers/tasks-controller-base.ts +++ b/worklenz-backend/src/controllers/tasks-controller-base.ts @@ -1,6 +1,6 @@ import WorklenzControllerBase from "./worklenz-controller-base"; -import {getColor} from "../shared/utils"; -import {PriorityColorCodes, TASK_PRIORITY_COLOR_ALPHA, TASK_STATUS_COLOR_ALPHA} from "../shared/constants"; +import { getColor } from "../shared/utils"; +import { PriorityColorCodes, TASK_PRIORITY_COLOR_ALPHA, TASK_STATUS_COLOR_ALPHA } from "../shared/constants"; import moment from "moment/moment"; export const GroupBy = { @@ -32,23 +32,14 @@ export default class TasksControllerBase extends WorklenzControllerBase { } public static updateTaskViewModel(task: any) { - console.log(`Processing task ${task.id} (${task.name})`); - console.log(` manual_progress: ${task.manual_progress}, progress_value: ${task.progress_value}`); - console.log(` project_use_manual_progress: ${task.project_use_manual_progress}, project_use_weighted_progress: ${task.project_use_weighted_progress}`); - console.log(` has subtasks: ${task.sub_tasks_count > 0}`); - // For parent tasks (with subtasks), always use calculated progress from subtasks if (task.sub_tasks_count > 0) { - // For parent tasks without manual progress, calculate from subtasks (already done via db function) - console.log(` Parent task with subtasks: complete_ratio=${task.complete_ratio}`); - // Ensure progress matches complete_ratio for consistency task.progress = task.complete_ratio || 0; - + // Important: Parent tasks should not have manual progress // If they somehow do, reset it if (task.manual_progress) { - console.log(` WARNING: Parent task ${task.id} had manual_progress set to true, resetting`); task.manual_progress = false; task.progress_value = null; } @@ -58,28 +49,24 @@ export default class TasksControllerBase extends WorklenzControllerBase { // For manually set progress, use that value directly task.progress = parseInt(task.progress_value); task.complete_ratio = parseInt(task.progress_value); - - console.log(` Using manual progress: progress=${task.progress}, complete_ratio=${task.complete_ratio}`); - } + } // For tasks with no subtasks and no manual progress, calculate based on time else { - task.progress = task.total_minutes_spent && task.total_minutes - ? ~~(task.total_minutes_spent / task.total_minutes * 100) + task.progress = task.total_minutes_spent && task.total_minutes + ? ~~(task.total_minutes_spent / task.total_minutes * 100) : 0; - + // Set complete_ratio to match progress task.complete_ratio = task.progress; - - console.log(` Calculated time-based progress: progress=${task.progress}, complete_ratio=${task.complete_ratio}`); } - + // Ensure numeric values task.progress = parseInt(task.progress) || 0; task.complete_ratio = parseInt(task.complete_ratio) || 0; - + task.overdue = task.total_minutes < task.total_minutes_spent; - task.time_spent = {hours: ~~(task.total_minutes_spent / 60), minutes: task.total_minutes_spent % 60}; + task.time_spent = { hours: ~~(task.total_minutes_spent / 60), minutes: task.total_minutes_spent % 60 }; task.comments_count = Number(task.comments_count) ? +task.comments_count : 0; task.attachments_count = Number(task.attachments_count) ? +task.attachments_count : 0; diff --git a/worklenz-backend/src/controllers/tasks-controller-v2.ts b/worklenz-backend/src/controllers/tasks-controller-v2.ts index d6efa1bb..6e01c686 100644 --- a/worklenz-backend/src/controllers/tasks-controller-v2.ts +++ b/worklenz-backend/src/controllers/tasks-controller-v2.ts @@ -97,7 +97,6 @@ export default class TasksControllerV2 extends TasksControllerBase { try { const result = await db.query("SELECT get_task_complete_ratio($1) AS info;", [taskId]); const [data] = result.rows; - console.log("data", data); if (data && data.info && data.info.ratio !== undefined) { data.info.ratio = +((data.info.ratio || 0).toFixed()); return data.info; diff --git a/worklenz-backend/src/cron_jobs/recurring-tasks.ts b/worklenz-backend/src/cron_jobs/recurring-tasks.ts index 16854c7e..2780edd5 100644 --- a/worklenz-backend/src/cron_jobs/recurring-tasks.ts +++ b/worklenz-backend/src/cron_jobs/recurring-tasks.ts @@ -7,12 +7,90 @@ import TasksController from "../controllers/tasks-controller"; // At 11:00+00 (4.30pm+530) on every day-of-month if it's on every day-of-week from Monday through Friday. // const TIME = "0 11 */1 * 1-5"; -const TIME = "*/2 * * * *"; // runs every 2 minutes - for testing purposes +const TIME = process.env.RECURRING_JOBS_INTERVAL || "0 11 */1 * 1-5"; const TIME_FORMAT = "YYYY-MM-DD"; // const TIME = "0 0 * * *"; // Runs at midnight every day const log = (value: any) => console.log("recurring-task-cron-job:", value); +// Define future limits for different schedule types +// More conservative limits to prevent task list clutter +const FUTURE_LIMITS = { + daily: moment.duration(3, "days"), + weekly: moment.duration(1, "week"), + monthly: moment.duration(1, "month"), + every_x_days: (interval: number) => moment.duration(interval, "days"), + every_x_weeks: (interval: number) => moment.duration(interval, "weeks"), + every_x_months: (interval: number) => moment.duration(interval, "months") +}; + +// Helper function to get the future limit based on schedule type +function getFutureLimit(scheduleType: string, interval?: number): moment.Duration { + switch (scheduleType) { + case "daily": + return FUTURE_LIMITS.daily; + case "weekly": + return FUTURE_LIMITS.weekly; + case "monthly": + return FUTURE_LIMITS.monthly; + case "every_x_days": + return FUTURE_LIMITS.every_x_days(interval || 1); + case "every_x_weeks": + return FUTURE_LIMITS.every_x_weeks(interval || 1); + case "every_x_months": + return FUTURE_LIMITS.every_x_months(interval || 1); + default: + return moment.duration(3, "days"); // Default to 3 days + } +} + +// Helper function to batch create tasks +async function createBatchTasks(template: ITaskTemplate & IRecurringSchedule, endDates: moment.Moment[]) { + const createdTasks = []; + + for (const nextEndDate of endDates) { + const existingTaskQuery = ` + SELECT id FROM tasks + WHERE schedule_id = $1 AND end_date::DATE = $2::DATE; + `; + const existingTaskResult = await db.query(existingTaskQuery, [template.schedule_id, nextEndDate.format(TIME_FORMAT)]); + + if (existingTaskResult.rows.length === 0) { + const createTaskQuery = `SELECT create_quick_task($1::json) as task;`; + const taskData = { + name: template.name, + priority_id: template.priority_id, + project_id: template.project_id, + reporter_id: template.reporter_id, + status_id: template.status_id || null, + end_date: nextEndDate.format(TIME_FORMAT), + schedule_id: template.schedule_id + }; + const createTaskResult = await db.query(createTaskQuery, [JSON.stringify(taskData)]); + const createdTask = createTaskResult.rows[0].task; + + if (createdTask) { + createdTasks.push(createdTask); + + for (const assignee of template.assignees) { + await TasksController.createTaskBulkAssignees(assignee.team_member_id, template.project_id, createdTask.id, assignee.assigned_by); + } + + for (const label of template.labels) { + const q = `SELECT add_or_remove_task_label($1, $2) AS labels;`; + await db.query(q, [createdTask.id, label.label_id]); + } + + console.log(`Created task for template ${template.name} with end date ${nextEndDate.format(TIME_FORMAT)}`); + } + } else { + console.log(`Skipped creating task for template ${template.name} with end date ${nextEndDate.format(TIME_FORMAT)} - task already exists`); + } + } + + return createdTasks; +} + async function onRecurringTaskJobTick() { try { log("(cron) Recurring tasks job started."); @@ -33,65 +111,44 @@ async function onRecurringTaskJobTick() { ? moment(template.last_task_end_date) : moment(template.created_at); - const futureLimit = moment(template.last_checked_at || template.created_at).add(1, "week"); + // Calculate future limit based on schedule type + const futureLimit = moment(template.last_checked_at || template.created_at) + .add(getFutureLimit( + template.schedule_type, + template.interval_days || template.interval_weeks || template.interval_months || 1 + )); let nextEndDate = calculateNextEndDate(template, lastTaskEndDate); + const endDatesToCreate: moment.Moment[] = []; - // Find the next future occurrence - while (nextEndDate.isSameOrBefore(now)) { + // Find all future occurrences within the limit + while (nextEndDate.isSameOrBefore(futureLimit)) { + if (nextEndDate.isAfter(now)) { + endDatesToCreate.push(moment(nextEndDate)); + } nextEndDate = calculateNextEndDate(template, nextEndDate); } - // Only create a task if it's within the future limit - if (nextEndDate.isSameOrBefore(futureLimit)) { - const existingTaskQuery = ` - SELECT id FROM tasks - WHERE schedule_id = $1 AND end_date::DATE = $2::DATE; + // Batch create tasks for all future dates + if (endDatesToCreate.length > 0) { + const createdTasks = await createBatchTasks(template, endDatesToCreate); + createdTaskCount += createdTasks.length; + + // Update the last_checked_at in the schedule + const updateScheduleQuery = ` + UPDATE task_recurring_schedules + SET last_checked_at = $1::DATE, + last_created_task_end_date = $2 + WHERE id = $3; `; - const existingTaskResult = await db.query(existingTaskQuery, [template.schedule_id, nextEndDate.format(TIME_FORMAT)]); - - if (existingTaskResult.rows.length === 0) { - const createTaskQuery = `SELECT create_quick_task($1::json) as task;`; - const taskData = { - name: template.name, - priority_id: template.priority_id, - project_id: template.project_id, - reporter_id: template.reporter_id, - status_id: template.status_id || null, - end_date: nextEndDate.format(TIME_FORMAT), - schedule_id: template.schedule_id - }; - const createTaskResult = await db.query(createTaskQuery, [JSON.stringify(taskData)]); - const createdTask = createTaskResult.rows[0].task; - - if (createdTask) { - createdTaskCount++; - - for (const assignee of template.assignees) { - await TasksController.createTaskBulkAssignees(assignee.team_member_id, template.project_id, createdTask.id, assignee.assigned_by); - } - - for (const label of template.labels) { - const q = `SELECT add_or_remove_task_label($1, $2) AS labels;`; - await db.query(q, [createdTask.id, label.label_id]); - } - - console.log(`Created task for template ${template.name} with end date ${nextEndDate.format(TIME_FORMAT)}`); - } - } else { - console.log(`Skipped creating task for template ${template.name} with end date ${nextEndDate.format(TIME_FORMAT)} - task already exists`); - } + await db.query(updateScheduleQuery, [ + moment().format(TIME_FORMAT), + endDatesToCreate[endDatesToCreate.length - 1].format(TIME_FORMAT), + template.schedule_id + ]); } else { - console.log(`No task created for template ${template.name} - next occurrence is beyond the future limit`); + console.log(`No tasks created for template ${template.name} - next occurrence is beyond the future limit`); } - - // Update the last_checked_at in the schedule - const updateScheduleQuery = ` - UPDATE task_recurring_schedules - SET last_checked_at = $1::DATE, last_created_task_end_date = $2 - WHERE id = $3; - `; - await db.query(updateScheduleQuery, [moment(template.last_checked_at || template.created_at).add(1, "day").format(TIME_FORMAT), nextEndDate.format(TIME_FORMAT), template.schedule_id]); } log(`(cron) Recurring tasks job ended with ${createdTaskCount} new tasks created.`); diff --git a/worklenz-frontend/public/locales/en/task-drawer/task-drawer-recurring-config.json b/worklenz-frontend/public/locales/en/task-drawer/task-drawer-recurring-config.json index f1d0301d..10a9db71 100644 --- a/worklenz-frontend/public/locales/en/task-drawer/task-drawer-recurring-config.json +++ b/worklenz-frontend/public/locales/en/task-drawer/task-drawer-recurring-config.json @@ -2,6 +2,7 @@ "recurring": "Recurring", "recurringTaskConfiguration": "Recurring task configuration", "repeats": "Repeats", + "daily": "Daily", "weekly": "Weekly", "everyXDays": "Every X Days", "everyXWeeks": "Every X Weeks", diff --git a/worklenz-frontend/public/locales/es/task-drawer/task-drawer-recurring-config.json b/worklenz-frontend/public/locales/es/task-drawer/task-drawer-recurring-config.json index d9c711a5..ecc48c5f 100644 --- a/worklenz-frontend/public/locales/es/task-drawer/task-drawer-recurring-config.json +++ b/worklenz-frontend/public/locales/es/task-drawer/task-drawer-recurring-config.json @@ -2,6 +2,7 @@ "recurring": "Recurrente", "recurringTaskConfiguration": "Configuración de tarea recurrente", "repeats": "Repeticiones", + "daily": "Diario", "weekly": "Semanal", "everyXDays": "Cada X días", "everyXWeeks": "Cada X semanas", diff --git a/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-recurring-config.json b/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-recurring-config.json index 5619884b..d693f277 100644 --- a/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-recurring-config.json +++ b/worklenz-frontend/public/locales/pt/task-drawer/task-drawer-recurring-config.json @@ -2,6 +2,7 @@ "recurring": "Recorrente", "recurringTaskConfiguration": "Configuração de tarefa recorrente", "repeats": "Repete", + "daily": "Diário", "weekly": "Semanal", "everyXDays": "A cada X dias", "everyXWeeks": "A cada X semanas", diff --git a/worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-recurring-config/task-drawer-recurring-config.tsx b/worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-recurring-config/task-drawer-recurring-config.tsx index 608fc321..1ff8b315 100644 --- a/worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-recurring-config/task-drawer-recurring-config.tsx +++ b/worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-recurring-config/task-drawer-recurring-config.tsx @@ -24,44 +24,46 @@ import { taskRecurringApiService } from '@/api/tasks/task-recurring.api.service' import logger from '@/utils/errorLogger'; import { setTaskRecurringSchedule } from '@/features/task-drawer/task-drawer.slice'; -const repeatOptions: IRepeatOption[] = [ - { label: 'Daily', value: ITaskRecurring.Daily }, - { label: 'Weekly', value: ITaskRecurring.Weekly }, - { label: 'Every X Days', value: ITaskRecurring.EveryXDays }, - { label: 'Every X Weeks', value: ITaskRecurring.EveryXWeeks }, - { label: 'Every X Months', value: ITaskRecurring.EveryXMonths }, - { label: 'Monthly', value: ITaskRecurring.Monthly }, -]; - -const daysOfWeek = [ - { label: 'Sunday', value: 0, checked: false }, - { label: 'Monday', value: 1, checked: false }, - { label: 'Tuesday', value: 2, checked: false }, - { label: 'Wednesday', value: 3, checked: false }, - { label: 'Thursday', value: 4, checked: false }, - { label: 'Friday', value: 5, checked: false }, - { label: 'Saturday', value: 6, checked: false } -]; - const monthlyDateOptions = Array.from({ length: 28 }, (_, i) => i + 1); -const weekOptions = [ - { label: 'First', value: 1 }, - { label: 'Second', value: 2 }, - { label: 'Third', value: 3 }, - { label: 'Fourth', value: 4 }, - { label: 'Last', value: 5 } -]; -const dayOptions = daysOfWeek.map(d => ({ label: d.label, value: d.value })); const TaskDrawerRecurringConfig = ({ task }: { task: ITaskViewModel }) => { const { socket, connected } = useSocket(); const dispatch = useAppDispatch(); const { t } = useTranslation('task-drawer/task-drawer-recurring-config'); + const repeatOptions: IRepeatOption[] = [ + { label: t('daily'), value: ITaskRecurring.Daily }, + { label: t('weekly'), value: ITaskRecurring.Weekly }, + { label: t('everyXDays'), value: ITaskRecurring.EveryXDays }, + { label: t('everyXWeeks'), value: ITaskRecurring.EveryXWeeks }, + { label: t('everyXMonths'), value: ITaskRecurring.EveryXMonths }, + { label: t('monthly'), value: ITaskRecurring.Monthly }, + ]; + + const daysOfWeek = [ + { label: t('sun'), value: 0, checked: false }, + { label: t('mon'), value: 1, checked: false }, + { label: t('tue'), value: 2, checked: false }, + { label: t('wed'), value: 3, checked: false }, + { label: t('thu'), value: 4, checked: false }, + { label: t('fri'), value: 5, checked: false }, + { label: t('sat'), value: 6, checked: false } + ]; + + const weekOptions = [ + { label: t('first'), value: 1 }, + { label: t('second'), value: 2 }, + { label: t('third'), value: 3 }, + { label: t('fourth'), value: 4 }, + { label: t('last'), value: 5 } + ]; + + const dayOptions = daysOfWeek.map(d => ({ label: d.label, value: d.value })); + const [recurring, setRecurring] = useState(false); const [showConfig, setShowConfig] = useState(false); const [repeatOption, setRepeatOption] = useState({}); - const [selectedDays, setSelectedDays] = useState([]); + const [selectedDays, setSelectedDays] = useState([]); const [monthlyOption, setMonthlyOption] = useState('date'); const [selectedMonthlyDate, setSelectedMonthlyDate] = useState(1); const [selectedMonthlyWeek, setSelectedMonthlyWeek] = useState(weekOptions[0].value); @@ -106,8 +108,8 @@ const TaskDrawerRecurringConfig = ({ task }: { task: ITaskViewModel }) => { [repeatOption] ); - const handleDayCheckboxChange = (checkedValues: string[]) => { - setSelectedDays(checkedValues as unknown as string[]); + const handleDayCheckboxChange = (checkedValues: number[]) => { + setSelectedDays(checkedValues); }; const getSelectedDays = () => { @@ -165,7 +167,9 @@ const TaskDrawerRecurringConfig = ({ task }: { task: ITaskViewModel }) => { const res = await taskRecurringApiService.updateTaskRecurringData(task.schedule_id, body); if (res.done) { + setRecurring(true); setShowConfig(false); + configVisibleChange(false); } } catch (e) { logger.error("handleSave", e); @@ -220,9 +224,9 @@ const TaskDrawerRecurringConfig = ({ task }: { task: ITaskViewModel }) => { if (!task) return; if (task) setRecurring(!!task.schedule_id); - if (recurring) void getScheduleData(); + if (task.schedule_id) void getScheduleData(); socket?.on(SocketEvents.TASK_RECURRING_CHANGE.toString(), handleResponse); - }, [task]); + }, [task?.schedule_id]); return (
@@ -232,11 +236,11 @@ const TaskDrawerRecurringConfig = ({ task }: { task: ITaskViewModel }) => {   {recurring && (
- + { )} {monthlyOption === 'day' && ( <> - + { )} {repeatOption.value === ITaskRecurring.EveryXDays && ( - + { )} {repeatOption.value === ITaskRecurring.EveryXWeeks && ( - + { )} {repeatOption.value === ITaskRecurring.EveryXMonths && ( - + { loading={updatingData} onClick={handleSave} > - Save Changes + {t('saveChanges')}