@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
@@ -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
- }