@fayz-ai/plugin-agenda 0.9.0 → 0.9.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 (153) hide show
  1. package/README.md +3 -3
  2. package/dist/bridges/create-financial-bridge.d.ts +1 -1
  3. package/dist/bridges/create-financial-bridge.d.ts.map +1 -1
  4. package/dist/bridges/financial-bridge.d.ts.map +1 -0
  5. package/dist/{chunk-Q2PAWBPR.js → chunk-76ZOHRBM.js} +2 -2
  6. package/dist/{chunk-Q2PAWBPR.js.map → chunk-76ZOHRBM.js.map} +1 -1
  7. package/dist/components/PaymentStatusBadge.d.ts +1 -1
  8. package/dist/components/PaymentStatusBadge.d.ts.map +1 -1
  9. package/dist/config.d.ts +2 -2
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/{AgendaContext.d.ts → context.d.ts} +1 -1
  12. package/dist/context.d.ts.map +1 -0
  13. package/dist/data/registries.d.ts.map +1 -0
  14. package/dist/index.d.ts +2 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +170 -7
  17. package/dist/index.js.map +1 -1
  18. package/dist/lib/message-events.d.ts +12 -0
  19. package/dist/lib/message-events.d.ts.map +1 -0
  20. package/dist/public/{data.d.ts → data/index.d.ts} +4 -4
  21. package/dist/public/data/index.d.ts.map +1 -0
  22. package/dist/public/{data.supabase.d.ts → data/supabase.d.ts} +2 -2
  23. package/dist/public/data/supabase.d.ts.map +1 -0
  24. package/dist/public/{hooks.d.ts → hooks/index.d.ts} +4 -4
  25. package/dist/public/hooks/index.d.ts.map +1 -0
  26. package/dist/public/index.d.ts +4 -4
  27. package/dist/public/index.d.ts.map +1 -1
  28. package/dist/public/index.js +3 -3
  29. package/dist/public/index.js.map +1 -1
  30. package/dist/public/{format.d.ts → lib/format.d.ts} +1 -1
  31. package/dist/public/lib/format.d.ts.map +1 -0
  32. package/dist/public/views/BookingWidget.d.ts.map +1 -0
  33. package/dist/public/{steps → views/steps}/ContactStep.d.ts +1 -1
  34. package/dist/public/views/steps/ContactStep.d.ts.map +1 -0
  35. package/dist/public/{steps → views/steps}/DateTimeStep.d.ts +3 -3
  36. package/dist/public/views/steps/DateTimeStep.d.ts.map +1 -0
  37. package/dist/public/{steps → views/steps}/OverviewStep.d.ts +1 -1
  38. package/dist/public/views/steps/OverviewStep.d.ts.map +1 -0
  39. package/dist/public/{steps → views/steps}/PaymentStep.d.ts +1 -1
  40. package/dist/public/views/steps/PaymentStep.d.ts.map +1 -0
  41. package/dist/store.d.ts +1 -1
  42. package/dist/store.d.ts.map +1 -1
  43. package/dist/styles/calendar-styles.d.ts.map +1 -0
  44. package/dist/types.d.ts +1 -1
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/{AgendaPage.d.ts → views/AgendaPage.d.ts} +3 -3
  47. package/dist/views/AgendaPage.d.ts.map +1 -0
  48. package/package.json +6 -9
  49. package/dist/AgendaContext.d.ts.map +0 -1
  50. package/dist/AgendaPage.d.ts.map +0 -1
  51. package/dist/calendar-styles.d.ts.map +0 -1
  52. package/dist/financial-bridge.d.ts.map +0 -1
  53. package/dist/public/BookingWidget.d.ts.map +0 -1
  54. package/dist/public/data.d.ts.map +0 -1
  55. package/dist/public/data.supabase.d.ts.map +0 -1
  56. package/dist/public/format.d.ts.map +0 -1
  57. package/dist/public/hooks.d.ts.map +0 -1
  58. package/dist/public/steps/ContactStep.d.ts.map +0 -1
  59. package/dist/public/steps/DateTimeStep.d.ts.map +0 -1
  60. package/dist/public/steps/OverviewStep.d.ts.map +0 -1
  61. package/dist/public/steps/PaymentStep.d.ts.map +0 -1
  62. package/dist/registries.d.ts.map +0 -1
  63. package/src/AgendaContext.tsx +0 -11
  64. package/src/AgendaPage.tsx +0 -33
  65. package/src/bridges/create-financial-bridge.ts +0 -296
  66. package/src/calendar-styles.css +0 -245
  67. package/src/calendar-styles.ts +0 -285
  68. package/src/components/AgendaGeneralSettings.tsx +0 -86
  69. package/src/components/AgendaMobileList.tsx +0 -103
  70. package/src/components/AppointmentModal.tsx +0 -1050
  71. package/src/components/AppointmentPopover.tsx +0 -231
  72. package/src/components/BookingPaymentPanel.tsx +0 -394
  73. package/src/components/CalendarSidebar.tsx +0 -119
  74. package/src/components/CalendarToolbar.tsx +0 -86
  75. package/src/components/EventContextMenu.tsx +0 -125
  76. package/src/components/FloatingPanel.tsx +0 -123
  77. package/src/components/MiniCalendar.tsx +0 -120
  78. package/src/components/PaymentStatusBadge.tsx +0 -62
  79. package/src/components/StatusBadge.tsx +0 -23
  80. package/src/config.ts +0 -368
  81. package/src/data/index.ts +0 -4
  82. package/src/data/mock.ts +0 -344
  83. package/src/data/supabase.ts +0 -636
  84. package/src/data/types.ts +0 -37
  85. package/src/financial-bridge.ts +0 -96
  86. package/src/hooks/useAgendaSettings.ts +0 -17
  87. package/src/index.ts +0 -297
  88. package/src/integrations/google-calendar/GoogleCalendarSettings.tsx +0 -136
  89. package/src/integrations/google-calendar/components/CalendarColorDot.tsx +0 -23
  90. package/src/integrations/google-calendar/components/ChannelMappingList.tsx +0 -329
  91. package/src/integrations/google-calendar/components/ImportEventsWizard.tsx +0 -215
  92. package/src/integrations/google-calendar/components/useAgendaLookups.ts +0 -100
  93. package/src/integrations/google-calendar/connector.ts +0 -20
  94. package/src/integrations/google-calendar/connectorDef.tsx +0 -185
  95. package/src/integrations/google-calendar/data/supabase.ts +0 -170
  96. package/src/integrations/google-calendar/data/tables.ts +0 -12
  97. package/src/integrations/google-calendar/index.ts +0 -45
  98. package/src/integrations/google-calendar/integration.test.ts +0 -199
  99. package/src/integrations/google-calendar/mapping.ts +0 -183
  100. package/src/integrations/google-calendar/migrations/001_google_calendar.sql +0 -97
  101. package/src/integrations/google-calendar/migrations/002_smart_sync.sql +0 -517
  102. package/src/integrations/google-calendar/migrations/003_plg_rename.sql +0 -273
  103. package/src/integrations/google-calendar/types.ts +0 -142
  104. package/src/lib/tenant.ts +0 -3
  105. package/src/locales/en.ts +0 -220
  106. package/src/locales/index.ts +0 -7
  107. package/src/locales/pt-BR.ts +0 -220
  108. package/src/migrations/000b_v_bookings_rename.sql +0 -21
  109. package/src/migrations/000c_dedupe_customer_phones.sql +0 -43
  110. package/src/migrations/001_public_booking.sql +0 -296
  111. package/src/migrations/002_v_bookings_compat.sql +0 -14
  112. package/src/migrations/003_public_client_kind.sql +0 -175
  113. package/src/migrations/004_public_booking_assignee.sql +0 -176
  114. package/src/migrations/005_agent_rpcs.sql +0 -168
  115. package/src/migrations/006_agent_rpc_localtime.sql +0 -181
  116. package/src/migrations/007_schedule_exception_precedence.sql +0 -290
  117. package/src/migrations/008_agent_rpc_exception_precedence.sql +0 -172
  118. package/src/migrations/009_public_booking_location.sql +0 -162
  119. package/src/migrations/010_public_service_curation.sql +0 -31
  120. package/src/migrations/011_default_assignee.sql +0 -167
  121. package/src/migrations/012_drop_legacy_booking_overload.sql +0 -29
  122. package/src/migrations/013_normalize_v_appointments.sql +0 -199
  123. package/src/migrations/014_agent_rpc_update_appointment.sql +0 -199
  124. package/src/migrations/index.ts +0 -2377
  125. package/src/public/BookingWidget.tsx +0 -324
  126. package/src/public/components/BrandHeader.tsx +0 -27
  127. package/src/public/components/ServiceCard.tsx +0 -39
  128. package/src/public/components/SidebarExtras.tsx +0 -30
  129. package/src/public/components/StepRailItem.tsx +0 -27
  130. package/src/public/components/SuccessPanel.tsx +0 -27
  131. package/src/public/components/SummaryRail.tsx +0 -90
  132. package/src/public/components/defaults.ts +0 -14
  133. package/src/public/context.tsx +0 -103
  134. package/src/public/createPublicBookingPlugin.ts +0 -281
  135. package/src/public/data.supabase.ts +0 -97
  136. package/src/public/data.ts +0 -105
  137. package/src/public/format.ts +0 -96
  138. package/src/public/hooks.ts +0 -110
  139. package/src/public/index.ts +0 -69
  140. package/src/public/steps/ContactStep.tsx +0 -184
  141. package/src/public/steps/DateTimeStep.tsx +0 -138
  142. package/src/public/steps/OverviewStep.tsx +0 -81
  143. package/src/public/steps/PaymentStep.tsx +0 -180
  144. package/src/public/types.ts +0 -250
  145. package/src/registries.ts +0 -35
  146. package/src/store.ts +0 -311
  147. package/src/types.ts +0 -273
  148. package/src/views/CalendarView.tsx +0 -1112
  149. package/src/views/ConfirmationsView.tsx +0 -155
  150. /package/dist/{financial-bridge.d.ts → bridges/financial-bridge.d.ts} +0 -0
  151. /package/dist/{registries.d.ts → data/registries.d.ts} +0 -0
  152. /package/dist/public/{BookingWidget.d.ts → views/BookingWidget.d.ts} +0 -0
  153. /package/dist/{calendar-styles.d.ts → styles/calendar-styles.d.ts} +0 -0
