@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
@@ -1,96 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // Agenda × Financial Bridge — Cross-plugin integration types
3
- // Pure types only. Runtime implementation in bridges/create-financial-bridge.ts
4
- // ---------------------------------------------------------------------------
5
-
6
- /** Payment status for a booking's linked invoice */
7
- export type BookingPaymentStatus = 'none' | 'pending' | 'partial' | 'paid' | 'overdue' | 'cancelled'
8
-
9
- /** Compact payment summary for calendar enrichment */
10
- export interface BookingPaymentSummary {
11
- invoiceId: string
12
- referenceNumber?: string
13
- status: BookingPaymentStatus
14
- totalAmount: number
15
- paidAmount: number
16
- }
17
-
18
- /** Detailed payment info with installment breakdown */
19
- export interface BookingPaymentDetail extends BookingPaymentSummary {
20
- movements: Array<{
21
- id: string
22
- amount: number
23
- paidAmount: number
24
- status: string
25
- dueDate: string
26
- installmentNumber?: number
27
- paymentDate?: string
28
- paymentMethodTypeName?: string
29
- cardBrand?: string
30
- cardInstallments?: number
31
- }>
32
- }
33
-
34
- /**
35
- * Bridge interface for agenda → financial plugin integration.
36
- * Injected via `AgendaPluginOptions.financialBridge`.
37
- * Follows the same DI pattern as `EntityLookup`.
38
- */
39
- export interface AgendaFinancialBridge {
40
- /** Batch-resolve payment status for multiple order IDs (for calendar enrichment) */
41
- resolvePaymentStatuses(orderIds: string[]): Promise<Map<string, BookingPaymentSummary>>
42
-
43
- /** Get detailed payment info for a single booking's order */
44
- getPaymentDetail(orderId: string): Promise<BookingPaymentDetail | null>
45
-
46
- /** Create a receivable invoice from the booking's service_order. Returns invoice ID */
47
- createInvoiceFromOrder(orderId: string, options?: {
48
- installments?: number
49
- dueDate?: string
50
- paymentMethodTypeId?: string
51
- }): Promise<string>
52
-
53
- /**
54
- * Accrue the assigned professional's commission for a booking's order as a
55
- * payable `financial_movements` row (direction='debit', movement_kind='commission').
56
- * Commission = order.total × professional commission_rate%. Resolves the rate
57
- * from the `v_staff` bridge view via the order's `assignee_id`.
58
- *
59
- * Idempotent per order: returns the existing commission movement id if one was
60
- * already accrued. Returns null when there is no professional, no configured
61
- * rate, a zero base, or Supabase is unavailable (nothing to accrue).
62
- */
63
- createCommissionMovement?(orderId: string, options?: {
64
- dueDate?: string
65
- }): Promise<string | null>
66
-
67
- /** Record a payment against a movement (installment) */
68
- payMovement(input: {
69
- movementId: string
70
- amount: number
71
- paymentDate: string
72
- paymentMethodTypeId?: string
73
- paymentMethodId?: string
74
- bankAccountId?: string
75
- cardBrand?: string
76
- cardInstallments?: number
77
- }): Promise<void>
78
-
79
- /** One-step POS checkout: create invoice + pay in full. Returns paid detail. */
80
- checkout(orderId: string, input: {
81
- paymentMethodTypeId: string
82
- paymentMethodId?: string
83
- bankAccountId?: string
84
- cardBrand?: string
85
- cardInstallments?: number
86
- }): Promise<BookingPaymentDetail>
87
-
88
- /** Get payment methods filtered by type */
89
- getPaymentMethods?(paymentMethodTypeId: string): Promise<Array<{ id: string; name: string }>>
90
-
91
- /** Get payment method types for the payment UI */
92
- getPaymentMethodTypes(): Promise<Array<{ id: string; name: string; transactionType?: string }>>
93
-
94
- /** Get bank accounts for payment destination */
95
- getBankAccounts(): Promise<Array<{ id: string; name: string; accountType: string }>>
96
- }
@@ -1,17 +0,0 @@
1
- import { usePluginPrefs } from '@fayz-ai/saas'
2
-
3
- const DEFAULTS = {
4
- startTime: '07:00',
5
- endTime: '21:00',
6
- }
7
-
8
- export function useAgendaSettings() {
9
- const prefs = usePluginPrefs('agenda', DEFAULTS)
10
-
11
- return {
12
- startTime: prefs.get('startTime'),
13
- endTime: prefs.get('endTime'),
14
- setStartTime: (v: string) => prefs.set('startTime', v),
15
- setEndTime: (v: string) => prefs.set('endTime', v),
16
- }
17
- }
package/src/index.ts DELETED
@@ -1,297 +0,0 @@
1
- import React from 'react'
2
- import type { PluginManifest } from '@fayz-ai/core'
3
- import { createSafeDataProvider, registerTranslations } from '@fayz-ai/core'
4
- import type { AgendaPluginOptions } from './config'
5
- import { resolveConfig } from './config'
6
- import { AgendaPage } from './AgendaPage'
7
- import { createSupabaseAgendaProvider } from './data/supabase'
8
- import { createMockAgendaProvider } from './data/mock'
9
- import { setAgendaTenantId } from './lib/tenant'
10
- import { createAgendaStore } from './store'
11
- import { agendaRegistries } from './registries'
12
- import { AgendaGeneralSettings } from './components/AgendaGeneralSettings'
13
- import { agendaLocales } from './locales'
14
- import { MIGRATION_001_PUBLIC_BOOKING } from './migrations'
15
- import { setScheduleBlockConfig, getScheduleBlockConfig, PluginSettingsPanel } from '@fayz-ai/saas'
16
-
17
- // ---------------------------------------------------------------------------
18
- // Factory
19
- // ---------------------------------------------------------------------------
20
-
21
- export function createAgendaPlugin(options?: AgendaPluginOptions): PluginManifest {
22
- const config = resolveConfig(options)
23
- registerTranslations(agendaLocales)
24
- const provider = options?.dataProvider ?? createSafeDataProvider(
25
- () => createSupabaseAgendaProvider({ professionalKind: config.professionalKind }),
26
- () => createMockAgendaProvider(),
27
- )
28
- const store = createAgendaStore(provider, options?.financialBridge)
29
- const registries = [...agendaRegistries, ...(options?.settingsRegistries ?? [])]
30
-
31
- // Register schedule block config globally so ScheduleEditor can read it
32
- setScheduleBlockConfig({
33
- defaults: config.scheduleBlockDefaults,
34
- showServices: !!config.serviceLookup,
35
- showConcurrent: true,
36
- showBookingWindow: true,
37
- showLocations: config.modules.locationSelection,
38
- locations: config.locations.map((l) => ({ id: l.id, name: l.name })),
39
- // Lazy fetcher — called by ScheduleEditor when locations are needed but empty
40
- fetchLocations: config.modules.locationSelection && provider.getLocations
41
- ? async () => {
42
- const locations = await provider.getLocations!()
43
- const current = getScheduleBlockConfig()
44
- if (current) setScheduleBlockConfig({ ...current, locations })
45
- return locations
46
- }
47
- : undefined,
48
- })
49
-
50
- const PageComponent: React.FC<any> = () =>
51
- React.createElement(AgendaPage, { config, provider, store })
52
-
53
- return {
54
- id: 'agenda',
55
- name: config.labels.pageTitle,
56
- icon: 'Calendar',
57
- version: '1.0.0',
58
- scope: options?.scope ?? 'universal',
59
- verticalId: options?.verticalId,
60
- // Booking/scheduling is business-ops (clinic, salon, services) — targets
61
- // the saas/admin Panel world, not the ecommerce storefront.
62
- scaffolds: ['saas'],
63
- defaultEnabled: true,
64
- dependencies: [],
65
- declaredFeatures: [
66
- { id: 'appointments', label: config.labels.pageTitle, group: config.labels.pageTitle },
67
- { id: 'agenda.schedules', label: 'Schedule Management', group: config.labels.pageTitle },
68
- ],
69
- // Recurring monthly quota — counts appointment rows created this month.
70
- // (Clients/patients are the app's CRUD entity, so their cap is declared by
71
- // the app's EntityDef.limitKey, not here.)
72
- declaredLimits: [
73
- { key: 'bookings_month', label: 'Appointments this month', table: 'appointments', period: 'month' },
74
- ],
75
- queryEntities: [
76
- {
77
- key: 'agenda:appointments',
78
- entity: {
79
- name: 'Appointment',
80
- namePlural: 'Appointments',
81
- icon: 'Calendar',
82
- permission: { feature: 'appointments', action: 'read' },
83
- // Keys are the PROVIDER's output shape (camelCase) — what filters/
84
- // dateField/groupBy match against, not the raw DB columns.
85
- fields: [
86
- { key: 'startsAt', label: 'Starts at', type: 'text' },
87
- { key: 'endsAt', label: 'Ends at', type: 'text' },
88
- { key: 'status', label: 'Status', type: 'text' },
89
- { key: 'clientName', label: 'Client', type: 'text', searchable: true },
90
- { key: 'professionalName', label: 'Professional', type: 'text', searchable: true },
91
- { key: 'orderTotal', label: 'Order total', type: 'number' },
92
- { key: 'totalDurationMinutes', label: 'Duration (min)', type: 'number' },
93
- { key: 'createdAt', label: 'Created at', type: 'text' },
94
- ],
95
- data: {
96
- table: 'v_appointments',
97
- tenantScoped: true,
98
- searchColumns: ['client_name', 'professional_name'],
99
- },
100
- },
101
- },
102
- ],
103
- declaredRpcs: [
104
- {
105
- name: 'agent_agenda_create_appointment',
106
- kind: 'write' as const,
107
- description:
108
- 'Guarded appointment create: agent_guard (role→plan→bookings_month cap), working hours, race-safe conflict check, audited.',
109
- audits: true,
110
- },
111
- {
112
- name: 'agent_agenda_update_appointment',
113
- kind: 'write' as const,
114
- description:
115
- 'Guarded appointment reschedule/reassign/cancel: agent_guard (role→plan, no cap), working hours, race-safe conflict check that skips the appointment itself, audited.',
116
- audits: true,
117
- },
118
- ],
119
-
120
- navigation: [
121
- {
122
- section: options?.navSection ?? 'main',
123
- position: options?.navPosition ?? 2,
124
- label: config.labels.pageTitle,
125
- route: '/agenda',
126
- icon: 'Calendar',
127
- permission: { feature: 'appointments', action: 'read' as const },
128
- },
129
- ],
130
-
131
- routes: [
132
- {
133
- path: '/agenda',
134
- component: PageComponent,
135
- permission: { feature: 'appointments', action: 'read' as const },
136
- // Matches the ref agent_agenda_create_appointment returns.
137
- entityRoutes: ['schedule:appointment'],
138
- },
139
- ],
140
-
141
- widgets: [],
142
-
143
- aiTools: [
144
- {
145
- id: 'agenda.list-appointments',
146
- name: 'listAppointments',
147
- description: 'Lists upcoming appointments for a given date or date range, optionally filtered by professional.',
148
- icon: 'Calendar',
149
- mode: 'read' as const,
150
- category: 'Scheduling',
151
- parameters: {
152
- type: 'object' as const,
153
- properties: {
154
- date: { type: 'string' as const, description: 'Date (YYYY-MM-DD) or "today", "tomorrow", "this week"' },
155
- professional: { type: 'string' as const, description: 'Professional name to filter by' },
156
- },
157
- },
158
- suggestions: [
159
- { label: "What's on the agenda today?" },
160
- { label: 'Show me tomorrow\'s appointments' },
161
- { label: 'Who has the most bookings this week?' },
162
- ],
163
- permission: { feature: 'appointments', action: 'read' as const },
164
- },
165
- {
166
- id: 'agenda.create-appointment',
167
- name: 'createAppointment',
168
- description:
169
- 'Creates a new appointment. Takes IDS, not names: resolve the client/service/professional ids first via findAnything/searchRecords, then call this with an ISO start time in the tenant timezone. The user confirms before booking.',
170
- icon: 'CalendarPlus',
171
- mode: 'persist' as const,
172
- limitKey: 'bookings_month',
173
- execution: { plane: 'server' as const, kind: 'rpc' as const, rpc: 'agent_agenda_create_appointment' },
174
- category: 'Scheduling',
175
- parameters: {
176
- type: 'object' as const,
177
- properties: {
178
- client_id: { type: 'string' as const, description: 'Client id (from a search)' },
179
- service_id: { type: 'string' as const, description: 'Service id (from a search)' },
180
- professional_id: { type: 'string' as const, description: 'Professional id (optional — auto-assigned when omitted)' },
181
- starts_at: { type: 'string' as const, description: "LOCAL date-time of the business, NO timezone suffix — e.g. '2026-07-27T10:00' for 10h. The system applies the tenant timezone." },
182
- notes: { type: 'string' as const, description: 'Optional notes' },
183
- },
184
- required: ['client_id', 'service_id', 'starts_at'],
185
- },
186
- suggestions: [
187
- { label: 'Book a haircut for Sarah tomorrow at 10am' },
188
- ],
189
- permission: { feature: 'appointments', action: 'create' as const },
190
- },
191
- {
192
- id: 'agenda.update-appointment',
193
- name: 'updateAppointment',
194
- description:
195
- 'Changes an appointment that ALREADY EXISTS: reschedule (starts_at), reassign (professional_id), cancel or confirm (status), or annotate (notes). ' +
196
- 'This is the ONLY way to change an appointment — the appointments entity is a read-only view, so updateRecord cannot touch it. ' +
197
- 'NEVER answer a reschedule or a cancellation by creating a new appointment: that leaves the original standing and double-books the professional. ' +
198
- 'Takes the appointment id from a previous search. The duration is preserved automatically. The user confirms before anything changes.',
199
- icon: 'CalendarClock',
200
- mode: 'persist' as const,
201
- execution: { plane: 'server' as const, kind: 'rpc' as const, rpc: 'agent_agenda_update_appointment' },
202
- category: 'Scheduling',
203
- parameters: {
204
- type: 'object' as const,
205
- properties: {
206
- appointment_id: { type: 'string' as const, description: 'Id of the appointment to change (from a search)' },
207
- starts_at: { type: 'string' as const, description: "New LOCAL date-time of the business, NO timezone suffix — e.g. '2026-07-27T16:30'. Omit to keep the current time." },
208
- professional_id: { type: 'string' as const, description: 'Move to another professional (optional)' },
209
- status: {
210
- type: 'string' as const,
211
- description: 'New status — use "cancelled" to cancel',
212
- enum: ['scheduled', 'confirmed', 'in_progress', 'completed', 'cancelled', 'no_show'],
213
- },
214
- notes: { type: 'string' as const, description: 'Optional notes' },
215
- },
216
- required: ['appointment_id'],
217
- },
218
- suggestions: [
219
- { label: 'Remarca o Gustavo para 16:30' },
220
- { label: 'Cancela o atendimento das 10h' },
221
- ],
222
- permission: { feature: 'appointments', action: 'edit' as const },
223
- },
224
- {
225
- id: 'agenda.check-availability',
226
- name: 'checkAvailability',
227
- description: 'Checks available time slots for a professional on a given date.',
228
- icon: 'Clock',
229
- mode: 'read' as const,
230
- category: 'Scheduling',
231
- parameters: {
232
- type: 'object' as const,
233
- properties: {
234
- professional: { type: 'string' as const, description: 'Professional name' },
235
- date: { type: 'string' as const, description: 'Date (YYYY-MM-DD) or "today", "tomorrow"' },
236
- duration: { type: 'number' as const, description: 'Required duration in minutes' },
237
- },
238
- required: ['professional', 'date'],
239
- },
240
- suggestions: [
241
- { label: 'When is Ana available tomorrow?' },
242
- { label: 'Find a 90-minute slot for Carlos this week' },
243
- ],
244
- permission: { feature: 'appointments', action: 'read' as const },
245
- },
246
- ],
247
-
248
- registries,
249
-
250
- settings: [
251
- {
252
- id: 'agenda',
253
- label: 'Agenda',
254
- icon: 'Calendar',
255
- // Standard layout: General | Integrations (Google Calendar addon) | Properties.
256
- component: (() => {
257
- const AgendaSettingsTab: React.FC = () => React.createElement(PluginSettingsPanel, {
258
- title: 'Agenda',
259
- generalSettings: React.createElement(AgendaGeneralSettings),
260
- registries,
261
- hostPluginId: 'agenda',
262
- routeBase: '/settings/agenda',
263
- })
264
- AgendaSettingsTab.displayName = 'AgendaSettingsTab'
265
- return AgendaSettingsTab as unknown as React.ComponentType<unknown>
266
- })(),
267
- order: 5,
268
- permission: { feature: 'appointments', action: 'read' as const },
269
- },
270
- ],
271
- migrations: [
272
- {
273
- id: 'agenda-001-public-booking',
274
- version: '0.3.0',
275
- sql: MIGRATION_001_PUBLIC_BOOKING,
276
- description:
277
- 'Booking read model (v_appointments) + anon public booking surface (v_public_services, get_available_slots, create_public_booking) + order_items duration/assignee columns',
278
- },
279
- ],
280
-
281
- locales: agendaLocales,
282
- }
283
- }
284
-
285
- // Re-export types for consumers
286
- export type { AgendaPluginOptions } from './config'
287
- export type { ResolvedAgendaConfig } from './config'
288
- export type { AgendaDataProvider } from './data/types'
289
- // Seedable in-memory provider — apps pass a seed to render the calendar with
290
- // app-specific events instead of the built-in salon sample.
291
- export { createMockAgendaProvider } from './data/mock'
292
- export type { MockAgendaSeed, MockAgendaProviderOptions } from './data/mock'
293
- export type { AgendaFinancialBridge, BookingPaymentStatus, BookingPaymentSummary, BookingPaymentDetail } from './financial-bridge'
294
- export { createFinancialBridge, computeCommissionAmount } from './bridges/create-financial-bridge'
295
-
296
- // Google Calendar integration (official addon) — settings-only plugin + helpers
297
- export { createGoogleCalendarPlugin, createGoogleCalendarProvider, googleCalendarConnector, bookingToEvent, eventToBookingPatch } from './integrations/google-calendar'
@@ -1,136 +0,0 @@
1
- // Google Calendar integration — settings tab (control plane UI).
2
- //
3
- // Connect/disconnect, pick the target calendar, run a manual inbound sync, and
4
- // see recent runs. Outbound (booking → Google) is automatic via the DB trigger;
5
- // this screen is the only UI surface the integration needs.
6
- import React, { useEffect, useState } from 'react'
7
- import { Calendar, Plug, Power, RefreshCw, CheckCircle2, Loader2, History, AlertCircle } from 'lucide-react'
8
- import { Button, toast } from '@fayz-ai/ui'
9
- import { createGoogleCalendarProvider } from './data/supabase'
10
- import type { CalendarIntegration, CalendarSyncLogEntry } from './types'
11
-
12
- const provider = createGoogleCalendarProvider()
13
-
14
- export function GoogleCalendarSettings() {
15
- const [integration, setIntegration] = useState<CalendarIntegration | null>(null)
16
- const [loading, setLoading] = useState(true)
17
- const [calendarId, setCalendarId] = useState('primary')
18
- const [connecting, setConnecting] = useState(false)
19
- const [syncing, setSyncing] = useState(false)
20
- const [log, setLog] = useState<CalendarSyncLogEntry[]>([])
21
-
22
- async function load() {
23
- setLoading(true)
24
- try {
25
- const found = await provider.getIntegration()
26
- setIntegration(found)
27
- if (found) { setCalendarId(found.calendarId); void provider.getSyncLog().then(setLog) }
28
- } finally { setLoading(false) }
29
- }
30
-
31
- useEffect(() => { void load() }, [])
32
-
33
- async function handleConnect() {
34
- setConnecting(true)
35
- try {
36
- const url = await provider.getConnectUrl()
37
- window.location.href = url // → Google consent, then back to the app
38
- } catch (e: any) {
39
- toast.error(e?.message ?? 'Connect failed')
40
- setConnecting(false)
41
- }
42
- }
43
-
44
- async function handleDisconnect() {
45
- await provider.disconnect()
46
- toast.success('Disconnected')
47
- void load()
48
- }
49
-
50
- async function handleSaveCalendar() {
51
- await provider.setCalendar(calendarId)
52
- toast.success('Saved')
53
- }
54
-
55
- async function handleSyncNow() {
56
- setSyncing(true)
57
- try {
58
- const res = await provider.syncNow()
59
- toast.success(`Synced — ${res.written} updated`)
60
- void provider.getSyncLog().then(setLog)
61
- void load()
62
- } catch (e: any) {
63
- toast.error(e?.message ?? 'Sync failed')
64
- } finally { setSyncing(false) }
65
- }
66
-
67
- const connected = !!integration?.connected
68
-
69
- return (
70
- <div className="space-y-6 max-w-3xl">
71
- <div className="flex items-center gap-2">
72
- <Calendar className="h-5 w-5 text-muted-foreground" />
73
- <div>
74
- <h2 className="text-lg font-semibold tracking-tight">Google Calendar</h2>
75
- <p className="text-sm text-muted-foreground">Two-way sync between your bookings and Google Calendar.</p>
76
- </div>
77
- {connected && (
78
- <span className="ml-auto inline-flex items-center gap-1 text-xs text-success">
79
- <CheckCircle2 className="h-3.5 w-3.5" /> Connected
80
- </span>
81
- )}
82
- </div>
83
-
84
- <section className="rounded-lg border bg-card shadow-sm p-5 space-y-4">
85
- {loading ? (
86
- <div className="py-6 text-center text-sm text-muted-foreground"><Loader2 className="h-4 w-4 animate-spin inline" /></div>
87
- ) : !connected ? (
88
- <div className="text-center py-6">
89
- <p className="text-sm text-muted-foreground mb-3">Connect a Google account to sync bookings both ways.</p>
90
- <Button onClick={handleConnect} disabled={connecting}>
91
- {connecting ? <Loader2 className="h-4 w-4 animate-spin" /> : <Plug className="h-4 w-4" />} Connect Google Calendar
92
- </Button>
93
- </div>
94
- ) : (
95
- <>
96
- <div className="flex flex-wrap items-end gap-3">
97
- <label className="block flex-1 min-w-[200px]">
98
- <span className="text-xs font-medium text-muted-foreground">Calendar ID</span>
99
- <input value={calendarId} onChange={(e) => setCalendarId(e.target.value)} placeholder="primary"
100
- className="w-full mt-1 rounded-input border border-input bg-card px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring" />
101
- </label>
102
- <Button variant="outline" size="sm" onClick={handleSaveCalendar}>Save</Button>
103
- <Button variant="outline" size="sm" onClick={handleSyncNow} disabled={syncing}>
104
- <RefreshCw className={`h-3.5 w-3.5 ${syncing ? 'animate-spin' : ''}`} /> Sync now
105
- </Button>
106
- <Button variant="outline" size="sm" onClick={handleDisconnect}>
107
- <Power className="h-3.5 w-3.5" /> Disconnect
108
- </Button>
109
- </div>
110
- {integration?.lastSyncAt && (
111
- <p className="text-[11px] text-muted-foreground">Last sync: {new Date(integration.lastSyncAt).toLocaleString()}</p>
112
- )}
113
- </>
114
- )}
115
- </section>
116
-
117
- {log.length > 0 && (
118
- <section className="rounded-lg border bg-card shadow-sm p-5 space-y-2">
119
- <div className="flex items-center gap-2"><History className="h-4 w-4 text-muted-foreground" /><h3 className="text-sm font-semibold">History</h3></div>
120
- <div className="rounded-md border divide-y text-sm">
121
- {log.map((r) => (
122
- <div key={r.id} className="flex items-center gap-3 px-3 py-2">
123
- <span className="text-muted-foreground text-xs">{new Date(r.createdAt).toLocaleString()}</span>
124
- <span className="text-[10px] uppercase tracking-wide px-1 py-px rounded bg-muted text-muted-foreground">{r.direction}</span>
125
- <span className="ml-auto text-xs">{r.written} written / {r.fetched} fetched</span>
126
- {r.status === 'success'
127
- ? <CheckCircle2 className="h-3.5 w-3.5 text-success" />
128
- : <AlertCircle className="h-3.5 w-3.5 text-warning" />}
129
- </div>
130
- ))}
131
- </div>
132
- </section>
133
- )}
134
- </div>
135
- )
136
- }
@@ -1,23 +0,0 @@
1
- // A small round swatch tinted with a Google calendar's backgroundColor.
2
- //
3
- // Google's calendarList entries carry a `backgroundColor` hex; we render it as a
4
- // dot next to the calendar summary in the de-para list and as the leading dot of
5
- // the origin badge in the import wizard. Falls back to the muted border when a
6
- // calendar has no color (rare, e.g. some shared calendars).
7
- import React from 'react'
8
-
9
- export function CalendarColorDot({
10
- color,
11
- className = '',
12
- }: {
13
- color?: string | null
14
- className?: string
15
- }) {
16
- return (
17
- <span
18
- className={`inline-block h-2.5 w-2.5 shrink-0 rounded-full border border-black/10 ${className}`}
19
- style={{ backgroundColor: color || 'hsl(var(--muted-foreground))' }}
20
- aria-hidden
21
- />
22
- )
23
- }