@fayz-ai/plugin-agenda 0.8.1 → 0.8.4

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 (118) hide show
  1. package/dist/{chunk-DRZF6CV2.js → chunk-Q2PAWBPR.js} +62 -3
  2. package/dist/chunk-Q2PAWBPR.js.map +1 -0
  3. package/dist/{chunk-LFUELUIT.cjs → chunk-RX2HMWXS.cjs} +62 -2
  4. package/dist/chunk-RX2HMWXS.cjs.map +1 -0
  5. package/dist/components/AgendaGeneralSettings.d.ts.map +1 -1
  6. package/dist/components/AppointmentModal.d.ts.map +1 -1
  7. package/dist/components/CalendarToolbar.d.ts.map +1 -1
  8. package/dist/data/supabase.d.ts +5 -1
  9. package/dist/data/supabase.d.ts.map +1 -1
  10. package/dist/index.cjs +1078 -768
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +921 -611
  14. package/dist/index.js.map +1 -1
  15. package/dist/integrations/google-calendar/components/CalendarColorDot.d.ts +6 -0
  16. package/dist/integrations/google-calendar/components/CalendarColorDot.d.ts.map +1 -0
  17. package/dist/integrations/google-calendar/components/ChannelMappingList.d.ts +9 -0
  18. package/dist/integrations/google-calendar/components/ChannelMappingList.d.ts.map +1 -0
  19. package/dist/integrations/google-calendar/components/ImportEventsWizard.d.ts +12 -0
  20. package/dist/integrations/google-calendar/components/ImportEventsWizard.d.ts.map +1 -0
  21. package/dist/integrations/google-calendar/components/useAgendaLookups.d.ts +26 -0
  22. package/dist/integrations/google-calendar/components/useAgendaLookups.d.ts.map +1 -0
  23. package/dist/integrations/google-calendar/connectorDef.d.ts.map +1 -1
  24. package/dist/integrations/google-calendar/data/supabase.d.ts +33 -1
  25. package/dist/integrations/google-calendar/data/supabase.d.ts.map +1 -1
  26. package/dist/integrations/google-calendar/data/tables.d.ts +6 -0
  27. package/dist/integrations/google-calendar/data/tables.d.ts.map +1 -0
  28. package/dist/integrations/google-calendar/index.d.ts +1 -1
  29. package/dist/integrations/google-calendar/index.d.ts.map +1 -1
  30. package/dist/integrations/google-calendar/mapping.d.ts +56 -4
  31. package/dist/integrations/google-calendar/mapping.d.ts.map +1 -1
  32. package/dist/integrations/google-calendar/types.d.ts +101 -0
  33. package/dist/integrations/google-calendar/types.d.ts.map +1 -1
  34. package/dist/locales/en.d.ts.map +1 -1
  35. package/dist/locales/pt-BR.d.ts.map +1 -1
  36. package/dist/migrations/index.d.ts +12 -1
  37. package/dist/migrations/index.d.ts.map +1 -1
  38. package/dist/public/BookingWidget.d.ts.map +1 -1
  39. package/dist/public/context.d.ts +3 -1
  40. package/dist/public/context.d.ts.map +1 -1
  41. package/dist/public/createPublicBookingPlugin.d.ts +7 -1
  42. package/dist/public/createPublicBookingPlugin.d.ts.map +1 -1
  43. package/dist/public/data.d.ts +6 -0
  44. package/dist/public/data.d.ts.map +1 -1
  45. package/dist/public/data.supabase.d.ts.map +1 -1
  46. package/dist/public/format.d.ts +22 -1
  47. package/dist/public/format.d.ts.map +1 -1
  48. package/dist/public/hooks.d.ts.map +1 -1
  49. package/dist/public/index.cjs +31 -63
  50. package/dist/public/index.cjs.map +1 -1
  51. package/dist/public/index.d.ts +1 -1
  52. package/dist/public/index.d.ts.map +1 -1
  53. package/dist/public/index.js +27 -61
  54. package/dist/public/index.js.map +1 -1
  55. package/dist/public/steps/ContactStep.d.ts +4 -2
  56. package/dist/public/steps/ContactStep.d.ts.map +1 -1
  57. package/dist/public/types.d.ts +16 -0
  58. package/dist/public/types.d.ts.map +1 -1
  59. package/dist/views/CalendarView.d.ts.map +1 -1
  60. package/package.json +5 -5
  61. package/src/components/AgendaGeneralSettings.tsx +38 -16
  62. package/src/components/AppointmentModal.tsx +50 -108
  63. package/src/components/CalendarToolbar.tsx +10 -7
  64. package/src/data/supabase.ts +67 -13
  65. package/src/index.ts +56 -8
  66. package/src/integrations/google-calendar/components/CalendarColorDot.tsx +23 -0
  67. package/src/integrations/google-calendar/components/ChannelMappingList.tsx +329 -0
  68. package/src/integrations/google-calendar/components/ImportEventsWizard.tsx +215 -0
  69. package/src/integrations/google-calendar/components/useAgendaLookups.ts +100 -0
  70. package/src/integrations/google-calendar/connectorDef.tsx +123 -40
  71. package/src/integrations/google-calendar/data/supabase.ts +109 -13
  72. package/src/integrations/google-calendar/data/tables.ts +12 -0
  73. package/src/integrations/google-calendar/index.ts +10 -1
  74. package/src/integrations/google-calendar/integration.test.ts +126 -1
  75. package/src/integrations/google-calendar/mapping.ts +149 -9
  76. package/src/integrations/google-calendar/migrations/001_google_calendar.sql +9 -6
  77. package/src/integrations/google-calendar/migrations/002_smart_sync.sql +517 -0
  78. package/src/integrations/google-calendar/migrations/003_plg_rename.sql +273 -0
  79. package/src/integrations/google-calendar/types.ts +111 -0
  80. package/src/locales/en.ts +1 -0
  81. package/src/locales/pt-BR.ts +1 -0
  82. package/src/migrations/001_public_booking.sql +7 -2
  83. package/src/migrations/003_public_client_kind.sql +175 -0
  84. package/src/migrations/004_public_booking_assignee.sql +176 -0
  85. package/src/migrations/005_agent_rpcs.sql +168 -0
  86. package/src/migrations/006_agent_rpc_localtime.sql +181 -0
  87. package/src/migrations/007_schedule_exception_precedence.sql +290 -0
  88. package/src/migrations/008_agent_rpc_exception_precedence.sql +172 -0
  89. package/src/migrations/009_public_booking_location.sql +162 -0
  90. package/src/migrations/010_public_service_curation.sql +31 -0
  91. package/src/migrations/011_default_assignee.sql +167 -0
  92. package/src/migrations/012_drop_legacy_booking_overload.sql +29 -0
  93. package/src/migrations/013_normalize_v_appointments.sql +199 -0
  94. package/src/migrations/index.ts +1791 -3
  95. package/src/public/BookingWidget.tsx +10 -1
  96. package/src/public/context.tsx +3 -0
  97. package/src/public/createPublicBookingPlugin.ts +14 -1
  98. package/src/public/data.supabase.ts +7 -4
  99. package/src/public/data.ts +6 -0
  100. package/src/public/format.ts +36 -1
  101. package/src/public/hooks.ts +7 -2
  102. package/src/public/index.ts +1 -0
  103. package/src/public/steps/ContactStep.tsx +10 -5
  104. package/src/public/types.ts +21 -0
  105. package/src/views/CalendarView.tsx +15 -1
  106. package/dist/chunk-3LJKZQK4.js +0 -12
  107. package/dist/chunk-3LJKZQK4.js.map +0 -1
  108. package/dist/chunk-DRZF6CV2.js.map +0 -1
  109. package/dist/chunk-LFUELUIT.cjs.map +0 -1
  110. package/dist/chunk-SYDBDI3Q.cjs +0 -15
  111. package/dist/chunk-SYDBDI3Q.cjs.map +0 -1
  112. package/dist/components/SearchCombobox.d.ts +0 -38
  113. package/dist/components/SearchCombobox.d.ts.map +0 -1
  114. package/dist/tenant-222EHYUC.cjs +0 -16
  115. package/dist/tenant-222EHYUC.cjs.map +0 -1
  116. package/dist/tenant-LAUWVUYB.js +0 -3
  117. package/dist/tenant-LAUWVUYB.js.map +0 -1
  118. package/src/components/SearchCombobox.tsx +0 -216