package/src/data/mock.ts DELETED
@@ -1,344 +0,0 @@
1
- import type { AgendaDataProvider } from './types'
2
- import type {
3
- CalendarBooking, Professional, Schedule, TimeSlot,
4
- CreateBookingInput, UpdateBookingInput, SaveScheduleInput,
5
- BookingQuery, ConflictCheckParams, AvailableSlotsParams, BookingStatus,
6
- } from '../types'
7
-
8
- // ---------------------------------------------------------------------------
9
- // Helpers
10
- // ---------------------------------------------------------------------------
11
-
12
- let nextId = 1
13
- function uid(): string { return `mock-${nextId++}` }
14
- function now(): string { return new Date().toISOString() }
15
-
16
- function addMinutes(date: string, minutes: number): string {
17
- const d = new Date(date)
18
- d.setMinutes(d.getMinutes() + minutes)
19
- return d.toISOString()
20
- }
21
-
22
- function timeOverlaps(aStart: string, aEnd: string, bStart: string, bEnd: string): boolean {
23
- return aStart < bEnd && aEnd > bStart
24
- }
25
-
26
- // ---------------------------------------------------------------------------
27
- // Sample data
28
- // ---------------------------------------------------------------------------
29
-
30
- const SAMPLE_PROFESSIONALS: Professional[] = [
31
- { id: 'prof-1', name: 'Ana Silva', avatarUrl: null, locationId: 'loc-1', locationName: 'Main Branch', isActive: true },
32
- { id: 'prof-2', name: 'Carlos Santos', avatarUrl: null, locationId: 'loc-1', locationName: 'Main Branch', isActive: true },
33
- { id: 'prof-3', name: 'Maria Oliveira', avatarUrl: null, locationId: 'loc-1', locationName: 'Main Branch', isActive: true },
34
- ]
35
-
36
- function createSampleBookings(): CalendarBooking[] {
37
- const today = new Date()
38
- const year = today.getFullYear()
39
- const month = today.getMonth()
40
- const day = today.getDate()
41
-
42
- function dt(hour: number, minute = 0): string {
43
- return new Date(year, month, day, hour, minute).toISOString()
44
- }
45
-
46
- return [
47
- {
48
- id: 'bk-1', tenantId: 'mock', kind: 'appointment',
49
- startsAt: dt(9, 0), endsAt: dt(10, 30), status: 'confirmed',
50
- notes: null, orderId: 'ord-1', locationId: 'loc-1', metadata: {},
51
- clientId: 'cl-1', clientName: 'Sarah Johnson', clientPhone: '(11) 99999-1111', clientEmail: 'sarah@email.com', clientAvatarUrl: null,
52
- professionalId: 'prof-1', professionalName: 'Ana Silva', professionalAvatarUrl: null,
53
- locationName: 'Main Branch', orderTotal: 250, orderStatus: 'draft',
54
- services: [{ id: 'bi-1', serviceId: 'svc-1', name: 'Balayage', durationMinutes: 90, price: 250, assigneeId: 'prof-1' }],
55
- totalDurationMinutes: 90, createdAt: now(), updatedAt: now(),
56
- },
57
- {
58
- id: 'bk-2', tenantId: 'mock', kind: 'appointment',
59
- startsAt: dt(10, 0), endsAt: dt(11, 0), status: 'scheduled',
60
- notes: null, orderId: 'ord-2', locationId: 'loc-1', metadata: {},
61
- clientId: 'cl-2', clientName: 'Emily Carter', clientPhone: '(11) 99999-2222', clientEmail: 'emily@email.com', clientAvatarUrl: null,
62
- professionalId: 'prof-2', professionalName: 'Carlos Santos', professionalAvatarUrl: null,
63
- locationName: 'Main Branch', orderTotal: 80, orderStatus: 'draft',
64
- services: [{ id: 'bi-2', serviceId: 'svc-2', name: 'Haircut', durationMinutes: 60, price: 80, assigneeId: 'prof-2' }],
65
- totalDurationMinutes: 60, createdAt: now(), updatedAt: now(),
66
- },
67
- {
68
- id: 'bk-3', tenantId: 'mock', kind: 'appointment',
69
- startsAt: dt(14, 0), endsAt: dt(15, 0), status: 'confirmed',
70
- notes: 'First visit', orderId: 'ord-3', locationId: 'loc-1', metadata: {},
71
- clientId: 'cl-3', clientName: 'Rachel Kim', clientPhone: '(11) 99999-3333', clientEmail: null, clientAvatarUrl: null,
72
- professionalId: 'prof-3', professionalName: 'Maria Oliveira', professionalAvatarUrl: null,
73
- locationName: 'Main Branch', orderTotal: 120, orderStatus: 'draft',
74
- services: [{ id: 'bi-3', serviceId: 'svc-3', name: 'Facial Treatment', durationMinutes: 60, price: 120, assigneeId: 'prof-3' }],
75
- totalDurationMinutes: 60, createdAt: now(), updatedAt: now(),
76
- },
77
- {
78
- id: 'bk-4', tenantId: 'mock', kind: 'appointment',
79
- startsAt: dt(11, 0), endsAt: dt(12, 0), status: 'scheduled',
80
- notes: null, orderId: 'ord-4', locationId: 'loc-1', metadata: {},
81
- clientId: 'cl-4', clientName: 'Jessica Lima', clientPhone: '(11) 99999-4444', clientEmail: null, clientAvatarUrl: null,
82
- professionalId: 'prof-1', professionalName: 'Ana Silva', professionalAvatarUrl: null,
83
- locationName: 'Main Branch', orderTotal: 60, orderStatus: 'draft',
84
- services: [{ id: 'bi-4', serviceId: 'svc-4', name: 'Manicure', durationMinutes: 60, price: 60, assigneeId: 'prof-1' }],
85
- totalDurationMinutes: 60, createdAt: now(), updatedAt: now(),
86
- },
87
- {
88
- id: 'bk-5', tenantId: 'mock', kind: 'appointment',
89
- startsAt: dt(15, 30), endsAt: dt(17, 0), status: 'confirmed',
90
- notes: null, orderId: 'ord-5', locationId: 'loc-1', metadata: {},
91
- clientId: 'cl-5', clientName: 'Amanda Wright', clientPhone: '(11) 99999-5555', clientEmail: 'amanda@email.com', clientAvatarUrl: null,
92
- professionalId: 'prof-2', professionalName: 'Carlos Santos', professionalAvatarUrl: null,
93
- locationName: 'Main Branch', orderTotal: 350, orderStatus: 'draft',
94
- services: [
95
- { id: 'bi-5', serviceId: 'svc-1', name: 'Highlights', durationMinutes: 60, price: 200, assigneeId: 'prof-2' },
96
- { id: 'bi-6', serviceId: 'svc-2', name: 'Haircut', durationMinutes: 30, price: 150, assigneeId: 'prof-2' },
97
- ],
98
- totalDurationMinutes: 90, createdAt: now(), updatedAt: now(),
99
- },
100
- ]
101
- }
102
-
103
- function createSampleSchedules(): Schedule[] {
104
- const schedules: Schedule[] = []
105
- for (const prof of SAMPLE_PROFESSIONALS) {
106
- // Mon-Fri 08:00-12:00, 13:00-18:00
107
- for (let day = 1; day <= 5; day++) {
108
- schedules.push({
109
- id: uid(), tenantId: 'mock', kind: 'working_hours',
110
- assigneeId: prof.id, locationId: prof.locationId,
111
- dayOfWeek: day, specificDate: null,
112
- startsAt: '08:00', endsAt: '12:00', isActive: true, metadata: {},
113
- })
114
- schedules.push({
115
- id: uid(), tenantId: 'mock', kind: 'working_hours',
116
- assigneeId: prof.id, locationId: prof.locationId,
117
- dayOfWeek: day, specificDate: null,
118
- startsAt: '13:00', endsAt: '18:00', isActive: true, metadata: {},
119
- })
120
- }
121
- // Saturday 08:00-14:00
122
- schedules.push({
123
- id: uid(), tenantId: 'mock', kind: 'working_hours',
124
- assigneeId: prof.id, locationId: prof.locationId,
125
- dayOfWeek: 6, specificDate: null,
126
- startsAt: '08:00', endsAt: '14:00', isActive: true, metadata: {},
127
- })
128
- }
129
- return schedules
130
- }
131
-
132
- // ---------------------------------------------------------------------------
133
- // Mock provider factory
134
- // ---------------------------------------------------------------------------
135
-
136
- /**
137
- * Pre-populated agenda data. Any omitted collection falls back to the built-in
138
- * salon-style sample. Pass `bookings` (and optionally an empty `professionals`)
139
- * to render a calendar seeded with app-specific events.
140
- */
141
- export interface MockAgendaSeed {
142
- bookings?: CalendarBooking[]
143
- professionals?: Professional[]
144
- schedules?: Schedule[]
145
- }
146
-
147
- export interface MockAgendaProviderOptions {
148
- seed?: MockAgendaSeed
149
- }
150
-
151
- export function createMockAgendaProvider(options?: MockAgendaProviderOptions): AgendaDataProvider {
152
- const seed = options?.seed
153
- const bookings: CalendarBooking[] = seed?.bookings ? [...seed.bookings] : createSampleBookings()
154
- const schedules: Schedule[] = seed?.schedules ? [...seed.schedules] : createSampleSchedules()
155
- const professionals: Professional[] = seed?.professionals ? [...seed.professionals] : [...SAMPLE_PROFESSIONALS]
156
-
157
- return {
158
- async getLocations(): Promise<Array<{ id: string; name: string }>> {
159
- return []
160
- },
161
- async getBookings(query: BookingQuery): Promise<CalendarBooking[]> {
162
- return bookings.filter((b) => {
163
- if (query.kind && b.kind !== query.kind) return false
164
- if (query.statuses?.length && !query.statuses.includes(b.status)) return false
165
- if (query.professionalIds?.length && !query.professionalIds.includes(b.professionalId ?? '')) return false
166
- if (query.locationId && b.locationId !== query.locationId) return false
167
- if (query.dateRange) {
168
- if (b.startsAt > query.dateRange.end || (b.endsAt ?? b.startsAt) < query.dateRange.start) return false
169
- }
170
- return true
171
- })
172
- },
173
-
174
- async getBookingById(id: string): Promise<CalendarBooking | null> {
175
- return bookings.find((b) => b.id === id) ?? null
176
- },
177
-
178
- async createBooking(input: CreateBookingInput): Promise<CalendarBooking> {
179
- const totalDuration = input.services.reduce((sum, s) => sum + s.durationMinutes, 0)
180
- const totalPrice = input.services.reduce((sum, s) => sum + s.price, 0)
181
- const booking: CalendarBooking = {
182
- id: uid(), tenantId: 'mock', kind: input.kind ?? 'appointment',
183
- startsAt: input.startsAt, endsAt: addMinutes(input.startsAt, totalDuration || 30),
184
- status: 'scheduled', notes: input.notes ?? null,
185
- orderId: input.services.length > 0 ? uid() : null, locationId: input.locationId ?? null, metadata: {},
186
- clientId: input.clientId, clientName: input.title?.trim() || 'New Client', clientPhone: null, clientEmail: null, clientAvatarUrl: null,
187
- professionalId: input.professionalId,
188
- professionalName: professionals.find((p) => p.id === input.professionalId)?.name ?? 'Unknown',
189
- professionalAvatarUrl: null,
190
- locationName: null, orderTotal: totalPrice, orderStatus: 'draft',
191
- services: input.services.map((s) => ({
192
- id: uid(), serviceId: s.serviceId, name: s.name,
193
- durationMinutes: s.durationMinutes, price: s.price,
194
- assigneeId: s.assigneeId ?? input.professionalId,
195
- })),
196
- totalDurationMinutes: totalDuration, createdAt: now(), updatedAt: now(),
197
- }
198
- bookings.push(booking)
199
- return booking
200
- },
201
-
202
- async updateBooking(id: string, data: UpdateBookingInput): Promise<CalendarBooking> {
203
- const idx = bookings.findIndex((b) => b.id === id)
204
- if (idx === -1) throw new Error('Booking not found')
205
- const existing = bookings[idx]
206
- const serviceUpdates: Partial<CalendarBooking> = {}
207
- if (data.services) {
208
- serviceUpdates.services = data.services.map((s) => ({
209
- id: uid(), serviceId: s.serviceId, name: s.name,
210
- durationMinutes: s.durationMinutes, price: s.price,
211
- assigneeId: s.assigneeId ?? existing.professionalId ?? '',
212
- }))
213
- serviceUpdates.totalDurationMinutes = data.services.reduce((sum, s) => sum + s.durationMinutes, 0)
214
- serviceUpdates.orderTotal = data.services.reduce((sum, s) => sum + s.price, 0)
215
- }
216
- const updated: CalendarBooking = {
217
- ...existing,
218
- ...(data.startsAt && { startsAt: data.startsAt }),
219
- ...(data.endsAt && { endsAt: data.endsAt }),
220
- ...(data.title !== undefined && { clientName: data.title }),
221
- ...(data.status && { status: data.status }),
222
- ...(data.notes !== undefined && { notes: data.notes ?? null }),
223
- ...(data.professionalId && { professionalId: data.professionalId }),
224
- ...(data.clientId && { clientId: data.clientId }),
225
- ...(data.locationId && { locationId: data.locationId }),
226
- ...serviceUpdates,
227
- updatedAt: now(),
228
- }
229
- bookings[idx] = updated
230
- return updated
231
- },
232
-
233
- async deleteBooking(id: string): Promise<void> {
234
- const idx = bookings.findIndex((b) => b.id === id)
235
- if (idx !== -1) bookings.splice(idx, 1)
236
- },
237
-
238
- async updateBookingStatus(id: string, status: string): Promise<CalendarBooking> {
239
- const idx = bookings.findIndex((b) => b.id === id)
240
- if (idx === -1) throw new Error('Booking not found')
241
- bookings[idx] = { ...bookings[idx], status: status as BookingStatus, updatedAt: now() }
242
- return bookings[idx]
243
- },
244
-
245
- async checkConflict(params: ConflictCheckParams): Promise<boolean> {
246
- return bookings.some((b) => {
247
- if (params.excludeBookingId && b.id === params.excludeBookingId) return false
248
- if (b.professionalId !== params.assigneeId) return false
249
- if (b.status === 'cancelled' || b.status === 'no_show') return false
250
- return timeOverlaps(b.startsAt, b.endsAt ?? b.startsAt, params.startsAt, params.endsAt)
251
- })
252
- },
253
-
254
- async getAvailableSlots(params: AvailableSlotsParams): Promise<TimeSlot[]> {
255
- // Parse 'YYYY-MM-DD' as LOCAL midnight (not UTC) so the weekday and the
256
- // generated slot instants land on the requested calendar day in any tz.
257
- const [yy, mm, dd] = params.date.split('-').map(Number)
258
- const date = new Date(yy, (mm ?? 1) - 1, dd ?? 1)
259
- const dayOfWeek = date.getDay()
260
- const interval = params.slotInterval ?? 30
261
-
262
- // Get working hours for this professional on this day
263
- const daySchedules = schedules.filter((s) =>
264
- s.assigneeId === params.assigneeId && s.isActive &&
265
- (s.dayOfWeek === dayOfWeek || s.specificDate === params.date)
266
- )
267
-
268
- const slots: TimeSlot[] = []
269
- for (const sched of daySchedules) {
270
- const [startH, startM] = sched.startsAt.split(':').map(Number)
271
- const [endH, endM] = sched.endsAt.split(':').map(Number)
272
- const schedStart = new Date(date); schedStart.setHours(startH, startM, 0, 0)
273
- const schedEnd = new Date(date); schedEnd.setHours(endH, endM, 0, 0)
274
-
275
- let slotStart = new Date(schedStart)
276
- while (true) {
277
- const slotEnd = new Date(slotStart.getTime() + params.durationMinutes * 60000)
278
- if (slotEnd > schedEnd) break
279
- const startIso = slotStart.toISOString()
280
- const endIso = slotEnd.toISOString()
281
- const hasConflict = bookings.some((b) => {
282
- if (b.professionalId !== params.assigneeId) return false
283
- if (b.status === 'cancelled' || b.status === 'no_show') return false
284
- return timeOverlaps(b.startsAt, b.endsAt ?? b.startsAt, startIso, endIso)
285
- })
286
- if (!hasConflict) {
287
- slots.push({ start: startIso, end: endIso })
288
- }
289
- slotStart = new Date(slotStart.getTime() + interval * 60000)
290
- }
291
- }
292
- return slots
293
- },
294
-
295
- async getSchedules(professionalId?: string): Promise<Schedule[]> {
296
- if (professionalId) return schedules.filter((s) => s.assigneeId === professionalId)
297
- return [...schedules]
298
- },
299
-
300
- async saveSchedule(input: SaveScheduleInput): Promise<Schedule> {
301
- const schedule: Schedule = {
302
- id: uid(), tenantId: 'mock', kind: 'working_hours',
303
- assigneeId: input.assigneeId, locationId: input.locationId ?? null,
304
- dayOfWeek: input.dayOfWeek ?? null, specificDate: input.specificDate ?? null,
305
- startsAt: input.startsAt, endsAt: input.endsAt,
306
- isActive: input.isActive !== false, metadata: input.metadata ?? {},
307
- }
308
- schedules.push(schedule)
309
- return schedule
310
- },
311
-
312
- async deleteSchedule(id: string): Promise<void> {
313
- const idx = schedules.findIndex((s) => s.id === id)
314
- if (idx !== -1) schedules.splice(idx, 1)
315
- },
316
-
317
- async getProfessionals(): Promise<Professional[]> {
318
- return [...professionals]
319
- },
320
-
321
- async getConfirmationsPending(daysAhead = 2): Promise<CalendarBooking[]> {
322
- const cutoff = new Date()
323
- cutoff.setDate(cutoff.getDate() + daysAhead)
324
- return bookings.filter((b) =>
325
- b.status === 'scheduled' && new Date(b.startsAt) <= cutoff && new Date(b.startsAt) >= new Date()
326
- )
327
- },
328
-
329
- async sendConfirmation(bookingId: string, channel: string): Promise<void> {
330
- const booking = bookings.find((b) => b.id === bookingId)
331
- if (!booking) throw new Error('Booking not found')
332
- const confirmations = (booking.metadata.confirmations as any[] ?? [])
333
- confirmations.push({ channel, sentAt: now(), status: 'sent' })
334
- booking.metadata = { ...booking.metadata, confirmations }
335
- },
336
-
337
- async completeBooking(bookingId: string): Promise<{ booking: CalendarBooking; orderId: string }> {
338
- const idx = bookings.findIndex((b) => b.id === bookingId)
339
- if (idx === -1) throw new Error('Booking not found')
340
- bookings[idx] = { ...bookings[idx], status: 'completed', updatedAt: now() }
341
- return { booking: bookings[idx], orderId: bookings[idx].orderId ?? '' }
342
- },
343
- }
344
- }