@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.
- package/dist/{chunk-DRZF6CV2.js → chunk-Q2PAWBPR.js} +62 -3
- package/dist/chunk-Q2PAWBPR.js.map +1 -0
- package/dist/{chunk-LFUELUIT.cjs → chunk-RX2HMWXS.cjs} +62 -2
- package/dist/chunk-RX2HMWXS.cjs.map +1 -0
- package/dist/components/AgendaGeneralSettings.d.ts.map +1 -1
- package/dist/components/AppointmentModal.d.ts.map +1 -1
- package/dist/components/CalendarToolbar.d.ts.map +1 -1
- package/dist/data/supabase.d.ts +5 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/index.cjs +1078 -768
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +921 -611
- package/dist/index.js.map +1 -1
- package/dist/integrations/google-calendar/components/CalendarColorDot.d.ts +6 -0
- package/dist/integrations/google-calendar/components/CalendarColorDot.d.ts.map +1 -0
- package/dist/integrations/google-calendar/components/ChannelMappingList.d.ts +9 -0
- package/dist/integrations/google-calendar/components/ChannelMappingList.d.ts.map +1 -0
- package/dist/integrations/google-calendar/components/ImportEventsWizard.d.ts +12 -0
- package/dist/integrations/google-calendar/components/ImportEventsWizard.d.ts.map +1 -0
- package/dist/integrations/google-calendar/components/useAgendaLookups.d.ts +26 -0
- package/dist/integrations/google-calendar/components/useAgendaLookups.d.ts.map +1 -0
- package/dist/integrations/google-calendar/connectorDef.d.ts.map +1 -1
- package/dist/integrations/google-calendar/data/supabase.d.ts +33 -1
- package/dist/integrations/google-calendar/data/supabase.d.ts.map +1 -1
- package/dist/integrations/google-calendar/data/tables.d.ts +6 -0
- package/dist/integrations/google-calendar/data/tables.d.ts.map +1 -0
- package/dist/integrations/google-calendar/index.d.ts +1 -1
- package/dist/integrations/google-calendar/index.d.ts.map +1 -1
- package/dist/integrations/google-calendar/mapping.d.ts +56 -4
- package/dist/integrations/google-calendar/mapping.d.ts.map +1 -1
- package/dist/integrations/google-calendar/types.d.ts +101 -0
- package/dist/integrations/google-calendar/types.d.ts.map +1 -1
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +12 -1
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/public/BookingWidget.d.ts.map +1 -1
- package/dist/public/context.d.ts +3 -1
- package/dist/public/context.d.ts.map +1 -1
- package/dist/public/createPublicBookingPlugin.d.ts +7 -1
- package/dist/public/createPublicBookingPlugin.d.ts.map +1 -1
- package/dist/public/data.d.ts +6 -0
- package/dist/public/data.d.ts.map +1 -1
- package/dist/public/data.supabase.d.ts.map +1 -1
- package/dist/public/format.d.ts +22 -1
- package/dist/public/format.d.ts.map +1 -1
- package/dist/public/hooks.d.ts.map +1 -1
- package/dist/public/index.cjs +31 -63
- package/dist/public/index.cjs.map +1 -1
- package/dist/public/index.d.ts +1 -1
- package/dist/public/index.d.ts.map +1 -1
- package/dist/public/index.js +27 -61
- package/dist/public/index.js.map +1 -1
- package/dist/public/steps/ContactStep.d.ts +4 -2
- package/dist/public/steps/ContactStep.d.ts.map +1 -1
- package/dist/public/types.d.ts +16 -0
- package/dist/public/types.d.ts.map +1 -1
- package/dist/views/CalendarView.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/components/AgendaGeneralSettings.tsx +38 -16
- package/src/components/AppointmentModal.tsx +50 -108
- package/src/components/CalendarToolbar.tsx +10 -7
- package/src/data/supabase.ts +67 -13
- package/src/index.ts +56 -8
- package/src/integrations/google-calendar/components/CalendarColorDot.tsx +23 -0
- package/src/integrations/google-calendar/components/ChannelMappingList.tsx +329 -0
- package/src/integrations/google-calendar/components/ImportEventsWizard.tsx +215 -0
- package/src/integrations/google-calendar/components/useAgendaLookups.ts +100 -0
- package/src/integrations/google-calendar/connectorDef.tsx +123 -40
- package/src/integrations/google-calendar/data/supabase.ts +109 -13
- package/src/integrations/google-calendar/data/tables.ts +12 -0
- package/src/integrations/google-calendar/index.ts +10 -1
- package/src/integrations/google-calendar/integration.test.ts +126 -1
- package/src/integrations/google-calendar/mapping.ts +149 -9
- package/src/integrations/google-calendar/migrations/001_google_calendar.sql +9 -6
- package/src/integrations/google-calendar/migrations/002_smart_sync.sql +517 -0
- package/src/integrations/google-calendar/migrations/003_plg_rename.sql +273 -0
- package/src/integrations/google-calendar/types.ts +111 -0
- package/src/locales/en.ts +1 -0
- package/src/locales/pt-BR.ts +1 -0
- package/src/migrations/001_public_booking.sql +7 -2
- package/src/migrations/003_public_client_kind.sql +175 -0
- package/src/migrations/004_public_booking_assignee.sql +176 -0
- package/src/migrations/005_agent_rpcs.sql +168 -0
- package/src/migrations/006_agent_rpc_localtime.sql +181 -0
- package/src/migrations/007_schedule_exception_precedence.sql +290 -0
- package/src/migrations/008_agent_rpc_exception_precedence.sql +172 -0
- package/src/migrations/009_public_booking_location.sql +162 -0
- package/src/migrations/010_public_service_curation.sql +31 -0
- package/src/migrations/011_default_assignee.sql +167 -0
- package/src/migrations/012_drop_legacy_booking_overload.sql +29 -0
- package/src/migrations/013_normalize_v_appointments.sql +199 -0
- package/src/migrations/index.ts +1791 -3
- package/src/public/BookingWidget.tsx +10 -1
- package/src/public/context.tsx +3 -0
- package/src/public/createPublicBookingPlugin.ts +14 -1
- package/src/public/data.supabase.ts +7 -4
- package/src/public/data.ts +6 -0
- package/src/public/format.ts +36 -1
- package/src/public/hooks.ts +7 -2
- package/src/public/index.ts +1 -0
- package/src/public/steps/ContactStep.tsx +10 -5
- package/src/public/types.ts +21 -0
- package/src/views/CalendarView.tsx +15 -1
- package/dist/chunk-3LJKZQK4.js +0 -12
- package/dist/chunk-3LJKZQK4.js.map +0 -1
- package/dist/chunk-DRZF6CV2.js.map +0 -1
- package/dist/chunk-LFUELUIT.cjs.map +0 -1
- package/dist/chunk-SYDBDI3Q.cjs +0 -15
- package/dist/chunk-SYDBDI3Q.cjs.map +0 -1
- package/dist/components/SearchCombobox.d.ts +0 -38
- package/dist/components/SearchCombobox.d.ts.map +0 -1
- package/dist/tenant-222EHYUC.cjs +0 -16
- package/dist/tenant-222EHYUC.cjs.map +0 -1
- package/dist/tenant-LAUWVUYB.js +0 -3
- package/dist/tenant-LAUWVUYB.js.map +0 -1
- package/src/components/SearchCombobox.tsx +0 -216
|
@@ -1,17 +1,38 @@
|
|
|
1
|
-
// Booking ↔ Google Calendar event mapping
|
|
1
|
+
// Booking ↔ Google Calendar event mapping — the SINGLE SOURCE OF TRUTH.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
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
|
-
|
|
12
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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.
|
|
30
|
-
CREATE INDEX IF NOT EXISTS
|
|
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.
|
|
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.
|
|
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['
|
|
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
|