Fix recurrence occurences (#750)
This commit is contained in:
parent
703f6fe506
commit
b4de9c23eb
4 changed files with 63 additions and 74 deletions
|
|
@ -87,7 +87,7 @@ async function calculateNextIterations(task, startFromDate, userTimezone) {
|
|||
const weekdays = Array.isArray(task.recurrence_weekdays)
|
||||
? task.recurrence_weekdays
|
||||
: JSON.parse(task.recurrence_weekdays);
|
||||
const todayWeekday = nextDate.getDay();
|
||||
const todayWeekday = nextDate.getUTCDay();
|
||||
console.log('Weekly recurrence check:', {
|
||||
weekdays,
|
||||
todayWeekday,
|
||||
|
|
@ -98,7 +98,7 @@ async function calculateNextIterations(task, startFromDate, userTimezone) {
|
|||
task.recurrence_weekday !== null &&
|
||||
task.recurrence_weekday !== undefined
|
||||
) {
|
||||
const todayWeekday = nextDate.getDay();
|
||||
const todayWeekday = nextDate.getUTCDay();
|
||||
includesToday = task.recurrence_weekday === todayWeekday;
|
||||
}
|
||||
} else if (task.recurrence_type === 'daily') {
|
||||
|
|
@ -145,8 +145,8 @@ async function calculateNextIterations(task, startFromDate, userTimezone) {
|
|||
// If today doesn't match, calculate the next occurrence
|
||||
if (!includesToday) {
|
||||
if (task.recurrence_type === 'daily') {
|
||||
nextDate.setDate(
|
||||
nextDate.getDate() + (task.recurrence_interval || 1)
|
||||
nextDate.setUTCDate(
|
||||
nextDate.getUTCDate() + (task.recurrence_interval || 1)
|
||||
);
|
||||
} else if (task.recurrence_type === 'weekly') {
|
||||
const interval = task.recurrence_interval || 1;
|
||||
|
|
@ -154,16 +154,18 @@ async function calculateNextIterations(task, startFromDate, userTimezone) {
|
|||
task.recurrence_weekday !== null &&
|
||||
task.recurrence_weekday !== undefined
|
||||
) {
|
||||
const currentWeekday = nextDate.getDay();
|
||||
const currentWeekday = nextDate.getUTCDay();
|
||||
const daysUntilTarget =
|
||||
(task.recurrence_weekday - currentWeekday + 7) % 7;
|
||||
if (daysUntilTarget === 0) {
|
||||
nextDate.setDate(nextDate.getDate() + interval * 7);
|
||||
nextDate.setUTCDate(nextDate.getUTCDate() + interval * 7);
|
||||
} else {
|
||||
nextDate.setDate(nextDate.getDate() + daysUntilTarget);
|
||||
nextDate.setUTCDate(
|
||||
nextDate.getUTCDate() + daysUntilTarget
|
||||
);
|
||||
}
|
||||
} else {
|
||||
nextDate.setDate(nextDate.getDate() + interval * 7);
|
||||
nextDate.setUTCDate(nextDate.getUTCDate() + interval * 7);
|
||||
}
|
||||
} else {
|
||||
nextDate = calculateNextDueDate(task, startDate);
|
||||
|
|
@ -188,8 +190,8 @@ async function calculateNextIterations(task, startFromDate, userTimezone) {
|
|||
|
||||
if (task.recurrence_type === 'daily') {
|
||||
nextDate = new Date(nextDate);
|
||||
nextDate.setDate(
|
||||
nextDate.getDate() + (task.recurrence_interval || 1)
|
||||
nextDate.setUTCDate(
|
||||
nextDate.getUTCDate() + (task.recurrence_interval || 1)
|
||||
);
|
||||
} else if (task.recurrence_type === 'weekly') {
|
||||
nextDate = new Date(nextDate);
|
||||
|
|
@ -200,14 +202,13 @@ async function calculateNextIterations(task, startFromDate, userTimezone) {
|
|||
const weekdays = Array.isArray(task.recurrence_weekdays)
|
||||
? task.recurrence_weekdays
|
||||
: JSON.parse(task.recurrence_weekdays);
|
||||
const currentWeekday = nextDate.getDay();
|
||||
|
||||
// Find next matching weekday
|
||||
let found = false;
|
||||
for (let daysAhead = 1; daysAhead <= 7; daysAhead++) {
|
||||
const testDate = new Date(nextDate);
|
||||
testDate.setDate(testDate.getDate() + daysAhead);
|
||||
const testWeekday = testDate.getDay();
|
||||
testDate.setUTCDate(testDate.getUTCDate() + daysAhead);
|
||||
const testWeekday = testDate.getUTCDay();
|
||||
|
||||
if (weekdays.includes(testWeekday)) {
|
||||
nextDate = testDate;
|
||||
|
|
@ -218,12 +219,12 @@ async function calculateNextIterations(task, startFromDate, userTimezone) {
|
|||
|
||||
if (!found) {
|
||||
// Fallback: add 7 days
|
||||
nextDate.setDate(nextDate.getDate() + 7);
|
||||
nextDate.setUTCDate(nextDate.getUTCDate() + 7);
|
||||
}
|
||||
} else {
|
||||
// Old behavior for single weekday
|
||||
nextDate.setDate(
|
||||
nextDate.getDate() + (task.recurrence_interval || 1) * 7
|
||||
nextDate.setUTCDate(
|
||||
nextDate.getUTCDate() + (task.recurrence_interval || 1) * 7
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ const calculateNextDueDate = (task, fromDate) => {
|
|||
|
||||
const calculateDailyRecurrence = (fromDate, interval) => {
|
||||
const nextDate = new Date(fromDate);
|
||||
nextDate.setDate(nextDate.getDate() + interval);
|
||||
nextDate.setUTCDate(nextDate.getUTCDate() + interval);
|
||||
return nextDate;
|
||||
};
|
||||
|
||||
|
|
@ -60,22 +60,22 @@ const calculateWeeklyRecurrence = (fromDate, interval, weekday) => {
|
|||
const nextDate = new Date(fromDate);
|
||||
|
||||
if (weekday !== null && weekday !== undefined) {
|
||||
const currentWeekday = nextDate.getDay();
|
||||
const currentWeekday = nextDate.getUTCDay();
|
||||
const daysUntilTarget = (weekday - currentWeekday + 7) % 7;
|
||||
|
||||
if (
|
||||
daysUntilTarget === 0 &&
|
||||
nextDate.getTime() === fromDate.getTime()
|
||||
) {
|
||||
nextDate.setDate(nextDate.getDate() + interval * 7);
|
||||
nextDate.setUTCDate(nextDate.getUTCDate() + interval * 7);
|
||||
} else {
|
||||
nextDate.setDate(nextDate.getDate() + daysUntilTarget);
|
||||
nextDate.setUTCDate(nextDate.getUTCDate() + daysUntilTarget);
|
||||
if (nextDate <= fromDate) {
|
||||
nextDate.setDate(nextDate.getDate() + interval * 7);
|
||||
nextDate.setUTCDate(nextDate.getUTCDate() + interval * 7);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nextDate.setDate(nextDate.getDate() + interval * 7);
|
||||
nextDate.setUTCDate(nextDate.getUTCDate() + interval * 7);
|
||||
}
|
||||
|
||||
return nextDate;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ describe('Recurring Tasks', () => {
|
|||
describe('Daily Recurrence', () => {
|
||||
it('should set correct due date for daily recurring task', async () => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ describe('Recurring Tasks', () => {
|
|||
|
||||
it('should handle daily recurrence with interval of 2', async () => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
const taskData = {
|
||||
name: 'Every Other Day Task',
|
||||
|
|
@ -73,7 +73,7 @@ describe('Recurring Tasks', () => {
|
|||
new Date(task.due_date)
|
||||
);
|
||||
const expectedDate = new Date(today);
|
||||
expectedDate.setDate(expectedDate.getDate() + 2);
|
||||
expectedDate.setUTCDate(expectedDate.getUTCDate() + 2);
|
||||
|
||||
expect(nextDate.toISOString().split('T')[0]).toBe(
|
||||
expectedDate.toISOString().split('T')[0]
|
||||
|
|
@ -82,7 +82,7 @@ describe('Recurring Tasks', () => {
|
|||
|
||||
it('should handle daily recurrence with interval of 7', async () => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
const taskData = {
|
||||
name: 'Weekly via Daily',
|
||||
|
|
@ -101,7 +101,7 @@ describe('Recurring Tasks', () => {
|
|||
new Date(task.due_date)
|
||||
);
|
||||
const expectedDate = new Date(today);
|
||||
expectedDate.setDate(expectedDate.getDate() + 7);
|
||||
expectedDate.setUTCDate(expectedDate.getUTCDate() + 7);
|
||||
|
||||
expect(nextDate.toISOString().split('T')[0]).toBe(
|
||||
expectedDate.toISOString().split('T')[0]
|
||||
|
|
@ -112,8 +112,8 @@ describe('Recurring Tasks', () => {
|
|||
describe('Weekly Recurrence', () => {
|
||||
it('should set correct due date for weekly recurring task', async () => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const todayWeekday = today.getDay();
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
const todayWeekday = today.getUTCDay();
|
||||
|
||||
const taskData = {
|
||||
name: 'Weekly Meeting',
|
||||
|
|
@ -135,8 +135,8 @@ describe('Recurring Tasks', () => {
|
|||
|
||||
it('should calculate next week for weekly recurrence', async () => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const todayWeekday = today.getDay();
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
const todayWeekday = today.getUTCDay();
|
||||
|
||||
const taskData = {
|
||||
name: 'Weekly Review',
|
||||
|
|
@ -154,7 +154,7 @@ describe('Recurring Tasks', () => {
|
|||
new Date(task.due_date)
|
||||
);
|
||||
const expectedDate = new Date(today);
|
||||
expectedDate.setDate(expectedDate.getDate() + 7);
|
||||
expectedDate.setUTCDate(expectedDate.getUTCDate() + 7);
|
||||
|
||||
expect(nextDate.toISOString().split('T')[0]).toBe(
|
||||
expectedDate.toISOString().split('T')[0]
|
||||
|
|
@ -163,8 +163,8 @@ describe('Recurring Tasks', () => {
|
|||
|
||||
it('should handle bi-weekly recurrence', async () => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const todayWeekday = today.getDay();
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
const todayWeekday = today.getUTCDay();
|
||||
|
||||
const taskData = {
|
||||
name: 'Bi-weekly Task',
|
||||
|
|
@ -182,7 +182,7 @@ describe('Recurring Tasks', () => {
|
|||
new Date(task.due_date)
|
||||
);
|
||||
const expectedDate = new Date(today);
|
||||
expectedDate.setDate(expectedDate.getDate() + 14);
|
||||
expectedDate.setUTCDate(expectedDate.getUTCDate() + 14);
|
||||
|
||||
expect(nextDate.toISOString().split('T')[0]).toBe(
|
||||
expectedDate.toISOString().split('T')[0]
|
||||
|
|
@ -191,8 +191,8 @@ describe('Recurring Tasks', () => {
|
|||
|
||||
it('should handle weekly recurrence on a different weekday', async () => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const todayWeekday = today.getDay();
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
const todayWeekday = today.getUTCDay();
|
||||
// Target a different weekday (e.g., if today is Monday (1), target Friday (5))
|
||||
const targetWeekday = (todayWeekday + 4) % 7;
|
||||
|
||||
|
|
@ -211,7 +211,7 @@ describe('Recurring Tasks', () => {
|
|||
task,
|
||||
new Date(task.due_date)
|
||||
);
|
||||
expect(nextDate.getDay()).toBe(targetWeekday);
|
||||
expect(nextDate.getUTCDay()).toBe(targetWeekday);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -413,7 +413,7 @@ describe('Recurring Tasks', () => {
|
|||
describe('Due Date Refresh on Completion', () => {
|
||||
it('should advance due date when daily recurring task is completed', async () => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
// Create a daily recurring task
|
||||
const task = await Task.create({
|
||||
|
|
@ -444,7 +444,7 @@ describe('Recurring Tasks', () => {
|
|||
// Due date should be advanced by 1 day
|
||||
const newDueDate = new Date(task.due_date);
|
||||
const expectedDate = new Date(originalDueDate);
|
||||
expectedDate.setDate(expectedDate.getDate() + 1);
|
||||
expectedDate.setUTCDate(expectedDate.getUTCDate() + 1);
|
||||
|
||||
expect(newDueDate.toISOString().split('T')[0]).toBe(
|
||||
expectedDate.toISOString().split('T')[0]
|
||||
|
|
@ -460,8 +460,8 @@ describe('Recurring Tasks', () => {
|
|||
|
||||
it('should advance due date when weekly recurring task is completed', async () => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const todayWeekday = today.getDay();
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
const todayWeekday = today.getUTCDay();
|
||||
|
||||
const task = await Task.create({
|
||||
name: 'Weekly Task',
|
||||
|
|
@ -485,7 +485,7 @@ describe('Recurring Tasks', () => {
|
|||
// Due date should be advanced by 1 week
|
||||
const newDueDate = new Date(task.due_date);
|
||||
const expectedDate = new Date(originalDueDate);
|
||||
expectedDate.setDate(expectedDate.getDate() + 7);
|
||||
expectedDate.setUTCDate(expectedDate.getUTCDate() + 7);
|
||||
|
||||
expect(newDueDate.toISOString().split('T')[0]).toBe(
|
||||
expectedDate.toISOString().split('T')[0]
|
||||
|
|
@ -536,7 +536,7 @@ describe('Recurring Tasks', () => {
|
|||
|
||||
it('should track multiple completions in RecurringCompletion table', async () => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
const task = await Task.create({
|
||||
name: 'Daily Task',
|
||||
|
|
@ -639,7 +639,7 @@ describe('Recurring Tasks', () => {
|
|||
// Next due date should be 2 days ago (original due date + 1 day)
|
||||
const newDueDate = new Date(task.due_date);
|
||||
const expectedDate = new Date(originalDueDate);
|
||||
expectedDate.setDate(expectedDate.getDate() + 1);
|
||||
expectedDate.setUTCDate(expectedDate.getUTCDate() + 1);
|
||||
|
||||
expect(newDueDate.toISOString().split('T')[0]).toBe(
|
||||
expectedDate.toISOString().split('T')[0]
|
||||
|
|
@ -767,7 +767,7 @@ describe('Recurring Tasks', () => {
|
|||
|
||||
it('should handle interval > 1 with completion_based', async () => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
const task = await Task.create({
|
||||
name: 'Every 3 Days Completion Based',
|
||||
|
|
@ -799,7 +799,7 @@ describe('Recurring Tasks', () => {
|
|||
|
||||
it('should respect updated completion_based flag', async () => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
// Create task with completion_based = false
|
||||
const task = await Task.create({
|
||||
|
|
@ -840,7 +840,7 @@ describe('Recurring Tasks', () => {
|
|||
|
||||
it('should handle multiple rapid completions with completion_based', async () => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
const task = await Task.create({
|
||||
name: 'Rapid Completions',
|
||||
|
|
@ -901,7 +901,7 @@ describe('Recurring Tasks', () => {
|
|||
describe('Recurrence End Date', () => {
|
||||
it('should stop recurring when end date is reached', async () => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const dayAfterTomorrow = new Date(today);
|
||||
|
|
@ -963,7 +963,7 @@ describe('Recurring Tasks', () => {
|
|||
|
||||
it('should allow recurring tasks without end date to continue indefinitely', async () => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
const task = await Task.create({
|
||||
name: 'Infinite Task',
|
||||
|
|
@ -995,7 +995,7 @@ describe('Recurring Tasks', () => {
|
|||
describe('Edge Cases', () => {
|
||||
it('should handle completing a task multiple times in quick succession', async () => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
const task = await Task.create({
|
||||
name: 'Quick Complete Task',
|
||||
|
|
@ -1029,7 +1029,7 @@ describe('Recurring Tasks', () => {
|
|||
|
||||
// Second due date should be one day after first
|
||||
const expectedDate = new Date(dueDateAfterFirst);
|
||||
expectedDate.setDate(expectedDate.getDate() + 1);
|
||||
expectedDate.setUTCDate(expectedDate.getUTCDate() + 1);
|
||||
expect(dueDateAfterSecond.toISOString().split('T')[0]).toBe(
|
||||
expectedDate.toISOString().split('T')[0]
|
||||
);
|
||||
|
|
@ -1037,7 +1037,7 @@ describe('Recurring Tasks', () => {
|
|||
|
||||
it('should handle uncompleting a recurring task', async () => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
const task = await Task.create({
|
||||
name: 'Uncomplete Task',
|
||||
|
|
@ -1124,7 +1124,7 @@ describe('Recurring Tasks', () => {
|
|||
|
||||
const dueDate = new Date(task.due_date);
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
|
||||
|
|
|
|||
|
|
@ -488,12 +488,9 @@ const TaskDetails: React.FC = () => {
|
|||
) {
|
||||
try {
|
||||
setLoadingIterations(true);
|
||||
const startFromDate = task.due_date
|
||||
? task.due_date.split('T')[0]
|
||||
: undefined;
|
||||
// Don't pass startFromDate - let backend default to today
|
||||
const iterations = await fetchTaskNextIterations(
|
||||
task.uid!,
|
||||
startFromDate
|
||||
task.uid!
|
||||
);
|
||||
setNextIterations(iterations);
|
||||
} catch (error) {
|
||||
|
|
@ -511,12 +508,9 @@ const TaskDetails: React.FC = () => {
|
|||
try {
|
||||
setLoadingIterations(true);
|
||||
|
||||
const startFromDate = task.due_date
|
||||
? task.due_date.split('T')[0]
|
||||
: undefined;
|
||||
// Don't pass startFromDate - let backend default to today
|
||||
const iterations = await fetchTaskNextIterations(
|
||||
parentTask.uid,
|
||||
startFromDate
|
||||
parentTask.uid
|
||||
);
|
||||
|
||||
setNextIterations(iterations);
|
||||
|
|
@ -721,21 +715,15 @@ const TaskDetails: React.FC = () => {
|
|||
try {
|
||||
setLoadingIterations(true);
|
||||
if (isTemplateTask) {
|
||||
const startFromDate = latestTask.due_date
|
||||
? latestTask.due_date.split('T')[0]
|
||||
: undefined;
|
||||
// Don't pass startFromDate - let backend default to today
|
||||
const iterations = await fetchTaskNextIterations(
|
||||
latestTask.uid!,
|
||||
startFromDate
|
||||
latestTask.uid!
|
||||
);
|
||||
setNextIterations(iterations);
|
||||
} else if (canUseParentIterations && parentTask?.uid) {
|
||||
const startFromDate = latestTask.due_date
|
||||
? latestTask.due_date.split('T')[0]
|
||||
: undefined;
|
||||
// Don't pass startFromDate - let backend default to today
|
||||
const iterations = await fetchTaskNextIterations(
|
||||
parentTask.uid,
|
||||
startFromDate
|
||||
parentTask.uid
|
||||
);
|
||||
setNextIterations(iterations);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue