@blazeo.com/appointment-client 1.0.10 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -63,18 +63,33 @@ function dayOrderIndex(d) {
63
63
  const i = DAY_NAMES.indexOf(u);
64
64
  return i >= 0 ? i : 999;
65
65
  }
66
- /** Merge rows that share participant + time span + off into one row with combined `days`. */
66
+ /** Merge rows that share participant + time span into one row with combined active `days`. */
67
67
  function mergeOpeningHoursBySlot(rows) {
68
68
  const map = new Map();
69
69
  for (const r of rows) {
70
- const key = [r.member, r.startHour, r.startMinute, r.endHour, r.endMinute, r.off].join("|");
70
+ // Key excludes 'off' because we want to merge ON and OFF records for the same time slot
71
+ const key = [r.member, r.startHour, r.startMinute, r.endHour, r.endMinute].join("|");
71
72
  const existing = map.get(key);
73
+ // We only want to add the day to the 'days' array if it is NOT marked as OFF.
74
+ // If the whole record was marked as OFF, we still process it to establish the slot,
75
+ // but its days won't be listed as 'active'.
76
+ const activeDaysFromThisRow = r.off ? [] : r.days;
72
77
  if (!existing) {
73
- map.set(key, { ...r, days: [...r.days] });
78
+ map.set(key, { ...r, days: [...activeDaysFromThisRow], off: false });
74
79
  }
75
80
  else {
76
- const set = new Set([...existing.days, ...r.days]);
81
+ const set = new Set([...existing.days, ...activeDaysFromThisRow]);
77
82
  existing.days = Array.from(set).sort((a, b) => dayOrderIndex(a) - dayOrderIndex(b));
83
+ // If we encounter any record that is NOT off, the whole merged slot is NOT off.
84
+ if (!r.off)
85
+ existing.off = false;
86
+ }
87
+ }
88
+ // Final Pass: If a slot has NO active days, it should be marked as off: true
89
+ // (though in Plan V2, these usually just disappear from the UI's 'days' list)
90
+ for (const group of map.values()) {
91
+ if (group.days.length === 0) {
92
+ group.off = true;
78
93
  }
79
94
  }
80
95
  rows.length = 0;
@@ -120,7 +120,10 @@ async function runMembersAndOpeningHoursAfterCalendarSave(calendar, calendarNode
120
120
  endHour: oh.endHour,
121
121
  endMinute: oh.endMinute,
122
122
  off: isOff,
123
- openingHourId: openingHourId,
123
+ // Plan V2 Optimization: Generate a unique ID for EVERY day record.
124
+ // This prevents the backend from deduplicating/overwriting when multiple
125
+ // records for the same participant + slot are sent in one batch.
126
+ openingHourId: newOpeningHourId(),
124
127
  });
125
128
  }
126
129
  }
@@ -128,6 +131,10 @@ async function runMembersAndOpeningHoursAfterCalendarSave(calendar, calendarNode
128
131
  for (const [participantId, payload] of hoursByParticipant.entries()) {
129
132
  if (payload.length === 0)
130
133
  continue;
134
+ // Plan V2 Optimization: Clear existing records for this participant first.
135
+ // This ensures that when we save the new batch (with unique per-day IDs),
136
+ // we don't leak orphaned records or create duplicates during updates.
137
+ await calendarNode.removeParticipantOpeningHours(participantId);
131
138
  // Use the batch save method (plural)
132
139
  const res = await saveCalendarOpeningHoursBatch(calendarNode, payload);
133
140
  if (isFailureStatus(res)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blazeo.com/appointment-client",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -91,19 +91,37 @@ function dayOrderIndex(d: string): number {
91
91
  return i >= 0 ? i : 999;
92
92
  }
93
93
 
94
- /** Merge rows that share participant + time span + off into one row with combined `days`. */
94
+ /** Merge rows that share participant + time span into one row with combined active `days`. */
95
95
  function mergeOpeningHoursBySlot(rows: UnifiedOpeningHourRow[]) {
96
96
  const map = new Map<string, UnifiedOpeningHourRow>();
97
97
  for (const r of rows) {
98
- const key = [r.member, r.startHour, r.startMinute, r.endHour, r.endMinute, r.off].join("|");
98
+ // Key excludes 'off' because we want to merge ON and OFF records for the same time slot
99
+ const key = [r.member, r.startHour, r.startMinute, r.endHour, r.endMinute].join("|");
99
100
  const existing = map.get(key);
101
+
102
+ // We only want to add the day to the 'days' array if it is NOT marked as OFF.
103
+ // If the whole record was marked as OFF, we still process it to establish the slot,
104
+ // but its days won't be listed as 'active'.
105
+ const activeDaysFromThisRow = r.off ? [] : r.days;
106
+
100
107
  if (!existing) {
101
- map.set(key, { ...r, days: [...r.days] });
108
+ map.set(key, { ...r, days: [...activeDaysFromThisRow], off: false });
102
109
  } else {
103
- const set = new Set([...existing.days, ...r.days]);
110
+ const set = new Set([...existing.days, ...activeDaysFromThisRow]);
104
111
  existing.days = Array.from(set).sort((a, b) => dayOrderIndex(a) - dayOrderIndex(b));
112
+ // If we encounter any record that is NOT off, the whole merged slot is NOT off.
113
+ if (!r.off) existing.off = false;
114
+ }
115
+ }
116
+
117
+ // Final Pass: If a slot has NO active days, it should be marked as off: true
118
+ // (though in Plan V2, these usually just disappear from the UI's 'days' list)
119
+ for (const group of map.values()) {
120
+ if (group.days.length === 0) {
121
+ group.off = true;
105
122
  }
106
123
  }
124
+
107
125
  rows.length = 0;
108
126
  rows.push(...map.values());
109
127
  }
@@ -132,7 +132,10 @@ async function runMembersAndOpeningHoursAfterCalendarSave(calendar: any, calenda
132
132
  endHour: oh.endHour,
133
133
  endMinute: oh.endMinute,
134
134
  off: isOff,
135
- openingHourId: openingHourId,
135
+ // Plan V2 Optimization: Generate a unique ID for EVERY day record.
136
+ // This prevents the backend from deduplicating/overwriting when multiple
137
+ // records for the same participant + slot are sent in one batch.
138
+ openingHourId: newOpeningHourId(),
136
139
  });
137
140
  }
138
141
  }
@@ -141,6 +144,11 @@ async function runMembersAndOpeningHoursAfterCalendarSave(calendar: any, calenda
141
144
  for (const [participantId, payload] of hoursByParticipant.entries()) {
142
145
  if (payload.length === 0) continue;
143
146
 
147
+ // Plan V2 Optimization: Clear existing records for this participant first.
148
+ // This ensures that when we save the new batch (with unique per-day IDs),
149
+ // we don't leak orphaned records or create duplicates during updates.
150
+ await (calendarNode as any).removeParticipantOpeningHours(participantId);
151
+
144
152
  // Use the batch save method (plural)
145
153
  const res = await saveCalendarOpeningHoursBatch(calendarNode, payload);
146
154