@@ -1,17 +1,38 @@
1
- // Booking ↔ Google Calendar event mapping (pure helpers).
1
+ // Booking ↔ Google Calendar event mapping — the SINGLE SOURCE OF TRUTH.
2
2
  //
3
- // Shared shape used both by the SDK (docs/tests) and mirrored by the edge
4
- // function. Keeping it pure makes the field decisions reviewable in one place.
3
+ // These helpers are pure and Deno-friendly (only `import type`, no runtime
4
+ // node/react/https imports), so the Supabase edge function imports this exact
5
+ // file via a relative path (`../../src/integrations/google-calendar/mapping.ts`)
6
+ // instead of keeping a divergent mirror. `import type` is elided by the Deno /
7
+ // esbuild bundler, so the plugin `types.ts`/`CalendarBooking` type imports never
8
+ // become runtime dependencies of the function bundle.
9
+ //
10
+ // Two callers, two input shapes:
11
+ // • the SDK/UI works with the rich `CalendarBooking` (camelCase, joined) — see
12
+ // bookingToEvent / eventToBookingPatch;
13
+ // • the edge function + RPCs work with the raw `AppointmentRow` (snake_case DB
14
+ // columns) — see appointmentRowToEvent / eventToAppointmentPatch.
15
+ // Field decisions (title, all-day, cancel) are made once, here.
5
16
  import type { CalendarBooking } from '../../types'
