@fayz-ai/plugin-agenda 0.1.8 → 0.2.1

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.
Files changed (68) hide show
  1. package/dist/chunk-GAJVIEXM.cjs +870 -0
  2. package/dist/chunk-GAJVIEXM.cjs.map +1 -0
  3. package/dist/chunk-U6VTLOHN.js +867 -0
  4. package/dist/chunk-U6VTLOHN.js.map +1 -0
  5. package/dist/components/AgendaMobileList.d.ts +10 -0
  6. package/dist/components/AgendaMobileList.d.ts.map +1 -0
  7. package/dist/components/AppointmentModal.d.ts +4 -2
  8. package/dist/components/AppointmentModal.d.ts.map +1 -1
  9. package/dist/components/AppointmentPopover.d.ts.map +1 -1
  10. package/dist/components/EventContextMenu.d.ts.map +1 -1
  11. package/dist/config.d.ts +62 -1
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/data/index.d.ts +1 -0
  14. package/dist/data/index.d.ts.map +1 -1
  15. package/dist/data/mock.d.ts +15 -1
  16. package/dist/data/mock.d.ts.map +1 -1
  17. package/dist/data/supabase.d.ts.map +1 -1
  18. package/dist/index.cjs +611 -1157
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +445 -994
  23. package/dist/index.js.map +1 -1
  24. package/dist/locales/en.d.ts.map +1 -1
  25. package/dist/locales/pt-BR.d.ts.map +1 -1
  26. package/dist/public/BookingWidget.d.ts +9 -0
  27. package/dist/public/BookingWidget.d.ts.map +1 -0
  28. package/dist/public/context.d.ts +37 -0
  29. package/dist/public/context.d.ts.map +1 -0
  30. package/dist/public/createPublicBookingPlugin.d.ts +45 -0
  31. package/dist/public/createPublicBookingPlugin.d.ts.map +1 -0
  32. package/dist/public/hooks.d.ts +32 -0
  33. package/dist/public/hooks.d.ts.map +1 -0
  34. package/dist/public/index.cjs +1020 -0
  35. package/dist/public/index.cjs.map +1 -0
  36. package/dist/public/index.d.ts +9 -0
  37. package/dist/public/index.d.ts.map +1 -0
  38. package/dist/public/index.js +1012 -0
  39. package/dist/public/index.js.map +1 -0
  40. package/dist/public/types.d.ts +51 -0
  41. package/dist/public/types.d.ts.map +1 -0
  42. package/dist/store.d.ts +3 -0
  43. package/dist/store.d.ts.map +1 -1
  44. package/dist/types.d.ts +8 -0
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/views/CalendarView.d.ts.map +1 -1
  47. package/package.json +11 -8
  48. package/src/components/AgendaMobileList.tsx +103 -0
  49. package/src/components/AppointmentModal.tsx +86 -9
  50. package/src/components/AppointmentPopover.tsx +20 -10
  51. package/src/components/EventContextMenu.tsx +3 -0
  52. package/src/config.ts +104 -5
  53. package/src/data/index.ts +1 -0
  54. package/src/data/mock.ts +26 -6
  55. package/src/data/supabase.ts +13 -2
  56. package/src/index.ts +11 -3
  57. package/src/integrations/google-calendar/integration.test.ts +1 -1
  58. package/src/locales/en.ts +12 -0
  59. package/src/locales/pt-BR.ts +12 -0
  60. package/src/public/BookingWidget.tsx +892 -0
  61. package/src/public/context.tsx +43 -0
  62. package/src/public/createPublicBookingPlugin.ts +155 -0
  63. package/src/public/hooks.ts +117 -0
  64. package/src/public/index.ts +15 -0
  65. package/src/public/types.ts +63 -0
  66. package/src/store.ts +4 -2
  67. package/src/types.ts +8 -0
  68. package/src/views/CalendarView.tsx +220 -54
