@blazeo.com/appointment-client 1.0.9 → 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.
- package/blazeo.com-appointment-client-1.0.11.tgz +0 -0
- package/dist/calendar/buildUnifiedCalendarView.js +19 -4
- package/dist/calendar/calendarCreation.js +43 -19
- package/package.json +1 -1
- package/src/calendar/buildUnifiedCalendarView.ts +22 -4
- package/src/calendar/calendarCreation.ts +52 -20
- package/blazeo.com-appointment-client-1.0.9.tgz +0 -0
|
Binary file
|
|
@@ -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
|
|
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
|
-
|
|
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: [...
|
|
78
|
+
map.set(key, { ...r, days: [...activeDaysFromThisRow], off: false });
|
|
74
79
|
}
|
|
75
80
|
else {
|
|
76
|
-
const set = new Set([...existing.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;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getSnapshot } from "mobx-state-tree";
|
|
2
|
-
import { addParticipantToCalendar,
|
|
2
|
+
import { addParticipantToCalendar, saveCalendarOpeningHoursBatch } from "./blazeoCalendarRelationMethods.js";
|
|
3
3
|
import { createCalendarAsync, updateCalendarAsync, deleteCalendarAsync } from "./createCalendar.js";
|
|
4
4
|
function isFailureStatus(res) {
|
|
5
5
|
return res.status !== "success" && res.status !== "Success";
|
|
@@ -90,8 +90,10 @@ async function runMembersAndOpeningHoursAfterCalendarSave(calendar, calendarNode
|
|
|
90
90
|
}
|
|
91
91
|
membersAdded += 1;
|
|
92
92
|
}
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
// 2. Save Opening Hours (Plan V2: Grouped by participant with explicit off-days per slot)
|
|
94
|
+
const openingHours = calendar.openingHours ?? [];
|
|
95
|
+
const hoursByParticipant = new Map();
|
|
96
|
+
for (const oh of openingHours) {
|
|
95
97
|
const participantId = resolveParticipantIdForOpeningHour(oh);
|
|
96
98
|
if (!participantId) {
|
|
97
99
|
return {
|
|
@@ -99,8 +101,17 @@ async function runMembersAndOpeningHoursAfterCalendarSave(calendar, calendarNode
|
|
|
99
101
|
error: `Opening hour id ${oh.id}: participantId is required.`,
|
|
100
102
|
};
|
|
101
103
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
+
if (!hoursByParticipant.has(participantId)) {
|
|
105
|
+
hoursByParticipant.set(participantId, []);
|
|
106
|
+
}
|
|
107
|
+
// Plan V2 Logic: For every opening hour object, generate EXACTLY 7 entries (days 0-6).
|
|
108
|
+
// If the day is in oh.days, it's ON. If not, it's OFF.
|
|
109
|
+
const activeDays = oh.days ?? [];
|
|
110
|
+
const openingHourId = oh.openingHourId?.trim() || newOpeningHourId();
|
|
111
|
+
for (let day = 0; day <= 6; day++) {
|
|
112
|
+
const isIncluded = activeDays.includes(day);
|
|
113
|
+
const isOff = isIncluded ? !!oh.off : true; // If not in days array, it's explicitly OFF
|
|
114
|
+
hoursByParticipant.get(participantId)?.push({
|
|
104
115
|
calendarId: calendarIdStr,
|
|
105
116
|
participantId,
|
|
106
117
|
day,
|
|
@@ -108,22 +119,35 @@ async function runMembersAndOpeningHoursAfterCalendarSave(calendar, calendarNode
|
|
|
108
119
|
startMinute: oh.startMinute,
|
|
109
120
|
endHour: oh.endHour,
|
|
110
121
|
endMinute: oh.endMinute,
|
|
111
|
-
off:
|
|
112
|
-
|
|
122
|
+
off: isOff,
|
|
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(),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
let openingHoursSaved = 0;
|
|
131
|
+
for (const [participantId, payload] of hoursByParticipant.entries()) {
|
|
132
|
+
if (payload.length === 0)
|
|
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);
|
|
138
|
+
// Use the batch save method (plural)
|
|
139
|
+
const res = await saveCalendarOpeningHoursBatch(calendarNode, payload);
|
|
140
|
+
if (isFailureStatus(res)) {
|
|
141
|
+
const msg = res.message ??
|
|
142
|
+
(typeof res.data === "string" ? res.data : undefined) ??
|
|
143
|
+
JSON.stringify(res);
|
|
144
|
+
return {
|
|
145
|
+
ok: false,
|
|
146
|
+
error: `saveOpeningHours batch failed for participant ${participantId}: ${msg}`,
|
|
147
|
+
apiResponse: res,
|
|
113
148
|
};
|
|
114
|
-
const res = await saveCalendarOpeningHour(calendarNode, payload);
|
|
115
|
-
if (isFailureStatus(res)) {
|
|
116
|
-
const msg = res.message ??
|
|
117
|
-
(typeof res.data === "string" ? res.data : undefined) ??
|
|
118
|
-
JSON.stringify(res);
|
|
119
|
-
return {
|
|
120
|
-
ok: false,
|
|
121
|
-
error: `saveOpeningHour failed (opening hour ${oh.id}, day ${day}): ${msg}`,
|
|
122
|
-
apiResponse: res,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
openingHoursSaved += 1;
|
|
126
149
|
}
|
|
150
|
+
openingHoursSaved += payload.length;
|
|
127
151
|
}
|
|
128
152
|
return {
|
|
129
153
|
...baseSuccess,
|
package/package.json
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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: [...
|
|
108
|
+
map.set(key, { ...r, days: [...activeDaysFromThisRow], off: false });
|
|
102
109
|
} else {
|
|
103
|
-
const set = new Set([...existing.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
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getSnapshot } from "mobx-state-tree";
|
|
2
|
-
import { addParticipantToCalendar, saveCalendarOpeningHour } from "./blazeoCalendarRelationMethods.js";
|
|
2
|
+
import { addParticipantToCalendar, saveCalendarOpeningHour, saveCalendarOpeningHoursBatch } from "./blazeoCalendarRelationMethods.js";
|
|
3
3
|
import { createCalendarAsync, updateCalendarAsync, deleteCalendarAsync } from "./createCalendar.js";
|
|
4
4
|
|
|
5
5
|
function isFailureStatus(res: any) {
|
|
@@ -97,8 +97,11 @@ async function runMembersAndOpeningHoursAfterCalendarSave(calendar: any, calenda
|
|
|
97
97
|
membersAdded += 1;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
// 2. Save Opening Hours (Plan V2: Grouped by participant with explicit off-days per slot)
|
|
101
|
+
const openingHours = calendar.openingHours ?? [];
|
|
102
|
+
const hoursByParticipant = new Map<string, any[]>();
|
|
103
|
+
|
|
104
|
+
for (const oh of openingHours) {
|
|
102
105
|
const participantId = resolveParticipantIdForOpeningHour(oh);
|
|
103
106
|
if (!participantId) {
|
|
104
107
|
return {
|
|
@@ -106,8 +109,21 @@ async function runMembersAndOpeningHoursAfterCalendarSave(calendar: any, calenda
|
|
|
106
109
|
error: `Opening hour id ${oh.id}: participantId is required.`,
|
|
107
110
|
};
|
|
108
111
|
}
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
|
|
113
|
+
if (!hoursByParticipant.has(participantId)) {
|
|
114
|
+
hoursByParticipant.set(participantId, []);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Plan V2 Logic: For every opening hour object, generate EXACTLY 7 entries (days 0-6).
|
|
118
|
+
// If the day is in oh.days, it's ON. If not, it's OFF.
|
|
119
|
+
const activeDays = oh.days ?? [];
|
|
120
|
+
const openingHourId = oh.openingHourId?.trim() || newOpeningHourId();
|
|
121
|
+
|
|
122
|
+
for (let day = 0; day <= 6; day++) {
|
|
123
|
+
const isIncluded = activeDays.includes(day);
|
|
124
|
+
const isOff = isIncluded ? !!oh.off : true; // If not in days array, it's explicitly OFF
|
|
125
|
+
|
|
126
|
+
hoursByParticipant.get(participantId)?.push({
|
|
111
127
|
calendarId: calendarIdStr,
|
|
112
128
|
participantId,
|
|
113
129
|
day,
|
|
@@ -115,23 +131,39 @@ async function runMembersAndOpeningHoursAfterCalendarSave(calendar: any, calenda
|
|
|
115
131
|
startMinute: oh.startMinute,
|
|
116
132
|
endHour: oh.endHour,
|
|
117
133
|
endMinute: oh.endMinute,
|
|
118
|
-
off:
|
|
119
|
-
|
|
134
|
+
off: isOff,
|
|
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(),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let openingHoursSaved = 0;
|
|
144
|
+
for (const [participantId, payload] of hoursByParticipant.entries()) {
|
|
145
|
+
if (payload.length === 0) continue;
|
|
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
|
+
|
|
152
|
+
// Use the batch save method (plural)
|
|
153
|
+
const res = await saveCalendarOpeningHoursBatch(calendarNode, payload);
|
|
154
|
+
|
|
155
|
+
if (isFailureStatus(res)) {
|
|
156
|
+
const msg =
|
|
157
|
+
res.message ??
|
|
158
|
+
(typeof res.data === "string" ? res.data : undefined) ??
|
|
159
|
+
JSON.stringify(res);
|
|
160
|
+
return {
|
|
161
|
+
ok: false,
|
|
162
|
+
error: `saveOpeningHours batch failed for participant ${participantId}: ${msg}`,
|
|
163
|
+
apiResponse: res,
|
|
120
164
|
};
|
|
121
|
-
const res = await saveCalendarOpeningHour(calendarNode, payload);
|
|
122
|
-
if (isFailureStatus(res)) {
|
|
123
|
-
const msg =
|
|
124
|
-
res.message ??
|
|
125
|
-
(typeof res.data === "string" ? res.data : undefined) ??
|
|
126
|
-
JSON.stringify(res);
|
|
127
|
-
return {
|
|
128
|
-
ok: false,
|
|
129
|
-
error: `saveOpeningHour failed (opening hour ${oh.id}, day ${day}): ${msg}`,
|
|
130
|
-
apiResponse: res,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
openingHoursSaved += 1;
|
|
134
165
|
}
|
|
166
|
+
openingHoursSaved += payload.length;
|
|
135
167
|
}
|
|
136
168
|
|
|
137
169
|
return {
|
|
Binary file
|