From 2c6f1318c650bab22cf0819e0351183fe40b3996 Mon Sep 17 00:00:00 2001 From: Vasily Strelyaev Date: Thu, 17 Apr 2025 17:29:47 +0300 Subject: [PATCH 1/6] fix editing time of secondary recurring appointment --- .../src/plugins/editing-state/helpers.ts | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts b/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts index 8303c516c5..cc9d06d45e 100644 --- a/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts +++ b/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts @@ -114,6 +114,12 @@ const getAppointmentSequenceData = ( return { initialSequence, currentChildIndex }; }; +const getSeriesChange = (date: Date, prevDate: Date, seriesDate: Date) => { + const diff = moment.utc(date).diff(prevDate); + + return moment(seriesDate).add(diff).toDate(); +}; + export const deleteCurrent: DeleteFn = (appointmentData) => { const { options, dates } = configureDateSequence( appointmentData.rRule, appointmentData.exDate, @@ -137,9 +143,33 @@ export const deleteCurrentAndFollowing: DeleteFn = appointmentData => changeCurr appointmentData, {}, deleteAll, ); -export const editAll: EditFn = (appointmentData, changes) => { +export const editAll: EditFn = (appointmentData, incomingChanges) => { const { rRule, id } = appointmentData; + let changes = { ...incomingChanges }; + + if (changes.startDate) { + changes = { + ...changes, + startDate: getSeriesChange( + changes.startDate as Date, + appointmentData.startDate as Date, + appointmentData.parentData.startDate, + ), + } + } + + if (changes.endDate) { + changes = { + ...changes, + endDate: getSeriesChange( + changes.endDate as Date, + appointmentData.endDate as Date, + appointmentData.parentData.endDate, + ), + } + } + const initialRule = new RRule(RRule.parseString(rRule as string)); if (changes.startDate && moment.utc(changes.startDate as Date).isAfter(initialRule.options.until!)) { From 4a6eb762ff10530a7d08081d4c9837190c21658a Mon Sep 17 00:00:00 2001 From: Vasily Strelyaev Date: Thu, 17 Apr 2025 17:38:58 +0300 Subject: [PATCH 2/6] lint --- .../dx-scheduler-core/src/plugins/editing-state/helpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts b/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts index cc9d06d45e..c6b68e8e84 100644 --- a/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts +++ b/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts @@ -156,7 +156,7 @@ export const editAll: EditFn = (appointmentData, incomingChanges) => { appointmentData.startDate as Date, appointmentData.parentData.startDate, ), - } + }; } if (changes.endDate) { @@ -167,7 +167,7 @@ export const editAll: EditFn = (appointmentData, incomingChanges) => { appointmentData.endDate as Date, appointmentData.parentData.endDate, ), - } + }; } const initialRule = new RRule(RRule.parseString(rRule as string)); From 35debd00e5a971d7cdaf7432bb99f6278fab5bc4 Mon Sep 17 00:00:00 2001 From: Vasily Strelyaev Date: Tue, 22 Apr 2025 15:11:18 +0300 Subject: [PATCH 3/6] update tests --- .../src/plugins/editing-state/helpers.test.ts | 86 +++++++++++++++---- 1 file changed, 70 insertions(+), 16 deletions(-) diff --git a/packages/dx-scheduler-core/src/plugins/editing-state/helpers.test.ts b/packages/dx-scheduler-core/src/plugins/editing-state/helpers.test.ts index edd46f4599..4f94577557 100644 --- a/packages/dx-scheduler-core/src/plugins/editing-state/helpers.test.ts +++ b/packages/dx-scheduler-core/src/plugins/editing-state/helpers.test.ts @@ -168,11 +168,23 @@ describe('EditingState', () => { }); describe('editing helpers', () => { - const appointmentDataBase = { + const firstAppointmentInSeries = { + id: 4, + startDate: new Date(Date.UTC(2019, 6, 15, 14, 20)), + endDate: new Date(Date.UTC(2019, 6, 15, 16)), + rRule: 'FREQ=DAILY;COUNT=2', + exDate: '20190716T142000Z', + parentData: { + id: 4, + startDate: new Date(Date.UTC(2019, 6, 15, 14, 20)), + endDate: new Date(Date.UTC(2019, 6, 15, 16)), + }, + }; + const secondAppointmentInSeries = { id: 4, startDate: new Date(Date.UTC(2019, 6, 17, 14, 20)), endDate: new Date(Date.UTC(2019, 6, 17, 16)), - rRule: 'FREQ=DAILY;COUNT=5', + rRule: 'FREQ=DAILY;COUNT=2', exDate: '20190716T142000Z', parentData: { id: 4, @@ -183,24 +195,66 @@ describe('EditingState', () => { describe('#editAll', () => { it('should edit simple recurrence', () => { const changes = { - startDate: new Date(Date.UTC(2019, 6, 17, 14, 20)), - endDate: new Date(Date.UTC(2019, 6, 17, 16)), - rRule: 'FREQ=DAILY;COUNT=5', + startDate: new Date(Date.UTC(2019, 6, 15, 15, 20)), + endDate: new Date(Date.UTC(2019, 6, 15, 17)), + rRule: 'FREQ=DAILY;COUNT=2', }; - expect(editAll(appointmentDataBase, changes)).toEqual({ + expect(editAll(firstAppointmentInSeries, changes)).toEqual({ changed: { 4: { - startDate: new Date(Date.UTC(2019, 6, 17, 14, 20)), - endDate: new Date(Date.UTC(2019, 6, 17, 16)), - rRule: 'FREQ=DAILY;COUNT=5', + startDate: new Date(Date.UTC(2019, 6, 15, 15, 20)), + endDate: new Date(Date.UTC(2019, 6, 15, 17)), + rRule: 'FREQ=DAILY;COUNT=2', + }, + }, + }); + }); + it('should update all appointments using deltas', () => { + const changes = { + startDate: new Date(Date.UTC(2019, 6, 17, 8)), + endDate: new Date(Date.UTC(2019, 6, 17, 19)), + rRule: 'FREQ=DAILY;COUNT=2', + }; + + expect(editAll(secondAppointmentInSeries, changes)).toEqual({ + changed: { + 4: { + startDate: new Date(Date.UTC(2019, 6, 15, 8)), + endDate: new Date(Date.UTC(2019, 6, 15, 19)), + rRule: 'FREQ=DAILY;COUNT=2', + }, + }, + }); + }); + it('should edit only one date in recurrent appointment', () => { + const changes = { + startDate: new Date(Date.UTC(2019, 6, 17, 14, 10)), + }; + + expect(editAll(secondAppointmentInSeries, changes)).toEqual({ + changed: { + 4: { + startDate: new Date(Date.UTC(2019, 6, 15, 14, 10)), + }, + }, + }); + + const otherChanges = { + endDate: new Date(Date.UTC(2019, 6, 17, 17)), + }; + + expect(editAll(secondAppointmentInSeries, otherChanges)).toEqual({ + changed: { + 4: { + endDate: new Date(Date.UTC(2019, 6, 15, 17)), }, }, }); }); it('should edit if the item is moved after until', () => { const appointmentData = { - ...appointmentDataBase, + ...firstAppointmentInSeries, startDate: new Date(Date.UTC(2019, 6, 17, 14, 20)), endDate: new Date(Date.UTC(2019, 6, 17, 16)), rRule: 'FREQ=DAILY;UNTIL=20190717T142000Z', @@ -224,7 +278,7 @@ describe('EditingState', () => { }); it('should edit if changes\' startDate is undefined', () => { const appointmentData = { - ...appointmentDataBase, + ...firstAppointmentInSeries, rRule: 'FREQ=DAILY;UNTIL=20190717T142000Z', }; const changes = { @@ -248,7 +302,7 @@ describe('EditingState', () => { endDate: new Date(Date.UTC(2019, 6, 17, 16)), }; - expect(editCurrent(appointmentDataBase, changes)).toEqual({ + expect(editCurrent(secondAppointmentInSeries, changes)).toEqual({ changed: { 4: { exDate: '20190716T142000Z,20190717T142000Z', @@ -268,7 +322,7 @@ describe('EditingState', () => { endDate: new Date(Date.UTC(2019, 6, 17, 16)), }; - expect(editCurrent({ ...appointmentDataBase, exDate: '' }, changes)).toEqual({ + expect(editCurrent({ ...secondAppointmentInSeries, exDate: '' }, changes)).toEqual({ changed: { 4: { exDate: '20190717T142000Z', @@ -287,7 +341,7 @@ describe('EditingState', () => { title: 'Next title', }; - expect(editCurrent(appointmentDataBase, changes)).toEqual({ + expect(editCurrent(secondAppointmentInSeries, changes)).toEqual({ changed: { 4: { exDate: '20190716T142000Z,20190717T142000Z', @@ -305,7 +359,7 @@ describe('EditingState', () => { describe('#editCurrentAndFollowing', () => { it('should work with excluded days', () => { const appointmentData = { - ...appointmentDataBase, + ...secondAppointmentInSeries, startDate: new Date(Date.UTC(2019, 6, 18, 14, 20)), endDate: new Date(Date.UTC(2019, 6, 18, 16)), }; @@ -332,7 +386,7 @@ describe('EditingState', () => { title: 'Next title', }; - expect(editCurrentAndFollowing(appointmentDataBase, changes)).toEqual({ + expect(editCurrentAndFollowing(secondAppointmentInSeries, changes)).toEqual({ changed: { 4: { rRule: 'FREQ=DAILY;UNTIL=20190715T142000Z', From 640f61edee5193820dd43035431c453a3f3b8f02 Mon Sep 17 00:00:00 2001 From: Vasily Strelyaev Date: Wed, 23 Apr 2025 15:01:04 +0300 Subject: [PATCH 4/6] fix tests again --- .../src/plugins/editing-state/helpers.test.ts | 42 +++++++++---------- .../src/plugins/editing-state/helpers.ts | 32 +++++++------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/packages/dx-scheduler-core/src/plugins/editing-state/helpers.test.ts b/packages/dx-scheduler-core/src/plugins/editing-state/helpers.test.ts index 4f94577557..e31c0231cd 100644 --- a/packages/dx-scheduler-core/src/plugins/editing-state/helpers.test.ts +++ b/packages/dx-scheduler-core/src/plugins/editing-state/helpers.test.ts @@ -172,7 +172,7 @@ describe('EditingState', () => { id: 4, startDate: new Date(Date.UTC(2019, 6, 15, 14, 20)), endDate: new Date(Date.UTC(2019, 6, 15, 16)), - rRule: 'FREQ=DAILY;COUNT=2', + rRule: 'FREQ=DAILY;COUNT=5', exDate: '20190716T142000Z', parentData: { id: 4, @@ -180,11 +180,11 @@ describe('EditingState', () => { endDate: new Date(Date.UTC(2019, 6, 15, 16)), }, }; - const secondAppointmentInSeries = { + const appointmentDataBase = { id: 4, startDate: new Date(Date.UTC(2019, 6, 17, 14, 20)), endDate: new Date(Date.UTC(2019, 6, 17, 16)), - rRule: 'FREQ=DAILY;COUNT=2', + rRule: 'FREQ=DAILY;COUNT=5', exDate: '20190716T142000Z', parentData: { id: 4, @@ -197,7 +197,7 @@ describe('EditingState', () => { const changes = { startDate: new Date(Date.UTC(2019, 6, 15, 15, 20)), endDate: new Date(Date.UTC(2019, 6, 15, 17)), - rRule: 'FREQ=DAILY;COUNT=2', + rRule: 'FREQ=DAILY;COUNT=5', }; expect(editAll(firstAppointmentInSeries, changes)).toEqual({ @@ -205,7 +205,7 @@ describe('EditingState', () => { 4: { startDate: new Date(Date.UTC(2019, 6, 15, 15, 20)), endDate: new Date(Date.UTC(2019, 6, 15, 17)), - rRule: 'FREQ=DAILY;COUNT=2', + rRule: 'FREQ=DAILY;COUNT=5', }, }, }); @@ -214,15 +214,15 @@ describe('EditingState', () => { const changes = { startDate: new Date(Date.UTC(2019, 6, 17, 8)), endDate: new Date(Date.UTC(2019, 6, 17, 19)), - rRule: 'FREQ=DAILY;COUNT=2', + rRule: 'FREQ=DAILY;COUNT=5', }; - expect(editAll(secondAppointmentInSeries, changes)).toEqual({ + expect(editAll(appointmentDataBase, changes)).toEqual({ changed: { 4: { startDate: new Date(Date.UTC(2019, 6, 15, 8)), endDate: new Date(Date.UTC(2019, 6, 15, 19)), - rRule: 'FREQ=DAILY;COUNT=2', + rRule: 'FREQ=DAILY;COUNT=5', }, }, }); @@ -232,7 +232,7 @@ describe('EditingState', () => { startDate: new Date(Date.UTC(2019, 6, 17, 14, 10)), }; - expect(editAll(secondAppointmentInSeries, changes)).toEqual({ + expect(editAll(appointmentDataBase, changes)).toEqual({ changed: { 4: { startDate: new Date(Date.UTC(2019, 6, 15, 14, 10)), @@ -244,7 +244,7 @@ describe('EditingState', () => { endDate: new Date(Date.UTC(2019, 6, 17, 17)), }; - expect(editAll(secondAppointmentInSeries, otherChanges)).toEqual({ + expect(editAll(appointmentDataBase, otherChanges)).toEqual({ changed: { 4: { endDate: new Date(Date.UTC(2019, 6, 15, 17)), @@ -261,15 +261,15 @@ describe('EditingState', () => { exDate: '20190716T142000Z', }; const changes = { - startDate: new Date(Date.UTC(2019, 6, 18, 14, 20)), - endDate: new Date(Date.UTC(2019, 6, 18, 16)), + startDate: new Date(Date.UTC(2019, 8, 18, 14, 20)), + endDate: new Date(Date.UTC(2019, 8, 18, 16)), }; expect(editAll(appointmentData, changes)).toEqual({ changed: { 4: { - startDate: new Date(Date.UTC(2019, 6, 18, 14, 20)), - endDate: new Date(Date.UTC(2019, 6, 18, 16)), + startDate: new Date(Date.UTC(2019, 8, 18, 14, 20)), + endDate: new Date(Date.UTC(2019, 8, 18, 16)), rRule: 'FREQ=DAILY;COUNT=1', exDate: '', }, @@ -302,7 +302,7 @@ describe('EditingState', () => { endDate: new Date(Date.UTC(2019, 6, 17, 16)), }; - expect(editCurrent(secondAppointmentInSeries, changes)).toEqual({ + expect(editCurrent(appointmentDataBase, changes)).toEqual({ changed: { 4: { exDate: '20190716T142000Z,20190717T142000Z', @@ -322,7 +322,7 @@ describe('EditingState', () => { endDate: new Date(Date.UTC(2019, 6, 17, 16)), }; - expect(editCurrent({ ...secondAppointmentInSeries, exDate: '' }, changes)).toEqual({ + expect(editCurrent({ ...appointmentDataBase, exDate: '' }, changes)).toEqual({ changed: { 4: { exDate: '20190717T142000Z', @@ -341,7 +341,7 @@ describe('EditingState', () => { title: 'Next title', }; - expect(editCurrent(secondAppointmentInSeries, changes)).toEqual({ + expect(editCurrent(appointmentDataBase, changes)).toEqual({ changed: { 4: { exDate: '20190716T142000Z,20190717T142000Z', @@ -359,7 +359,7 @@ describe('EditingState', () => { describe('#editCurrentAndFollowing', () => { it('should work with excluded days', () => { const appointmentData = { - ...secondAppointmentInSeries, + ...appointmentDataBase, startDate: new Date(Date.UTC(2019, 6, 18, 14, 20)), endDate: new Date(Date.UTC(2019, 6, 18, 16)), }; @@ -386,7 +386,7 @@ describe('EditingState', () => { title: 'Next title', }; - expect(editCurrentAndFollowing(secondAppointmentInSeries, changes)).toEqual({ + expect(editCurrentAndFollowing(appointmentDataBase, changes)).toEqual({ changed: { 4: { rRule: 'FREQ=DAILY;UNTIL=20190715T142000Z', @@ -414,8 +414,8 @@ describe('EditingState', () => { }, }; const changes = { - startDate: new Date(Date.UTC(2019, 6, 15, 11, 20)), - endDate: new Date(Date.UTC(2019, 6, 15, 14)), + startDate: new Date(Date.UTC(2019, 6, 16, 11, 20)), + endDate: new Date(Date.UTC(2019, 6, 16, 14)), }; expect(editCurrentAndFollowing(appointmentData, changes)).toEqual({ diff --git a/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts b/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts index c6b68e8e84..163ca6e940 100644 --- a/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts +++ b/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts @@ -143,10 +143,24 @@ export const deleteCurrentAndFollowing: DeleteFn = appointmentData => changeCurr appointmentData, {}, deleteAll, ); -export const editAll: EditFn = (appointmentData, incomingChanges) => { +export const editAll: EditFn = (appointmentData, appointmentChanges) => { const { rRule, id } = appointmentData; - let changes = { ...incomingChanges }; + let changes = { ...appointmentChanges }; + + const initialRule = new RRule(RRule.parseString(rRule as string)); + if (changes.startDate + && moment.utc(changes.startDate as Date).isAfter(initialRule.options.until!)) { + return { + changed: { + [id!]: { + ...changes, + rRule: 'FREQ=DAILY;COUNT=1', + exDate: '', + }, + }, + }; + } if (changes.startDate) { changes = { @@ -170,20 +184,6 @@ export const editAll: EditFn = (appointmentData, incomingChanges) => { }; } - const initialRule = new RRule(RRule.parseString(rRule as string)); - if (changes.startDate - && moment.utc(changes.startDate as Date).isAfter(initialRule.options.until!)) { - return { - changed: { - [id!]: { - ...changes, - rRule: 'FREQ=DAILY;COUNT=1', - exDate: '', - }, - }, - }; - } - return { changed: { [appointmentData.id!]: changes } }; }; From d34233c6e933242c4e4cb4a1c60dcbafd1422fa1 Mon Sep 17 00:00:00 2001 From: Vasily Strelyaev Date: Wed, 23 Apr 2025 17:58:33 +0300 Subject: [PATCH 5/6] prettify code --- .../src/plugins/editing-state/helpers.ts | 77 +++++++++++-------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts b/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts index 163ca6e940..cd41daf7d7 100644 --- a/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts +++ b/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts @@ -114,12 +114,6 @@ const getAppointmentSequenceData = ( return { initialSequence, currentChildIndex }; }; -const getSeriesChange = (date: Date, prevDate: Date, seriesDate: Date) => { - const diff = moment.utc(date).diff(prevDate); - - return moment(seriesDate).add(diff).toDate(); -}; - export const deleteCurrent: DeleteFn = (appointmentData) => { const { options, dates } = configureDateSequence( appointmentData.rRule, appointmentData.exDate, @@ -143,48 +137,69 @@ export const deleteCurrentAndFollowing: DeleteFn = appointmentData => changeCurr appointmentData, {}, deleteAll, ); -export const editAll: EditFn = (appointmentData, appointmentChanges) => { - const { rRule, id } = appointmentData; +const getParentChanges = ( + appointmentData: Partial, changes: Changes +): Partial => { + let parentChanges: Changes = {}; - let changes = { ...appointmentChanges }; + const convert = ( + date: Date, prevDate: Date, parentDate: Date + ): Date => { + const diff = moment.utc(date).diff(prevDate); - const initialRule = new RRule(RRule.parseString(rRule as string)); - if (changes.startDate - && moment.utc(changes.startDate as Date).isAfter(initialRule.options.until!)) { - return { - changed: { - [id!]: { - ...changes, - rRule: 'FREQ=DAILY;COUNT=1', - exDate: '', - }, - }, - }; + return moment(parentDate).add(diff).toDate(); } if (changes.startDate) { - changes = { - ...changes, - startDate: getSeriesChange( + parentChanges = { + ...parentChanges, + startDate: convert( changes.startDate as Date, appointmentData.startDate as Date, - appointmentData.parentData.startDate, + appointmentData.parentData.startDate as Date, ), - }; + } } if (changes.endDate) { - changes = { - ...changes, - endDate: getSeriesChange( + parentChanges = { + ...parentChanges, + endDate: convert( changes.endDate as Date, appointmentData.endDate as Date, - appointmentData.parentData.endDate, + appointmentData.parentData.endDate as Date, ), + } + } + + return parentChanges; +}; + +export const editAll: EditFn = (appointmentData, changes) => { + const { rRule, id } = appointmentData; + + const initialRule = new RRule(RRule.parseString(rRule as string)); + if (changes.startDate + && moment.utc(changes.startDate as Date).isAfter(initialRule.options.until!)) { + return { + changed: { + [id!]: { + ...changes, + rRule: 'FREQ=DAILY;COUNT=1', + exDate: '', + }, + }, }; } - return { changed: { [appointmentData.id!]: changes } }; + return { + changed: { + [appointmentData.id!]: { + ...changes, + ...getParentChanges(appointmentData, changes), + } + } + }; }; export const editCurrent: EditFn = (appointmentData, changes) => ({ From 8c31e60880c6f95a8d904703a7748f9e36333ca9 Mon Sep 17 00:00:00 2001 From: Vasily Strelyaev Date: Wed, 23 Apr 2025 18:06:30 +0300 Subject: [PATCH 6/6] lint --- .../src/plugins/editing-state/helpers.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts b/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts index cd41daf7d7..2971d87e60 100644 --- a/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts +++ b/packages/dx-scheduler-core/src/plugins/editing-state/helpers.ts @@ -138,17 +138,17 @@ export const deleteCurrentAndFollowing: DeleteFn = appointmentData => changeCurr ); const getParentChanges = ( - appointmentData: Partial, changes: Changes + appointmentData: Partial, changes: Changes, ): Partial => { let parentChanges: Changes = {}; const convert = ( - date: Date, prevDate: Date, parentDate: Date + date: Date, prevDate: Date, parentDate: Date, ): Date => { const diff = moment.utc(date).diff(prevDate); return moment(parentDate).add(diff).toDate(); - } + }; if (changes.startDate) { parentChanges = { @@ -158,7 +158,7 @@ const getParentChanges = ( appointmentData.startDate as Date, appointmentData.parentData.startDate as Date, ), - } + }; } if (changes.endDate) { @@ -169,7 +169,7 @@ const getParentChanges = ( appointmentData.endDate as Date, appointmentData.parentData.endDate as Date, ), - } + }; } return parentChanges; @@ -197,8 +197,8 @@ export const editAll: EditFn = (appointmentData, changes) => { [appointmentData.id!]: { ...changes, ...getParentChanges(appointmentData, changes), - } - } + }, + }, }; };