@@ -0,0 +1,870 @@
1
+ 'use strict';
2
+
3
+ var chunkSYDBDI3Q_cjs = require('./chunk-SYDBDI3Q.cjs');
4
+ var core = require('@fayz-ai/core');
5
+
6
+ function getTenantId() {
7
+ return chunkSYDBDI3Q_cjs.getAgendaTenantId() ?? core.getActiveTenantId();
8
+ }
9
+ function snakeToCamel(obj) {
10
+ const result = {};
11
+ for (const [key, value] of Object.entries(obj)) {
12
+ result[key.replace(/_([a-z])/g, (_, c) => c.toUpperCase())] = value;
13
+ }
14
+ return result;
15
+ }
16
+ function getClients() {
17
+ const supabase = core.getSupabaseClientOptional();
18
+ if (!supabase) throw new Error("Supabase not initialized");
19
+ return { core: supabase.schema("saas_core"), pub: supabase };
20
+ }
21
+ function mapBooking(row) {
22
+ return snakeToCamel(row);
23
+ }
24
+ function mapSchedule(row) {
25
+ return snakeToCamel(row);
26
+ }
27
+ function addMinutes(iso, minutes) {
28
+ const d = new Date(iso);
29
+ d.setMinutes(d.getMinutes() + minutes);
30
+ return d.toISOString();
31
+ }
32
+ function unique(values) {
33
+ return Array.from(new Set(values.filter((value) => !!value)));
34
+ }
35
+ function indexById(rows) {
36
+ return new Map((rows ?? []).filter((row) => row?.id).map((row) => [row.id, row]));
37
+ }
38
+ function groupBy(rows, getKey) {
39
+ const grouped = /* @__PURE__ */ new Map();
40
+ for (const row of rows) {
41
+ const key = getKey(row);
42
+ if (!key) continue;
43
+ grouped.set(key, [...grouped.get(key) ?? [], row]);
44
+ }
45
+ return grouped;
46
+ }
47
+ function getDurationMinutes(startsAt, endsAt) {
48
+ if (!endsAt) return null;
49
+ return Math.round((new Date(endsAt).getTime() - new Date(startsAt).getTime()) / 6e4);
50
+ }
51
+ function mergeBookings(viewBookings, canonicalBookings) {
52
+ const canonicalIds = new Set(canonicalBookings.map((booking) => booking.id));
53
+ const canonicalOrderIds = new Set(canonicalBookings.map((booking) => booking.orderId).filter(Boolean));
54
+ const merged = [
55
+ ...canonicalBookings,
56
+ ...viewBookings.filter((booking) => {
57
+ if (canonicalIds.has(booking.id)) return false;
58
+ if (booking.orderId && canonicalOrderIds.has(booking.orderId)) return false;
59
+ if (canonicalOrderIds.has(booking.id)) return false;
60
+ return true;
61
+ })
62
+ ];
63
+ return merged.sort((a, b) => new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime());
64
+ }
65
+ async function hydrateCanonicalBookings(core, bookings) {
66
+ const personIds = unique(bookings.flatMap((booking) => [booking.party_id, booking.assignee_id]));
67
+ const locationIds = unique(bookings.map((booking) => booking.location_id));
68
+ const orderIds = unique(bookings.map((booking) => booking.order_id));
69
+ unique(bookings.map((booking) => booking.id));
70
+ const [
71
+ { data: persons, error: personsError },
72
+ { data: locations, error: locationsError },
73
+ { data: orders, error: ordersError },
74
+ { data: items, error: itemsError }
75
+ ] = await Promise.all([
76
+ personIds.length ? core.from("persons").select("id, name, phone, email, avatar_url").in("id", personIds) : Promise.resolve({ data: [], error: null }),
77
+ locationIds.length ? core.from("locations").select("id, name").in("id", locationIds) : Promise.resolve({ data: [], error: null }),
78
+ orderIds.length ? core.from("orders").select("id, total, status").in("id", orderIds) : Promise.resolve({ data: [], error: null }),
79
+ orderIds.length ? core.from("order_items").select("id, order_id, service_id, name, duration_minutes, unit_price, assignee_id, sort_order").in("order_id", orderIds) : Promise.resolve({ data: [], error: null })
80
+ ]);
81
+ if (personsError) throw personsError;
82
+ if (locationsError) throw locationsError;
83
+ if (ordersError) throw ordersError;
84
+ if (itemsError) throw itemsError;
85
+ const personsById = indexById(persons);
86
+ const locationsById = indexById(locations);
87
+ const ordersById = indexById(orders);
88
+ const itemsByOrderId = groupBy(items ?? [], (item) => item.order_id);
89
+ return bookings.map((booking) => {
90
+ const client = booking.party_id ? personsById.get(booking.party_id) : null;
91
+ const professional = booking.assignee_id ? personsById.get(booking.assignee_id) : null;
92
+ const location = booking.location_id ? locationsById.get(booking.location_id) : null;
93
+ const order = booking.order_id ? ordersById.get(booking.order_id) : null;
94
+ const bookingItems = ((booking.order_id ? itemsByOrderId.get(booking.order_id) : null) ?? []).sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0)).map((item) => ({
95
+ id: item.id,
96
+ serviceId: item.service_id,
97
+ name: item.name,
98
+ durationMinutes: item.duration_minutes,
99
+ price: item.unit_price,
100
+ assigneeId: item.assignee_id
101
+ }));
102
+ const itemDuration = bookingItems.reduce((sum, item) => sum + (item.durationMinutes ?? 0), 0);
103
+ return {
104
+ id: booking.id,
105
+ tenantId: booking.tenant_id,
106
+ kind: booking.kind,
107
+ startsAt: booking.starts_at,
108
+ endsAt: booking.ends_at,
109
+ status: booking.status,
110
+ notes: booking.notes,
111
+ orderId: booking.order_id,
112
+ locationId: booking.location_id,
113
+ metadata: booking.metadata ?? {},
114
+ clientId: booking.party_id,
115
+ // Simple events (no party) fall back to the title stored in metadata.
116
+ clientName: client?.name ?? booking.metadata?.title ?? null,
117
+ clientPhone: client?.phone ?? null,
118
+ clientEmail: client?.email ?? null,
119
+ clientAvatarUrl: client?.avatar_url ?? null,
120
+ professionalId: booking.assignee_id,
121
+ professionalName: professional?.name ?? null,
122
+ professionalAvatarUrl: professional?.avatar_url ?? null,
123
+ locationName: location?.name ?? null,
124
+ orderTotal: order?.total ?? null,
125
+ orderStatus: order?.status ?? null,
126
+ services: bookingItems.length ? bookingItems : null,
127
+ totalDurationMinutes: itemDuration || getDurationMinutes(booking.starts_at, booking.ends_at),
128
+ createdAt: booking.created_at,
129
+ updatedAt: booking.updated_at
130
+ };
131
+ });
132
+ }
133
+ async function getCanonicalBookings(core, query) {
134
+ let q = core.from("bookings").select("id, tenant_id, kind, starts_at, ends_at, status, notes, order_id, location_id, metadata, party_id, assignee_id, created_at, updated_at").gte("starts_at", query.dateRange.start).lte("starts_at", query.dateRange.end);
135
+ if (query.kind) q = q.eq("kind", query.kind);
136
+ if (query.statuses?.length) q = q.in("status", query.statuses);
137
+ if (query.professionalIds?.length) q = q.in("assignee_id", query.professionalIds);
138
+ if (query.locationId) q = q.eq("location_id", query.locationId);
139
+ const { data, error } = await q.order("starts_at", { ascending: true });
140
+ if (error) throw error;
141
+ return hydrateCanonicalBookings(core, data ?? []);
142
+ }
143
+ async function getCanonicalBookingById(core, id) {
144
+ const columns = "id, tenant_id, kind, starts_at, ends_at, status, notes, order_id, location_id, metadata, party_id, assignee_id, created_at, updated_at";
145
+ let { data, error } = await core.from("bookings").select(columns).eq("id", id).maybeSingle();
146
+ if (error) throw error;
147
+ if (!data) {
148
+ const fallback = await core.from("bookings").select(columns).eq("order_id", id).maybeSingle();
149
+ if (fallback.error) throw fallback.error;
150
+ data = fallback.data;
151
+ }
152
+ if (!data) return null;
153
+ const [booking] = await hydrateCanonicalBookings(core, [data]);
154
+ return booking ?? null;
155
+ }
156
+ function createSupabaseAgendaProvider() {
157
+ return {
158
+ // v_bookings is in public schema (PostgREST default)
159
+ async getBookings(query) {
160
+ const { core, pub } = getClients();
161
+ let q = pub.from("v_bookings").select("*").gte("starts_at", query.dateRange.start).lte("starts_at", query.dateRange.end);
162
+ if (query.kind) q = q.eq("kind", query.kind);
163
+ if (query.statuses?.length) q = q.in("status", query.statuses);
164
+ if (query.professionalIds?.length) q = q.in("professional_id", query.professionalIds);
165
+ if (query.locationId) q = q.eq("location_id", query.locationId);
166
+ q = q.order("starts_at", { ascending: true });
167
+ const { data, error } = await q;
168
+ if (error) throw error;
169
+ const viewBookings = (data ?? []).map(mapBooking);
170
+ const canonicalBookings = await getCanonicalBookings(core, query);
171
+ return mergeBookings(viewBookings, canonicalBookings);
172
+ },
173
+ async getBookingById(id) {
174
+ const { core, pub } = getClients();
175
+ const { data, error } = await pub.from("v_bookings").select("*").eq("id", id).maybeSingle();
176
+ if (error) throw error;
177
+ if (data) return mapBooking(data);
178
+ return getCanonicalBookingById(core, id);
179
+ },
180
+ async createBooking(input) {
181
+ const { core } = getClients();
182
+ const tenantId = getTenantId();
183
+ if (!tenantId) throw new Error("No active tenant");
184
+ const hasServices = input.services.length > 0;
185
+ const totalDuration = input.services.reduce((sum, s) => sum + s.durationMinutes, 0);
186
+ const totalPrice = input.services.reduce((sum, s) => sum + s.price, 0);
187
+ const endsAt = addMinutes(input.startsAt, totalDuration || 30);
188
+ const serviceNames = input.services.map((s) => s.name).join(", ");
189
+ let contactName;
190
+ if (input.clientId) {
191
+ const { data: person } = await core.from("persons").select("name").eq("id", input.clientId).single();
192
+ contactName = person?.name;
193
+ }
194
+ const eventTitle = input.title?.trim() || void 0;
195
+ const { data: order, error: orderErr } = await core.from("orders").insert({
196
+ tenant_id: tenantId,
197
+ kind: input.kind ?? "appointment",
198
+ status: "scheduled",
199
+ party_id: input.clientId || null,
200
+ assignee_id: input.professionalId,
201
+ location_id: input.locationId ?? null,
202
+ subtotal: totalPrice,
203
+ total: totalPrice,
204
+ notes: input.notes ?? null,
205
+ metadata: {
206
+ source: "agenda",
207
+ ...serviceNames ? { serviceNames, itemsSummary: serviceNames } : {},
208
+ ...contactName ? { contactName } : {},
209
+ ...eventTitle ? { title: eventTitle } : {}
210
+ }
211
+ }).select("id").single();
212
+ if (orderErr) throw orderErr;
213
+ const { data: booking, error: bookingErr } = await core.from("bookings").insert({
214
+ tenant_id: tenantId,
215
+ kind: input.kind ?? "appointment",
216
+ party_id: input.clientId || null,
217
+ assignee_id: input.professionalId,
218
+ location_id: input.locationId ?? null,
219
+ order_id: order.id,
220
+ starts_at: input.startsAt,
221
+ ends_at: endsAt,
222
+ status: "scheduled",
223
+ notes: input.notes ?? null,
224
+ metadata: {
225
+ source: "agenda",
226
+ ...serviceNames ? { serviceNames, itemsSummary: serviceNames } : {},
227
+ ...contactName ? { contactName } : {},
228
+ ...eventTitle ? { title: eventTitle } : {}
229
+ }
230
+ }).select("id").single();
231
+ if (bookingErr) throw bookingErr;
232
+ if (hasServices) {
233
+ const orderItems = input.services.map((s, i) => ({
234
+ order_id: order.id,
235
+ service_id: s.serviceId,
236
+ name: s.name,
237
+ quantity: 1,
238
+ unit_price: s.price,
239
+ total: s.price,
240
+ sort_order: i,
241
+ duration_minutes: s.durationMinutes,
242
+ assignee_id: s.assigneeId ?? input.professionalId
243
+ }));
244
+ const { error: oiErr } = await core.from("order_items").insert(orderItems);
245
+ if (oiErr) throw oiErr;
246
+ }
247
+ const createdBooking = await this.getBookingById(booking.id);
248
+ if (!createdBooking) throw new Error("Created booking did not resolve from agenda read model");
249
+ return createdBooking;
250
+ },
251
+ async updateBooking(id, data) {
252
+ const { core } = getClients();
253
+ const { data: existing, error: existingErr } = await core.from("bookings").select("id, order_id, starts_at, assignee_id, metadata").eq("id", id).single();
254
+ if (existingErr) throw existingErr;
255
+ const updates = {};
256
+ if (data.startsAt) updates.starts_at = data.startsAt;
257
+ if (data.endsAt) updates.ends_at = data.endsAt;
258
+ if (data.title !== void 0) {
259
+ updates.metadata = { ...existing?.metadata ?? {}, title: data.title };
260
+ }
261
+ if (data.status) {
262
+ updates.status = data.status;
263
+ }
264
+ if (data.notes !== void 0) updates.notes = data.notes;
265
+ if (data.professionalId) updates.assignee_id = data.professionalId;
266
+ if (data.clientId) updates.party_id = data.clientId;
267
+ if (data.locationId) updates.location_id = data.locationId;
268
+ if (data.services) {
269
+ const totalDuration = data.services.reduce((s, svc) => s + svc.durationMinutes, 0);
270
+ data.services.reduce((s, svc) => s + svc.price, 0);
271
+ const startsAt = data.startsAt ?? existing?.starts_at;
272
+ if (startsAt) {
273
+ const ends = new Date(new Date(startsAt).getTime() + totalDuration * 6e4);
274
+ updates.ends_at = ends.toISOString();
275
+ }
276
+ }
277
+ const effectiveAssigneeId = data.professionalId ?? existing?.assignee_id ?? null;
278
+ const { error } = await core.from("bookings").update(updates).eq("id", id);
279
+ if (error) throw error;
280
+ const orderId = existing?.order_id;
281
+ if (orderId) {
282
+ const orderUpdates = {};
283
+ if (data.notes !== void 0) orderUpdates.notes = data.notes;
284
+ if (data.professionalId) orderUpdates.assignee_id = data.professionalId;
285
+ if (data.clientId) orderUpdates.party_id = data.clientId;
286
+ if (data.locationId) orderUpdates.location_id = data.locationId;
287
+ if (data.services) {
288
+ const totalPrice = data.services.reduce((s, svc) => s + svc.price, 0);
289
+ orderUpdates.subtotal = totalPrice;
290
+ orderUpdates.total = totalPrice;
291
+ }
292
+ if (Object.keys(orderUpdates).length > 0) {
293
+ const { error: orderErr } = await core.from("orders").update(orderUpdates).eq("id", orderId);
294
+ if (orderErr) throw orderErr;
295
+ }
296
+ if (data.services) {
297
+ await core.from("order_items").delete().eq("order_id", orderId);
298
+ if (data.services.length > 0) {
299
+ const items = data.services.map((svc, idx) => ({
300
+ order_id: orderId,
301
+ service_id: svc.serviceId || null,
302
+ name: svc.name,
303
+ quantity: 1,
304
+ unit_price: svc.price,
305
+ total: svc.price,
306
+ sort_order: idx,
307
+ duration_minutes: svc.durationMinutes,
308
+ assignee_id: svc.assigneeId ?? effectiveAssigneeId
309
+ }));
310
+ const { error: itemErr } = await core.from("order_items").insert(items);
311
+ if (itemErr) throw itemErr;
312
+ }
313
+ }
314
+ }
315
+ return await this.getBookingById(id);
316
+ },
317
+ async deleteBooking(id) {
318
+ const { core, pub } = getClients();
319
+ const { data: booking, error: bookingErr } = await core.from("bookings").select("order_id").eq("id", id).single();
320
+ if (bookingErr) throw bookingErr;
321
+ const orderId = booking?.order_id;
322
+ const { count, error: movementErr } = orderId ? await pub.from("financial_movements").select("id", { count: "exact", head: true }).eq("invoice_id", orderId) : { count: 0, error: null };
323
+ if (movementErr) throw movementErr;
324
+ if (orderId && (count ?? 0) > 0) {
325
+ await pub.from("financial_movements").update({ status: "cancelled" }).eq("invoice_id", orderId);
326
+ await core.from("orders").update({ status: "cancelled" }).eq("id", orderId);
327
+ const { error } = await core.from("bookings").update({ status: "cancelled" }).eq("id", id);
328
+ if (error) throw error;
329
+ } else {
330
+ if (orderId) {
331
+ await pub.from("financial_movements").delete().eq("invoice_id", orderId);
332
+ }
333
+ const { error } = await core.from("bookings").delete().eq("id", id);
334
+ if (error) throw error;
335
+ if (orderId) {
336
+ const { error: orderErr } = await core.from("orders").delete().eq("id", orderId);
337
+ if (orderErr) throw orderErr;
338
+ }
339
+ }
340
+ },
341
+ async updateBookingStatus(id, status) {
342
+ return this.updateBooking(id, { status });
343
+ },
344
+ async checkConflict(params) {
345
+ const { core } = getClients();
346
+ let q = core.from("bookings").select("id", { count: "exact", head: true }).eq("assignee_id", params.assigneeId).eq("kind", "appointment").not("status", "in", '("cancelled","no_show")').not("starts_at", "is", null).lt("starts_at", params.endsAt).gt("ends_at", params.startsAt);
347
+ if (params.excludeBookingId) {
348
+ q = q.neq("id", params.excludeBookingId);
349
+ }
350
+ const { count, error } = await q;
351
+ if (error) throw error;
352
+ return (count ?? 0) > 0;
353
+ },
354
+ async getAvailableSlots(params) {
355
+ const { pub } = getClients();
356
+ const { data, error } = await pub.rpc("get_available_slots", {
357
+ p_tenant_id: getTenantId(),
358
+ p_assignee_id: params.assigneeId,
359
+ p_date: params.date,
360
+ p_duration_minutes: params.durationMinutes,
361
+ p_slot_interval: params.slotInterval ?? 30
362
+ });
363
+ if (error) throw error;
364
+ return (data ?? []).map((row) => ({ start: row.slot_start, end: row.slot_end }));
365
+ },
366
+ async getSchedules(professionalId) {
367
+ const { core } = getClients();
368
+ let q = core.from("schedules").select("*").eq("kind", "working_hours");
369
+ if (professionalId) q = q.eq("assignee_id", professionalId);
370
+ q = q.order("day_of_week").order("starts_at");
371
+ const { data, error } = await q;
372
+ if (error) throw error;
373
+ return (data ?? []).map(mapSchedule);
374
+ },
375
+ async saveSchedule(input) {
376
+ const { core } = getClients();
377
+ const tenantId = getTenantId();
378
+ if (!tenantId) throw new Error("No active tenant");
379
+ const { data, error } = await core.from("schedules").insert({
380
+ tenant_id: tenantId,
381
+ kind: "working_hours",
382
+ assignee_id: input.assigneeId,
383
+ location_id: input.locationId ?? null,
384
+ day_of_week: input.dayOfWeek ?? null,
385
+ specific_date: input.specificDate ?? null,
386
+ starts_at: input.startsAt,
387
+ ends_at: input.endsAt,
388
+ is_active: input.isActive !== false,
389
+ metadata: input.metadata ?? {}
390
+ }).select("*").single();
391
+ if (error) throw error;
392
+ return mapSchedule(data);
393
+ },
394
+ async deleteSchedule(id) {
395
+ const { core } = getClients();
396
+ const { error } = await core.from("schedules").delete().eq("id", id);
397
+ if (error) throw error;
398
+ },
399
+ async getProfessionals() {
400
+ const { core } = getClients();
401
+ const { data, error } = await core.from("persons").select("id, name, avatar_url, is_active").eq("kind", "staff").eq("is_active", true).order("name");
402
+ if (error) throw error;
403
+ return (data ?? []).map((p) => ({
404
+ id: p.id,
405
+ name: p.name,
406
+ avatarUrl: p.avatar_url,
407
+ locationId: null,
408
+ locationName: null,
409
+ isActive: p.is_active
410
+ }));
411
+ },
412
+ async getLocations() {
413
+ const { core } = getClients();
414
+ const { data, error } = await core.from("locations").select("id, name").eq("is_active", true).order("name");
415
+ if (error) throw error;
416
+ return (data ?? []).map((l) => ({ id: l.id, name: l.name }));
417
+ },
418
+ async getConfirmationsPending(daysAhead = 2) {
419
+ const { pub } = getClients();
420
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
421
+ const cutoff = /* @__PURE__ */ new Date();
422
+ cutoff.setDate(cutoff.getDate() + daysAhead);
423
+ const { data, error } = await pub.from("v_bookings").select("*").eq("status", "scheduled").gte("starts_at", now2).lte("starts_at", cutoff.toISOString()).order("starts_at");
424
+ if (error) throw error;
425
+ return (data ?? []).map(mapBooking);
426
+ },
427
+ async sendConfirmation(bookingId, channel) {
428
+ const { core } = getClients();
429
+ const { data: booking } = await core.from("bookings").select("metadata").eq("id", bookingId).single();
430
+ const metadata = booking?.metadata ?? {};
431
+ const confirmations = metadata.confirmations ?? [];
432
+ confirmations.push({ channel, sentAt: (/* @__PURE__ */ new Date()).toISOString(), status: "sent" });
433
+ await core.from("bookings").update({ metadata: { ...metadata, confirmations } }).eq("id", bookingId);
434
+ },
435
+ async completeBooking(bookingId) {
436
+ const { core } = getClients();
437
+ const { data: existing, error: existingErr } = await core.from("bookings").select("order_id").eq("id", bookingId).single();
438
+ if (existingErr) throw existingErr;
439
+ const { error } = await core.from("bookings").update({ status: "completed" }).eq("id", bookingId);
440
+ if (error) throw error;
441
+ const orderId = existing?.order_id;
442
+ const booking = await this.getBookingById(bookingId);
443
+ if (!booking) throw new Error("Booking not found");
444
+ return { booking, orderId: orderId ?? bookingId };
445
+ }
446
+ };
447
+ }
448
+
449
+ // src/data/mock.ts
450
+ var nextId = 1;
451
+ function uid() {
452
+ return `mock-${nextId++}`;
453
+ }
454
+ function now() {
455
+ return (/* @__PURE__ */ new Date()).toISOString();
456
+ }
457
+ function addMinutes2(date, minutes) {
458
+ const d = new Date(date);
459
+ d.setMinutes(d.getMinutes() + minutes);
460
+ return d.toISOString();
461
+ }
462
+ function timeOverlaps(aStart, aEnd, bStart, bEnd) {
463
+ return aStart < bEnd && aEnd > bStart;
464
+ }
465
+ var SAMPLE_PROFESSIONALS = [
466
+ { id: "prof-1", name: "Ana Silva", avatarUrl: null, locationId: "loc-1", locationName: "Main Branch", isActive: true },
467
+ { id: "prof-2", name: "Carlos Santos", avatarUrl: null, locationId: "loc-1", locationName: "Main Branch", isActive: true },
468
+ { id: "prof-3", name: "Maria Oliveira", avatarUrl: null, locationId: "loc-1", locationName: "Main Branch", isActive: true }
469
+ ];
470
+ function createSampleBookings() {
471
+ const today = /* @__PURE__ */ new Date();
472
+ const year = today.getFullYear();
473
+ const month = today.getMonth();
474
+ const day = today.getDate();
475
+ function dt(hour, minute = 0) {
476
+ return new Date(year, month, day, hour, minute).toISOString();
477
+ }
478
+ return [
479
+ {
480
+ id: "bk-1",
481
+ tenantId: "mock",
482
+ kind: "appointment",
483
+ startsAt: dt(9, 0),
484
+ endsAt: dt(10, 30),
485
+ status: "confirmed",
486
+ notes: null,
487
+ orderId: "ord-1",
488
+ locationId: "loc-1",
489
+ metadata: {},
490
+ clientId: "cl-1",
491
+ clientName: "Sarah Johnson",
492
+ clientPhone: "(11) 99999-1111",
493
+ clientEmail: "sarah@email.com",
494
+ clientAvatarUrl: null,
495
+ professionalId: "prof-1",
496
+ professionalName: "Ana Silva",
497
+ professionalAvatarUrl: null,
498
+ locationName: "Main Branch",
499
+ orderTotal: 250,
500
+ orderStatus: "draft",
501
+ services: [{ id: "bi-1", serviceId: "svc-1", name: "Balayage", durationMinutes: 90, price: 250, assigneeId: "prof-1" }],
502
+ totalDurationMinutes: 90,
503
+ createdAt: now(),
504
+ updatedAt: now()
505
+ },
506
+ {
507
+ id: "bk-2",
508
+ tenantId: "mock",
509
+ kind: "appointment",
510
+ startsAt: dt(10, 0),
511
+ endsAt: dt(11, 0),
512
+ status: "scheduled",
513
+ notes: null,
514
+ orderId: "ord-2",
515
+ locationId: "loc-1",
516
+ metadata: {},
517
+ clientId: "cl-2",
518
+ clientName: "Emily Carter",
519
+ clientPhone: "(11) 99999-2222",
520
+ clientEmail: "emily@email.com",
521
+ clientAvatarUrl: null,
522
+ professionalId: "prof-2",
523
+ professionalName: "Carlos Santos",
524
+ professionalAvatarUrl: null,
525
+ locationName: "Main Branch",
526
+ orderTotal: 80,
527
+ orderStatus: "draft",
528
+ services: [{ id: "bi-2", serviceId: "svc-2", name: "Haircut", durationMinutes: 60, price: 80, assigneeId: "prof-2" }],
529
+ totalDurationMinutes: 60,
530
+ createdAt: now(),
531
+ updatedAt: now()
532
+ },
533
+ {
534
+ id: "bk-3",
535
+ tenantId: "mock",
536
+ kind: "appointment",
537
+ startsAt: dt(14, 0),
538
+ endsAt: dt(15, 0),
539
+ status: "confirmed",
540
+ notes: "First visit",
541
+ orderId: "ord-3",
542
+ locationId: "loc-1",
543
+ metadata: {},
544
+ clientId: "cl-3",
545
+ clientName: "Rachel Kim",
546
+ clientPhone: "(11) 99999-3333",
547
+ clientEmail: null,
548
+ clientAvatarUrl: null,
549
+ professionalId: "prof-3",
550
+ professionalName: "Maria Oliveira",
551
+ professionalAvatarUrl: null,
552
+ locationName: "Main Branch",
553
+ orderTotal: 120,
554
+ orderStatus: "draft",
555
+ services: [{ id: "bi-3", serviceId: "svc-3", name: "Facial Treatment", durationMinutes: 60, price: 120, assigneeId: "prof-3" }],
556
+ totalDurationMinutes: 60,
557
+ createdAt: now(),
558
+ updatedAt: now()
559
+ },
560
+ {
561
+ id: "bk-4",
562
+ tenantId: "mock",
563
+ kind: "appointment",
564
+ startsAt: dt(11, 0),
565
+ endsAt: dt(12, 0),
566
+ status: "scheduled",
567
+ notes: null,
568
+ orderId: "ord-4",
569
+ locationId: "loc-1",
570
+ metadata: {},
571
+ clientId: "cl-4",
572
+ clientName: "Jessica Lima",
573
+ clientPhone: "(11) 99999-4444",
574
+ clientEmail: null,
575
+ clientAvatarUrl: null,
576
+ professionalId: "prof-1",
577
+ professionalName: "Ana Silva",
578
+ professionalAvatarUrl: null,
579
+ locationName: "Main Branch",
580
+ orderTotal: 60,
581
+ orderStatus: "draft",
582
+ services: [{ id: "bi-4", serviceId: "svc-4", name: "Manicure", durationMinutes: 60, price: 60, assigneeId: "prof-1" }],
583
+ totalDurationMinutes: 60,
584
+ createdAt: now(),
585
+ updatedAt: now()
586
+ },
587
+ {
588
+ id: "bk-5",
589
+ tenantId: "mock",
590
+ kind: "appointment",
591
+ startsAt: dt(15, 30),
592
+ endsAt: dt(17, 0),
593
+ status: "confirmed",
594
+ notes: null,
595
+ orderId: "ord-5",
596
+ locationId: "loc-1",
597
+ metadata: {},
598
+ clientId: "cl-5",
599
+ clientName: "Amanda Wright",
600
+ clientPhone: "(11) 99999-5555",
601
+ clientEmail: "amanda@email.com",
602
+ clientAvatarUrl: null,
603
+ professionalId: "prof-2",
604
+ professionalName: "Carlos Santos",
605
+ professionalAvatarUrl: null,
606
+ locationName: "Main Branch",
607
+ orderTotal: 350,
608
+ orderStatus: "draft",
609
+ services: [
610
+ { id: "bi-5", serviceId: "svc-1", name: "Highlights", durationMinutes: 60, price: 200, assigneeId: "prof-2" },
611
+ { id: "bi-6", serviceId: "svc-2", name: "Haircut", durationMinutes: 30, price: 150, assigneeId: "prof-2" }
612
+ ],
613
+ totalDurationMinutes: 90,
614
+ createdAt: now(),
615
+ updatedAt: now()
616
+ }
617
+ ];
618
+ }
619
+ function createSampleSchedules() {
620
+ const schedules = [];
621
+ for (const prof of SAMPLE_PROFESSIONALS) {
622
+ for (let day = 1; day <= 5; day++) {
623
+ schedules.push({
624
+ id: uid(),
625
+ tenantId: "mock",
626
+ kind: "working_hours",
627
+ assigneeId: prof.id,
628
+ locationId: prof.locationId,
629
+ dayOfWeek: day,
630
+ specificDate: null,
631
+ startsAt: "08:00",
632
+ endsAt: "12:00",
633
+ isActive: true,
634
+ metadata: {}
635
+ });
636
+ schedules.push({
637
+ id: uid(),
638
+ tenantId: "mock",
639
+ kind: "working_hours",
640
+ assigneeId: prof.id,
641
+ locationId: prof.locationId,
642
+ dayOfWeek: day,
643
+ specificDate: null,
644
+ startsAt: "13:00",
645
+ endsAt: "18:00",
646
+ isActive: true,
647
+ metadata: {}
648
+ });
649
+ }
650
+ schedules.push({
651
+ id: uid(),
652
+ tenantId: "mock",
653
+ kind: "working_hours",
654
+ assigneeId: prof.id,
655
+ locationId: prof.locationId,
656
+ dayOfWeek: 6,
657
+ specificDate: null,
658
+ startsAt: "08:00",
659
+ endsAt: "14:00",
660
+ isActive: true,
661
+ metadata: {}
662
+ });
663
+ }
664
+ return schedules;
665
+ }
666
+ function createMockAgendaProvider(options) {
667
+ const seed = options?.seed;
668
+ const bookings = seed?.bookings ? [...seed.bookings] : createSampleBookings();
669
+ const schedules = seed?.schedules ? [...seed.schedules] : createSampleSchedules();
670
+ const professionals = seed?.professionals ? [...seed.professionals] : [...SAMPLE_PROFESSIONALS];
671
+ return {
672
+ async getLocations() {
673
+ return [];
674
+ },
675
+ async getBookings(query) {
676
+ return bookings.filter((b) => {
677
+ if (query.kind && b.kind !== query.kind) return false;
678
+ if (query.statuses?.length && !query.statuses.includes(b.status)) return false;
679
+ if (query.professionalIds?.length && !query.professionalIds.includes(b.professionalId ?? "")) return false;
680
+ if (query.locationId && b.locationId !== query.locationId) return false;
681
+ if (query.dateRange) {
682
+ if (b.startsAt > query.dateRange.end || (b.endsAt ?? b.startsAt) < query.dateRange.start) return false;
683
+ }
684
+ return true;
685
+ });
686
+ },
687
+ async getBookingById(id) {
688
+ return bookings.find((b) => b.id === id) ?? null;
689
+ },
690
+ async createBooking(input) {
691
+ const totalDuration = input.services.reduce((sum, s) => sum + s.durationMinutes, 0);
692
+ const totalPrice = input.services.reduce((sum, s) => sum + s.price, 0);
693
+ const booking = {
694
+ id: uid(),
695
+ tenantId: "mock",
696
+ kind: input.kind ?? "appointment",
697
+ startsAt: input.startsAt,
698
+ endsAt: addMinutes2(input.startsAt, totalDuration || 30),
699
+ status: "scheduled",
700
+ notes: input.notes ?? null,
701
+ orderId: input.services.length > 0 ? uid() : null,
702
+ locationId: input.locationId ?? null,
703
+ metadata: {},
704
+ clientId: input.clientId,
705
+ clientName: input.title?.trim() || "New Client",
706
+ clientPhone: null,
707
+ clientEmail: null,
708
+ clientAvatarUrl: null,
709
+ professionalId: input.professionalId,
710
+ professionalName: professionals.find((p) => p.id === input.professionalId)?.name ?? "Unknown",
711
+ professionalAvatarUrl: null,
712
+ locationName: null,
713
+ orderTotal: totalPrice,
714
+ orderStatus: "draft",
715
+ services: input.services.map((s) => ({
716
+ id: uid(),
717
+ serviceId: s.serviceId,
718
+ name: s.name,
719
+ durationMinutes: s.durationMinutes,
720
+ price: s.price,
721
+ assigneeId: s.assigneeId ?? input.professionalId
722
+ })),
723
+ totalDurationMinutes: totalDuration,
724
+ createdAt: now(),
725
+ updatedAt: now()
726
+ };
727
+ bookings.push(booking);
728
+ return booking;
729
+ },
730
+ async updateBooking(id, data) {
731
+ const idx = bookings.findIndex((b) => b.id === id);
732
+ if (idx === -1) throw new Error("Booking not found");
733
+ const existing = bookings[idx];
734
+ const serviceUpdates = {};
735
+ if (data.services) {
736
+ serviceUpdates.services = data.services.map((s) => ({
737
+ id: uid(),
738
+ serviceId: s.serviceId,
739
+ name: s.name,
740
+ durationMinutes: s.durationMinutes,
741
+ price: s.price,
742
+ assigneeId: s.assigneeId ?? existing.professionalId ?? ""
743
+ }));
744
+ serviceUpdates.totalDurationMinutes = data.services.reduce((sum, s) => sum + s.durationMinutes, 0);
745
+ serviceUpdates.orderTotal = data.services.reduce((sum, s) => sum + s.price, 0);
746
+ }
747
+ const updated = {
748
+ ...existing,
749
+ ...data.startsAt && { startsAt: data.startsAt },
750
+ ...data.endsAt && { endsAt: data.endsAt },
751
+ ...data.title !== void 0 && { clientName: data.title },
752
+ ...data.status && { status: data.status },
753
+ ...data.notes !== void 0 && { notes: data.notes ?? null },
754
+ ...data.professionalId && { professionalId: data.professionalId },
755
+ ...data.clientId && { clientId: data.clientId },
756
+ ...data.locationId && { locationId: data.locationId },
757
+ ...serviceUpdates,
758
+ updatedAt: now()
759
+ };
760
+ bookings[idx] = updated;
761
+ return updated;
762
+ },
763
+ async deleteBooking(id) {
764
+ const idx = bookings.findIndex((b) => b.id === id);
765
+ if (idx !== -1) bookings.splice(idx, 1);
766
+ },
767
+ async updateBookingStatus(id, status) {
768
+ const idx = bookings.findIndex((b) => b.id === id);
769
+ if (idx === -1) throw new Error("Booking not found");
770
+ bookings[idx] = { ...bookings[idx], status, updatedAt: now() };
771
+ return bookings[idx];
772
+ },
773
+ async checkConflict(params) {
774
+ return bookings.some((b) => {
775
+ if (params.excludeBookingId && b.id === params.excludeBookingId) return false;
776
+ if (b.professionalId !== params.assigneeId) return false;
777
+ if (b.status === "cancelled" || b.status === "no_show") return false;
778
+ return timeOverlaps(b.startsAt, b.endsAt ?? b.startsAt, params.startsAt, params.endsAt);
779
+ });
780
+ },
781
+ async getAvailableSlots(params) {
782
+ const [yy, mm, dd] = params.date.split("-").map(Number);
783
+ const date = new Date(yy, (mm ?? 1) - 1, dd ?? 1);
784
+ const dayOfWeek = date.getDay();
785
+ const interval = params.slotInterval ?? 30;
786
+ const daySchedules = schedules.filter(
787
+ (s) => s.assigneeId === params.assigneeId && s.isActive && (s.dayOfWeek === dayOfWeek || s.specificDate === params.date)
788
+ );
789
+ const slots = [];
790
+ for (const sched of daySchedules) {
791
+ const [startH, startM] = sched.startsAt.split(":").map(Number);
792
+ const [endH, endM] = sched.endsAt.split(":").map(Number);
793
+ const schedStart = new Date(date);
794
+ schedStart.setHours(startH, startM, 0, 0);
795
+ const schedEnd = new Date(date);
796
+ schedEnd.setHours(endH, endM, 0, 0);
797
+ let slotStart = new Date(schedStart);
798
+ while (true) {
799
+ const slotEnd = new Date(slotStart.getTime() + params.durationMinutes * 6e4);
800
+ if (slotEnd > schedEnd) break;
801
+ const startIso = slotStart.toISOString();
802
+ const endIso = slotEnd.toISOString();
803
+ const hasConflict = bookings.some((b) => {
804
+ if (b.professionalId !== params.assigneeId) return false;
805
+ if (b.status === "cancelled" || b.status === "no_show") return false;
806
+ return timeOverlaps(b.startsAt, b.endsAt ?? b.startsAt, startIso, endIso);
807
+ });
808
+ if (!hasConflict) {
809
+ slots.push({ start: startIso, end: endIso });
810
+ }
811
+ slotStart = new Date(slotStart.getTime() + interval * 6e4);
812
+ }
813
+ }
814
+ return slots;
815
+ },
816
+ async getSchedules(professionalId) {
817
+ if (professionalId) return schedules.filter((s) => s.assigneeId === professionalId);
818
+ return [...schedules];
819
+ },
820
+ async saveSchedule(input) {
821
+ const schedule = {
822
+ id: uid(),
823
+ tenantId: "mock",
824
+ kind: "working_hours",
825
+ assigneeId: input.assigneeId,
826
+ locationId: input.locationId ?? null,
827
+ dayOfWeek: input.dayOfWeek ?? null,
828
+ specificDate: input.specificDate ?? null,
829
+ startsAt: input.startsAt,
830
+ endsAt: input.endsAt,
831
+ isActive: input.isActive !== false,
832
+ metadata: input.metadata ?? {}
833
+ };
834
+ schedules.push(schedule);
835
+ return schedule;
836
+ },
837
+ async deleteSchedule(id) {
838
+ const idx = schedules.findIndex((s) => s.id === id);
839
+ if (idx !== -1) schedules.splice(idx, 1);
840
+ },
841
+ async getProfessionals() {
842
+ return [...professionals];
843
+ },
844
+ async getConfirmationsPending(daysAhead = 2) {
845
+ const cutoff = /* @__PURE__ */ new Date();
846
+ cutoff.setDate(cutoff.getDate() + daysAhead);
847
+ return bookings.filter(
848
+ (b) => b.status === "scheduled" && new Date(b.startsAt) <= cutoff && new Date(b.startsAt) >= /* @__PURE__ */ new Date()
849
+ );
850
+ },
851
+ async sendConfirmation(bookingId, channel) {
852
+ const booking = bookings.find((b) => b.id === bookingId);
853
+ if (!booking) throw new Error("Booking not found");
854
+ const confirmations = booking.metadata.confirmations ?? [];
855
+ confirmations.push({ channel, sentAt: now(), status: "sent" });
856
+ booking.metadata = { ...booking.metadata, confirmations };
857
+ },
858
+ async completeBooking(bookingId) {
859
+ const idx = bookings.findIndex((b) => b.id === bookingId);
860
+ if (idx === -1) throw new Error("Booking not found");
861
+ bookings[idx] = { ...bookings[idx], status: "completed", updatedAt: now() };
862
+ return { booking: bookings[idx], orderId: bookings[idx].orderId ?? "" };
863
+ }
864
+ };
865
+ }
866
+
867
+ exports.createMockAgendaProvider = createMockAgendaProvider;
868
+ exports.createSupabaseAgendaProvider = createSupabaseAgendaProvider;
869
+ //# sourceMappingURL=chunk-GAJVIEXM.cjs.map
870
+ //# sourceMappingURL=chunk-GAJVIEXM.cjs.map