6
- import type { GCalEvent } from './types'
17
+ import type { GCalEvent, AppointmentRow, CalendarChannel, CalendarChannelRow } from './types'
7
18
 
8
19
  export interface GCalEventInput {
9
20
  summary: string
10
21
  description?: string
11
- start: { dateTime: string; timeZone?: string }
12
- end: { dateTime: string; timeZone?: string }
22
+ // Timed events use dateTime; all-day events use date (YYYY-MM-DD).
23
+ start: { dateTime?: string; date?: string; timeZone?: string }
24
+ end: { dateTime?: string; date?: string; timeZone?: string }
25
+ }
26
+
27
+ /** Normalized time window read off a Google event, all-day aware. */
28
+ export interface EventTimes {
29
+ startsAt: string
30
+ endsAt: string
31
+ allDay: boolean
13
32
  }
14
33
 
34
+ // --- Booking (rich SDK shape) → event -------------------------------------
35
+
15
36
  /** Build the event title from the booking's services + client. */
16
37
  export function bookingSummary(b: Pick<CalendarBooking, 'services' | 'clientName'>): string {
17
38
  const services = (b.services ?? []).map((s) => s.name).filter(Boolean).join(', ')
@@ -33,11 +54,130 @@ export function bookingToEvent(b: CalendarBooking, timeZone?: string): GCalEvent
33
54
  }
34
55
  }
35
56
 
