@fayz-ai/plugin-agenda 0.9.0 → 0.10.0

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 (196) 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-ISLJXP7O.js} +7 -2
  6. package/dist/chunk-ISLJXP7O.js.map +1 -0
  7. package/dist/components/AppointmentModal.d.ts.map +1 -1
  8. package/dist/components/PaymentStatusBadge.d.ts +1 -1
  9. package/dist/components/PaymentStatusBadge.d.ts.map +1 -1
  10. package/dist/config.d.ts +2 -2
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/{AgendaContext.d.ts → context.d.ts} +1 -1
  13. package/dist/context.d.ts.map +1 -0
  14. package/dist/data/mock.d.ts.map +1 -1
  15. package/dist/data/realtime.test.d.ts +2 -0
  16. package/dist/data/realtime.test.d.ts.map +1 -0
  17. package/dist/data/registries.d.ts.map +1 -0
  18. package/dist/data/supabase.d.ts.map +1 -1
  19. package/dist/data/types.d.ts +7 -0
  20. package/dist/data/types.d.ts.map +1 -1
  21. package/dist/index.d.ts +2 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2515 -187
  24. package/dist/index.js.map +1 -1
  25. package/dist/integrations/google-calendar/conflict.d.ts +25 -0
  26. package/dist/integrations/google-calendar/conflict.d.ts.map +1 -0
  27. package/dist/integrations/google-calendar/connectorDef.d.ts +12 -0
  28. package/dist/integrations/google-calendar/connectorDef.d.ts.map +1 -1
  29. package/dist/integrations/google-calendar/credentials.d.ts +27 -0
  30. package/dist/integrations/google-calendar/credentials.d.ts.map +1 -0
  31. package/dist/integrations/google-calendar/data/supabase.d.ts +10 -2
  32. package/dist/integrations/google-calendar/data/supabase.d.ts.map +1 -1
  33. package/dist/integrations/google-calendar/data/tables.d.ts +2 -0
  34. package/dist/integrations/google-calendar/data/tables.d.ts.map +1 -1
  35. package/dist/integrations/google-calendar/health.d.ts +23 -0
  36. package/dist/integrations/google-calendar/health.d.ts.map +1 -0
  37. package/dist/integrations/google-calendar/health.test.d.ts +2 -0
  38. package/dist/integrations/google-calendar/health.test.d.ts.map +1 -0
  39. package/dist/integrations/google-calendar/index.d.ts +6 -0
  40. package/dist/integrations/google-calendar/index.d.ts.map +1 -1
  41. package/dist/integrations/google-calendar/migrations/index.d.ts +12 -0
  42. package/dist/integrations/google-calendar/migrations/index.d.ts.map +1 -0
  43. package/dist/integrations/google-calendar/panel-data.d.ts +26 -0
  44. package/dist/integrations/google-calendar/panel-data.d.ts.map +1 -0
  45. package/dist/integrations/google-calendar/panel-data.test.d.ts +2 -0
  46. package/dist/integrations/google-calendar/panel-data.test.d.ts.map +1 -0
  47. package/dist/integrations/google-calendar/types.d.ts +17 -0
  48. package/dist/integrations/google-calendar/types.d.ts.map +1 -1
  49. package/dist/lib/booking-events.d.ts +5 -0
  50. package/dist/lib/booking-events.d.ts.map +1 -0
  51. package/dist/lib/calendar-events.d.ts +8 -0
  52. package/dist/lib/calendar-events.d.ts.map +1 -0
  53. package/dist/lib/calendar-events.test.d.ts +2 -0
  54. package/dist/lib/calendar-events.test.d.ts.map +1 -0
  55. package/dist/lib/message-events.d.ts +12 -0
  56. package/dist/lib/message-events.d.ts.map +1 -0
  57. package/dist/locales/en.d.ts.map +1 -1
  58. package/dist/locales/pt-BR.d.ts.map +1 -1
  59. package/dist/public/{data.d.ts → data/index.d.ts} +4 -4
  60. package/dist/public/data/index.d.ts.map +1 -0
  61. package/dist/public/{data.supabase.d.ts → data/supabase.d.ts} +2 -2
  62. package/dist/public/data/supabase.d.ts.map +1 -0
  63. package/dist/public/{hooks.d.ts → hooks/index.d.ts} +4 -4
  64. package/dist/public/hooks/index.d.ts.map +1 -0
  65. package/dist/public/index.d.ts +4 -4
  66. package/dist/public/index.d.ts.map +1 -1
  67. package/dist/public/index.js +3 -3
  68. package/dist/public/index.js.map +1 -1
  69. package/dist/public/{format.d.ts → lib/format.d.ts} +1 -1
  70. package/dist/public/lib/format.d.ts.map +1 -0
  71. package/dist/public/views/BookingWidget.d.ts.map +1 -0
  72. package/dist/public/{steps → views/steps}/ContactStep.d.ts +1 -1
  73. package/dist/public/views/steps/ContactStep.d.ts.map +1 -0
  74. package/dist/public/{steps → views/steps}/DateTimeStep.d.ts +3 -3
  75. package/dist/public/views/steps/DateTimeStep.d.ts.map +1 -0
  76. package/dist/public/{steps → views/steps}/OverviewStep.d.ts +1 -1
  77. package/dist/public/views/steps/OverviewStep.d.ts.map +1 -0
  78. package/dist/public/{steps → views/steps}/PaymentStep.d.ts +1 -1
  79. package/dist/public/views/steps/PaymentStep.d.ts.map +1 -0
  80. package/dist/store.d.ts +1 -1
  81. package/dist/store.d.ts.map +1 -1
  82. package/dist/styles/calendar-styles.d.ts.map +1 -0
  83. package/dist/types.d.ts +1 -1
  84. package/dist/types.d.ts.map +1 -1
  85. package/dist/{AgendaPage.d.ts → views/AgendaPage.d.ts} +3 -3
  86. package/dist/views/AgendaPage.d.ts.map +1 -0
  87. package/dist/views/CalendarView.d.ts.map +1 -1
  88. package/package.json +9 -11
  89. package/dist/AgendaContext.d.ts.map +0 -1
  90. package/dist/AgendaPage.d.ts.map +0 -1
  91. package/dist/calendar-styles.d.ts.map +0 -1
  92. package/dist/chunk-Q2PAWBPR.js.map +0 -1
  93. package/dist/financial-bridge.d.ts.map +0 -1
  94. package/dist/integrations/google-calendar/GoogleCalendarSettings.d.ts +0 -3
  95. package/dist/integrations/google-calendar/GoogleCalendarSettings.d.ts.map +0 -1
  96. package/dist/public/BookingWidget.d.ts.map +0 -1
  97. package/dist/public/data.d.ts.map +0 -1
  98. package/dist/public/data.supabase.d.ts.map +0 -1
  99. package/dist/public/format.d.ts.map +0 -1
  100. package/dist/public/hooks.d.ts.map +0 -1
  101. package/dist/public/steps/ContactStep.d.ts.map +0 -1
  102. package/dist/public/steps/DateTimeStep.d.ts.map +0 -1
  103. package/dist/public/steps/OverviewStep.d.ts.map +0 -1
  104. package/dist/public/steps/PaymentStep.d.ts.map +0 -1
  105. package/dist/registries.d.ts.map +0 -1
  106. package/src/AgendaContext.tsx +0 -11
  107. package/src/AgendaPage.tsx +0 -33
  108. package/src/bridges/create-financial-bridge.ts +0 -296
  109. package/src/calendar-styles.css +0 -245
  110. package/src/calendar-styles.ts +0 -285
  111. package/src/components/AgendaGeneralSettings.tsx +0 -86
  112. package/src/components/AgendaMobileList.tsx +0 -103
  113. package/src/components/AppointmentModal.tsx +0 -1050
  114. package/src/components/AppointmentPopover.tsx +0 -231
  115. package/src/components/BookingPaymentPanel.tsx +0 -394
  116. package/src/components/CalendarSidebar.tsx +0 -119
  117. package/src/components/CalendarToolbar.tsx +0 -86
  118. package/src/components/EventContextMenu.tsx +0 -125
  119. package/src/components/FloatingPanel.tsx +0 -123
  120. package/src/components/MiniCalendar.tsx +0 -120
  121. package/src/components/PaymentStatusBadge.tsx +0 -62
  122. package/src/components/StatusBadge.tsx +0 -23
  123. package/src/config.ts +0 -368
  124. package/src/data/index.ts +0 -4
  125. package/src/data/mock.ts +0 -344
  126. package/src/data/supabase.ts +0 -636
  127. package/src/data/types.ts +0 -37
  128. package/src/financial-bridge.ts +0 -96
  129. package/src/hooks/useAgendaSettings.ts +0 -17
  130. package/src/index.ts +0 -297
  131. package/src/integrations/google-calendar/GoogleCalendarSettings.tsx +0 -136
  132. package/src/integrations/google-calendar/components/CalendarColorDot.tsx +0 -23
  133. package/src/integrations/google-calendar/components/ChannelMappingList.tsx +0 -329
  134. package/src/integrations/google-calendar/components/ImportEventsWizard.tsx +0 -215
  135. package/src/integrations/google-calendar/components/useAgendaLookups.ts +0 -100
  136. package/src/integrations/google-calendar/connector.ts +0 -20
  137. package/src/integrations/google-calendar/connectorDef.tsx +0 -185
  138. package/src/integrations/google-calendar/data/supabase.ts +0 -170
  139. package/src/integrations/google-calendar/data/tables.ts +0 -12
  140. package/src/integrations/google-calendar/index.ts +0 -45
  141. package/src/integrations/google-calendar/integration.test.ts +0 -199
  142. package/src/integrations/google-calendar/mapping.ts +0 -183
  143. package/src/integrations/google-calendar/migrations/001_google_calendar.sql +0 -97
  144. package/src/integrations/google-calendar/migrations/002_smart_sync.sql +0 -517
  145. package/src/integrations/google-calendar/migrations/003_plg_rename.sql +0 -273
  146. package/src/integrations/google-calendar/types.ts +0 -142
  147. package/src/lib/tenant.ts +0 -3
  148. package/src/locales/en.ts +0 -220
  149. package/src/locales/index.ts +0 -7
  150. package/src/locales/pt-BR.ts +0 -220
  151. package/src/migrations/000b_v_bookings_rename.sql +0 -21
  152. package/src/migrations/000c_dedupe_customer_phones.sql +0 -43
  153. package/src/migrations/001_public_booking.sql +0 -296
  154. package/src/migrations/002_v_bookings_compat.sql +0 -14
  155. package/src/migrations/003_public_client_kind.sql +0 -175
  156. package/src/migrations/004_public_booking_assignee.sql +0 -176
  157. package/src/migrations/005_agent_rpcs.sql +0 -168
  158. package/src/migrations/006_agent_rpc_localtime.sql +0 -181
  159. package/src/migrations/007_schedule_exception_precedence.sql +0 -290
  160. package/src/migrations/008_agent_rpc_exception_precedence.sql +0 -172
  161. package/src/migrations/009_public_booking_location.sql +0 -162
  162. package/src/migrations/010_public_service_curation.sql +0 -31
  163. package/src/migrations/011_default_assignee.sql +0 -167
  164. package/src/migrations/012_drop_legacy_booking_overload.sql +0 -29
  165. package/src/migrations/013_normalize_v_appointments.sql +0 -199
  166. package/src/migrations/014_agent_rpc_update_appointment.sql +0 -199
  167. package/src/migrations/index.ts +0 -2377
  168. package/src/public/BookingWidget.tsx +0 -324
  169. package/src/public/components/BrandHeader.tsx +0 -27
  170. package/src/public/components/ServiceCard.tsx +0 -39
  171. package/src/public/components/SidebarExtras.tsx +0 -30
  172. package/src/public/components/StepRailItem.tsx +0 -27
  173. package/src/public/components/SuccessPanel.tsx +0 -27
  174. package/src/public/components/SummaryRail.tsx +0 -90
  175. package/src/public/components/defaults.ts +0 -14
  176. package/src/public/context.tsx +0 -103
  177. package/src/public/createPublicBookingPlugin.ts +0 -281
  178. package/src/public/data.supabase.ts +0 -97
  179. package/src/public/data.ts +0 -105
  180. package/src/public/format.ts +0 -96
  181. package/src/public/hooks.ts +0 -110
  182. package/src/public/index.ts +0 -69
  183. package/src/public/steps/ContactStep.tsx +0 -184
  184. package/src/public/steps/DateTimeStep.tsx +0 -138
  185. package/src/public/steps/OverviewStep.tsx +0 -81
  186. package/src/public/steps/PaymentStep.tsx +0 -180
  187. package/src/public/types.ts +0 -250
  188. package/src/registries.ts +0 -35
  189. package/src/store.ts +0 -311
  190. package/src/types.ts +0 -273
  191. package/src/views/CalendarView.tsx +0 -1112
  192. package/src/views/ConfirmationsView.tsx +0 -155
  193. /package/dist/{financial-bridge.d.ts → bridges/financial-bridge.d.ts} +0 -0
  194. /package/dist/{registries.d.ts → data/registries.d.ts} +0 -0
  195. /package/dist/public/{BookingWidget.d.ts → views/BookingWidget.d.ts} +0 -0
  196. /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
- }