36
- /** Map an inbound Google event change to a partial booking update (reschedule/cancel). */
57
+ /** Map an inbound Google event change to a partial booking update (reschedule/cancel), all-day aware. */
37
58
  export function eventToBookingPatch(ev: GCalEvent): { startsAt?: string; endsAt?: string; status?: 'cancelled' } {
38
59
  if (ev.status === 'cancelled') return { status: 'cancelled' }
60
+ const t = eventTimes(ev)
61
+ return { startsAt: t.startsAt, endsAt: t.endsAt }
62
+ }
63
+
64
+ // --- Appointment row (raw DB shape) → event -------------------------------
65
+
66
+ /**
67
+ * Build the event title from an appointments row's denormalized metadata.
68
+ * The raw row has no joined services/client, so we read the names the app
69
+ * stamps onto metadata (serviceNames, contactName/clientName).
70
+ */
71
+ export function appointmentRowSummary(row: AppointmentRow): string {
72
+ const m = row.metadata ?? {}
73
+ const services = (m.serviceNames as string) ?? ''
74
+ const who = (m.contactName as string) ?? (m.clientName as string) ?? ''
75
+ if (services && who) return `${services} — ${who}`
76
+ return services || who || 'Appointment'
77
+ }
78
+
79
+ /** Map a raw appointments row to a Google event input (outbound push from the edge function). */
80
+ export function appointmentRowToEvent(row: AppointmentRow): GCalEventInput {
39
81
  return {
40
- startsAt: ev.start?.dateTime,
41
- endsAt: ev.end?.dateTime,
82
+ summary: appointmentRowSummary(row),
83
+ description: row.notes ?? undefined,
84
+ start: { dateTime: row.starts_at },
85
+ end: { dateTime: row.ends_at ?? row.starts_at },
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Map an inbound Google event to a DB-friendly appointment patch (snake_case
91
+ * times), all-day aware, cancellation included. Consumed by the edge function
92
+ * when calling gcal_apply_event_patch.
93
+ */
94
+ export function eventToAppointmentPatch(ev: GCalEvent): {
95
+ starts_at?: string
96
+ ends_at?: string
97
+ summary?: string
98
+ allDay?: boolean
99
+ cancelled: boolean
100
+ } {
101
+ if (ev.status === 'cancelled') return { cancelled: true }
102
+ const t = eventTimes(ev)
103
+ return { starts_at: t.startsAt, ends_at: t.endsAt, summary: ev.summary, allDay: t.allDay, cancelled: false }
104
+ }
105
+
106
+ // --- Shared time normalization (all-day aware) ----------------------------
107
+
108
+ /**
109
+ * Read a normalized {startsAt, endsAt, allDay} window off a Google event.
110
+ * All-day events carry `start.date`/`end.date` (YYYY-MM-DD, end exclusive) and
111
+ * no dateTime; timed events carry dateTime. Falls back start→end when one side
112
+ * is missing so callers always get a usable pair.
113
+ */
114
+ export function eventTimes(ev: GCalEvent): EventTimes {
115
+ const allDay = !ev.start?.dateTime && !!ev.start?.date
116
+ const startsAt = ev.start?.dateTime ?? ev.start?.date ?? ''
117
+ const endsAt = ev.end?.dateTime ?? ev.end?.date ?? startsAt
118
+ return { startsAt, endsAt, allDay }
119
+ }
120
+
121
+ // --- Channel resolution (outbound destination priority) -------------------
122
+
123
+ /** Convert a raw plg_calendar_channels row (snake_case) to the camelCase CalendarChannel. */
124
+ export function channelRowToChannel(r: CalendarChannelRow): CalendarChannel {
125
+ return {
126
+ id: r.id,
127
+ integrationId: r.integration_id,
128
+ tenantId: r.tenant_id,
129
+ googleCalendarId: r.google_calendar_id,
130
+ summary: r.summary ?? null,
131
+ color: r.color ?? null,
132
+ direction: r.direction,
133
+ targetKind: r.target_kind ?? null,
134
+ targetId: r.target_id ?? null,
135
+ importMode: r.import_mode ?? 'appointment',
136
+ syncToken: r.sync_token ?? null,
137
+ channelId: r.channel_id ?? null,
138
+ resourceId: r.resource_id ?? null,
139
+ channelExpiresAt: r.channel_expires_at ?? null,
140
+ isActive: r.is_active ?? true,
141
+ createdAt: r.created_at ?? '',
142
+ updatedAt: r.updated_at ?? '',
143
+ }
144
+ }
145
+
146
+ export interface ChannelMatchInput {
147
+ assigneeId?: string | null
148
+ serviceIds?: string[]
149
+ locationId?: string | null
150
+ }
151
+
152
+ /**
153
+ * Pick the outbound destination channel for a booking, by priority:
154
+ * 1. a channel targeting the booking's assignee,
155
+ * 2. else a channel targeting one of the booking's services,
156
+ * 3. else a channel targeting the booking's location,
157
+ * 4. else the whole-agenda channel (targetKind === null).
158
+ * Only active outbound/bidirectional channels are eligible. Returns null when
159
+ * nothing matches (caller falls back to the integration's default calendar).
160
+ */
161
+ export function resolveTargetChannel(
162
+ channels: CalendarChannel[],
163
+ booking: ChannelMatchInput,
164
+ ): CalendarChannel | null {
165
+ const eligible = channels.filter(
166
+ (c) => c.isActive && (c.direction === 'outbound' || c.direction === 'bidirectional'),
167
+ )
168
+ const serviceIds = booking.serviceIds ?? []
169
+
170
+ if (booking.assigneeId) {
171
+ const byAssignee = eligible.find((c) => c.targetKind === 'assignee' && c.targetId === booking.assigneeId)
172
+ if (byAssignee) return byAssignee
173
+ }
174
+ if (serviceIds.length) {
175
+ const byService = eligible.find((c) => c.targetKind === 'service' && !!c.targetId && serviceIds.includes(c.targetId))
176
+ if (byService) return byService
177
+ }
178
+ if (booking.locationId) {
179
+ const byLocation = eligible.find((c) => c.targetKind === 'location' && c.targetId === booking.locationId)
180
+ if (byLocation) return byLocation
42
181
  }
182
+ return eligible.find((c) => c.targetKind === null) ?? null
43
183
  }
@@ -7,7 +7,10 @@
7
7
  -- watch-channel webhook. The booking↔event link is stored on
8
8
  -- public.appointments.metadata.googleCalendarEventId (no schema change there).
9
9
 
10
- CREATE TABLE IF NOT EXISTS public.calendar_integrations (
10
+ -- Plugin-owned tables carry the platform `plg_<plugin>_` prefix (DATA-MODEL.md
11
+ -- Ring 1). Legacy pools provisioned under the old `calendar_*` names are migrated
12
+ -- by 003_plg_rename.sql; fresh installs are born with the prefixed names here.
13
+ CREATE TABLE IF NOT EXISTS public.plg_calendar_integrations (
11
14
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
12
15
  tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
13
16
  provider text NOT NULL DEFAULT 'google',
@@ -26,10 +29,10 @@ CREATE TABLE IF NOT EXISTS public.calendar_integrations (
26
29
  updated_at timestamptz NOT NULL DEFAULT now(),
27
30
  UNIQUE (tenant_id, provider)
28
31
  );
29
- ALTER TABLE public.calendar_integrations ENABLE ROW LEVEL SECURITY;
30
- CREATE INDEX IF NOT EXISTS idx_calendar_integrations_tenant ON public.calendar_integrations(tenant_id);
32
+ ALTER TABLE public.plg_calendar_integrations ENABLE ROW LEVEL SECURITY;
33
+ CREATE INDEX IF NOT EXISTS idx_plg_calendar_integrations_tenant ON public.plg_calendar_integrations(tenant_id);
31
34
 
32
- CREATE TABLE IF NOT EXISTS public.calendar_sync_log (
35
+ CREATE TABLE IF NOT EXISTS public.plg_calendar_sync_log (
33
36
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
34
37
  tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
35
38
  direction text NOT NULL, -- inbound | outbound
@@ -40,13 +43,13 @@ CREATE TABLE IF NOT EXISTS public.calendar_sync_log (
40
43
  error text,
41
44
  created_at timestamptz NOT NULL DEFAULT now()
42
45
  );
43
- ALTER TABLE public.calendar_sync_log ENABLE ROW LEVEL SECURITY;
46
+ ALTER TABLE public.plg_calendar_sync_log ENABLE ROW LEVEL SECURITY;
44
47
 
45
48
  -- RLS: tenant isolation.
46
49
  DO $$
47
50
  DECLARE t text;
48
51
  BEGIN
49
- FOR t IN SELECT unnest(ARRAY['calendar_integrations','calendar_sync_log'])
52
+ FOR t IN SELECT unnest(ARRAY['plg_calendar_integrations','plg_calendar_sync_log'])
50
53
  LOOP
51
54
  EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON public.%I TO authenticated', t);
52
55
  IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE schemaname='public' AND tablename=t AND policyname=t||'_rls') THEN