@fayz-ai/plugin-agenda 0.1.3 → 0.1.5

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 (34) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.cjs +412 -201
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +254 -48
  7. package/dist/index.js.map +1 -1
  8. package/dist/integrations/google-calendar/GoogleCalendarSettings.d.ts +3 -0
  9. package/dist/integrations/google-calendar/GoogleCalendarSettings.d.ts.map +1 -0
  10. package/dist/integrations/google-calendar/connector.d.ts +3 -0
  11. package/dist/integrations/google-calendar/connector.d.ts.map +1 -0
  12. package/dist/integrations/google-calendar/connectorDef.d.ts +3 -0
  13. package/dist/integrations/google-calendar/connectorDef.d.ts.map +1 -0
  14. package/dist/integrations/google-calendar/data/supabase.d.ts +16 -0
  15. package/dist/integrations/google-calendar/data/supabase.d.ts.map +1 -0
  16. package/dist/integrations/google-calendar/index.d.ts +8 -0
  17. package/dist/integrations/google-calendar/index.d.ts.map +1 -0
  18. package/dist/integrations/google-calendar/integration.test.d.ts +2 -0
  19. package/dist/integrations/google-calendar/integration.test.d.ts.map +1 -0
  20. package/dist/integrations/google-calendar/mapping.d.ts +25 -0
  21. package/dist/integrations/google-calendar/mapping.d.ts.map +1 -0
  22. package/dist/integrations/google-calendar/types.d.ts +36 -0
  23. package/dist/integrations/google-calendar/types.d.ts.map +1 -0
  24. package/package.json +10 -7
  25. package/src/index.ts +12 -2
  26. package/src/integrations/google-calendar/GoogleCalendarSettings.tsx +136 -0
  27. package/src/integrations/google-calendar/connector.ts +20 -0
  28. package/src/integrations/google-calendar/connectorDef.tsx +94 -0
  29. package/src/integrations/google-calendar/data/supabase.ts +74 -0
  30. package/src/integrations/google-calendar/index.ts +36 -0
  31. package/src/integrations/google-calendar/integration.test.ts +74 -0
  32. package/src/integrations/google-calendar/mapping.ts +43 -0
  33. package/src/integrations/google-calendar/migrations/001_google_calendar.sql +94 -0
  34. package/src/integrations/google-calendar/types.ts +31 -0
package/dist/index.d.ts CHANGED
@@ -6,4 +6,5 @@ export type { ResolvedAgendaConfig } from './config';
6
6
  export type { AgendaDataProvider } from './data/types';
7
7
  export type { AgendaFinancialBridge, BookingPaymentStatus, BookingPaymentSummary, BookingPaymentDetail } from './financial-bridge';
8
8
  export { createFinancialBridge, computeCommissionAmount } from './bridges/create-financial-bridge';
9
+ export { createGoogleCalendarPlugin, createGoogleCalendarProvider, googleCalendarConnector, bookingToEvent, eventToBookingPatch } from './integrations/google-calendar';
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAgBnD,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,cAAc,CAyJhF;AAGD,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AACnD,YAAY,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AACpD,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACtD,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAClI,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAgBnD,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,cAAc,CAgKhF;AAGD,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AACnD,YAAY,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AACpD,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACtD,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAClI,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAA;AAGlG,OAAO,EAAE,0BAA0B,EAAE,4BAA4B,EAAE,uBAAuB,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAA"}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { setAgendaTenantId, getAgendaTenantId } from './chunk-3LJKZQK4.js';
2
- import React10, { useRef, useState, useCallback, useMemo, useEffect } from 'react';
2
+ import React11, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
3
3
  import { getSupabaseClientOptional, registerTranslations, createSafeDataProvider, usePluginRuntimeOptional, useTranslation, getActiveTenantId, getCurrentLocale } from '@fayz-ai/core';
4
- import { createPluginContext, setScheduleBlockConfig, getScheduleBlockConfig, dedup, SettingsGroup, ToggleRow, SelectRow, usePluginPrefs, PersonLink, CrudFormPage, CrudDetailPage } from '@fayz-ai/saas';
4
+ import { createPluginContext, setScheduleBlockConfig, getScheduleBlockConfig, PluginSettingsPanel, dedup, SettingsGroup, ToggleRow, SelectRow, usePluginPrefs, PersonLink, CrudFormPage, CrudDetailPage } from '@fayz-ai/saas';
5
5
  import FullCalendar from '@fullcalendar/react';
6
6
  import dayGridPlugin from '@fullcalendar/daygrid';
7
7
  import timeGridPlugin from '@fullcalendar/timegrid';
@@ -9,10 +9,10 @@ import resourceTimeGridPlugin from '@fullcalendar/resource-timegrid';
9
9
  import interactionPlugin from '@fullcalendar/interaction';
10
10
  import listPlugin from '@fullcalendar/list';
11
11
  import ptBrLocale from '@fullcalendar/core/locales/pt-br';
12
- import { Plus, ChevronDown, ChevronLeft, ChevronRight, Settings, Pencil, Trash2, X, Clock, User, MapPin, FileText, AlertTriangle, Check, DollarSign, HandCoins, Ban, CheckSquare, Calendar, Briefcase, CircleDot, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, ExternalLink, Search } from 'lucide-react';
13
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
12
+ import { RefreshCw, History, CheckCircle2, AlertCircle, Plus, ChevronDown, ChevronLeft, ChevronRight, Settings, Pencil, Trash2, X, Clock, User, MapPin, FileText, AlertTriangle, Check, DollarSign, HandCoins, Ban, CheckSquare, Calendar, Briefcase, CircleDot, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, ExternalLink, Search } from 'lucide-react';
13
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
14
14
  import { createPortal } from 'react-dom';
15
- import { toast, Button, ConfirmDialog, Modal, ModalContent, ModalTitle, cn, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '@fayz-ai/ui';
15
+ import { Button, toast, ConfirmDialog, Modal, ModalContent, ModalTitle, cn, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '@fayz-ai/ui';
16
16
  import { createStore } from 'zustand/vanilla';
17
17
 
18
18
  // src/config.ts
@@ -418,7 +418,7 @@ function toDateLocale(locale) {
418
418
  }
419
419
  function MiniCalendar({ selectedDate, onDateSelect, onMonthChange }) {
420
420
  const locale = getCurrentLocale();
421
- const [viewMonth, setViewMonth] = React10.useState(() => new Date(selectedDate.getFullYear(), selectedDate.getMonth(), 1));
421
+ const [viewMonth, setViewMonth] = React11.useState(() => new Date(selectedDate.getFullYear(), selectedDate.getMonth(), 1));
422
422
  const monthLabel = viewMonth.toLocaleDateString(toDateLocale(locale), { month: "long", year: "numeric" });
423
423
  const dayLabels = DAY_LABELS[locale] ?? DAY_LABELS.en;
424
424
  const weeks = useMemo(() => {
@@ -1236,7 +1236,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1236
1236
  const t = useTranslation();
1237
1237
  const locale = getCurrentLocale();
1238
1238
  const config = useAgendaConfig();
1239
- const provider = useAgendaProvider();
1239
+ const provider2 = useAgendaProvider();
1240
1240
  const initialSnapshot = useRef("");
1241
1241
  const createBooking = useAgendaStore((s) => s.createBooking);
1242
1242
  const updateBooking = useAgendaStore((s) => s.updateBooking);
@@ -1340,7 +1340,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1340
1340
  useEffect(() => {
1341
1341
  if (!open || mode !== "edit" || !bookingId) return;
1342
1342
  setLoading(true);
1343
- provider.getBookingById(bookingId).then((b) => {
1343
+ provider2.getBookingById(bookingId).then((b) => {
1344
1344
  if (!b) {
1345
1345
  setLoading(false);
1346
1346
  return;
@@ -1459,7 +1459,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1459
1459
  return;
1460
1460
  }
1461
1461
  const timer = setTimeout(() => {
1462
- provider.checkConflict({
1462
+ provider2.checkConflict({
1463
1463
  assigneeId: professionalId,
1464
1464
  startsAt: `${date}T${startTime}:00`,
1465
1465
  endsAt: `${date}T${endTime}:00`,
@@ -1654,8 +1654,8 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1654
1654
  onCancel: () => setClientEditing(false),
1655
1655
  onSubmit: async (data) => {
1656
1656
  try {
1657
- const { getSupabaseClientOptional: getSupabaseClientOptional3 } = await import('@fayz-ai/core');
1658
- const supabase = getSupabaseClientOptional3();
1657
+ const { getSupabaseClientOptional: getSupabaseClientOptional4 } = await import('@fayz-ai/core');
1658
+ const supabase = getSupabaseClientOptional4();
1659
1659
  if (!supabase) return;
1660
1660
  const dbData = {};
1661
1661
  for (const [k, v] of Object.entries(data)) {
@@ -1802,9 +1802,9 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1802
1802
  onClick: async () => {
1803
1803
  setCreatingClient(true);
1804
1804
  try {
1805
- const { getSupabaseClientOptional: getSupabaseClientOptional3 } = await import('@fayz-ai/core');
1805
+ const { getSupabaseClientOptional: getSupabaseClientOptional4 } = await import('@fayz-ai/core');
1806
1806
  const { getAgendaTenantId: getAgendaTenantId2 } = await import('./tenant-LAUWVUYB.js');
1807
- const supabase = getSupabaseClientOptional3();
1807
+ const supabase = getSupabaseClientOptional4();
1808
1808
  const tenantId = getAgendaTenantId2();
1809
1809
  if (!supabase || !tenantId) throw new Error("Not initialized");
1810
1810
  const { data: personRow, error: personError } = await supabase.schema("saas_core").from("persons").insert({
@@ -1827,8 +1827,8 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1827
1827
  setClientSearch(personRow.name);
1828
1828
  setQuickCreate(false);
1829
1829
  } catch (err) {
1830
- const { toast: toast2 } = await import('@fayz-ai/ui');
1831
- toast2.error(t("agenda.appointment.createClientFailed"), { description: err?.message });
1830
+ const { toast: toast3 } = await import('@fayz-ai/ui');
1831
+ toast3.error(t("agenda.appointment.createClientFailed"), { description: err?.message });
1832
1832
  }
1833
1833
  setCreatingClient(false);
1834
1834
  },
@@ -3179,14 +3179,14 @@ function CalendarView() {
3179
3179
  )
3180
3180
  ] });
3181
3181
  }
3182
- function AgendaPage({ config, provider, store }) {
3182
+ function AgendaPage({ config, provider: provider2, store }) {
3183
3183
  const runtime = usePluginRuntimeOptional();
3184
3184
  const tenantId = runtime?.context.tenant?.id;
3185
- React10.useEffect(() => {
3185
+ React11.useEffect(() => {
3186
3186
  setAgendaTenantId(tenantId);
3187
3187
  return () => setAgendaTenantId(void 0);
3188
3188
  }, [tenantId]);
3189
- return /* @__PURE__ */ jsx(AgendaContextProvider, { config, provider, store, children: /* @__PURE__ */ jsx(CalendarView, {}) });
3189
+ return /* @__PURE__ */ jsx(AgendaContextProvider, { config, provider: provider2, store, children: /* @__PURE__ */ jsx(CalendarView, {}) });
3190
3190
  }
3191
3191
  function getTenantId() {
3192
3192
  return getAgendaTenantId() ?? getActiveTenantId();
@@ -4046,7 +4046,7 @@ function getCurrentBookingQuery(state, dateRange = state.dateRange) {
4046
4046
  statuses: state.selectedStatuses.length ? state.selectedStatuses : void 0
4047
4047
  };
4048
4048
  }
4049
- function createAgendaStore(provider, financialBridge) {
4049
+ function createAgendaStore(provider2, financialBridge) {
4050
4050
  return createStore((set, get) => ({
4051
4051
  bookings: [],
4052
4052
  bookingsLoading: false,
@@ -4070,7 +4070,7 @@ function createAgendaStore(provider, financialBridge) {
4070
4070
  set({ bookingsLoading: true, dateRange: range });
4071
4071
  const state = get();
4072
4072
  const query = getCurrentBookingQuery(state, range);
4073
- let bookings = await provider.getBookings(query);
4073
+ let bookings = await provider2.getBookings(query);
4074
4074
  if (financialBridge) {
4075
4075
  const orderIds = bookings.map((b) => b.orderId).filter((id) => !!id);
4076
4076
  if (orderIds.length > 0) {
@@ -4091,14 +4091,14 @@ function createAgendaStore(provider, financialBridge) {
4091
4091
  async fetchProfessionals() {
4092
4092
  return dedup("agenda:professionals", async () => {
4093
4093
  set({ professionalsLoading: true });
4094
- const professionals = await provider.getProfessionals();
4094
+ const professionals = await provider2.getProfessionals();
4095
4095
  set({ professionals, professionalsLoading: false });
4096
4096
  });
4097
4097
  },
4098
4098
  async fetchLocations() {
4099
4099
  return dedup("agenda:locations", async () => {
4100
- if (!provider.getLocations) return;
4101
- const locations = await provider.getLocations();
4100
+ if (!provider2.getLocations) return;
4101
+ const locations = await provider2.getLocations();
4102
4102
  set({ locations });
4103
4103
  const current = getScheduleBlockConfig();
4104
4104
  if (current) {
@@ -4109,23 +4109,23 @@ function createAgendaStore(provider, financialBridge) {
4109
4109
  async fetchSchedules(professionalId) {
4110
4110
  return dedup("agenda:schedules:" + (professionalId ?? "all"), async () => {
4111
4111
  set({ schedulesLoading: true });
4112
- const schedules = await provider.getSchedules(professionalId);
4112
+ const schedules = await provider2.getSchedules(professionalId);
4113
4113
  set({ schedules, schedulesLoading: false });
4114
4114
  });
4115
4115
  },
4116
4116
  async fetchConfirmations() {
4117
4117
  return dedup("agenda:confirmations", async () => {
4118
4118
  set({ confirmationsLoading: true });
4119
- const pendingConfirmations = await provider.getConfirmationsPending();
4119
+ const pendingConfirmations = await provider2.getConfirmationsPending();
4120
4120
  set({ pendingConfirmations, confirmationsLoading: false });
4121
4121
  });
4122
4122
  },
4123
4123
  async createBooking(input) {
4124
4124
  try {
4125
- const booking = await provider.createBooking(input);
4125
+ const booking = await provider2.createBooking(input);
4126
4126
  const state = get();
4127
4127
  if (state.dateRange.start && state.dateRange.end) {
4128
- const bookings = await provider.getBookings(getCurrentBookingQuery(state));
4128
+ const bookings = await provider2.getBookings(getCurrentBookingQuery(state));
4129
4129
  set({ bookings });
4130
4130
  }
4131
4131
  toast.success("Appointment created");
@@ -4137,10 +4137,10 @@ function createAgendaStore(provider, financialBridge) {
4137
4137
  },
4138
4138
  async updateBooking(id, data) {
4139
4139
  try {
4140
- const booking = await provider.updateBooking(id, data);
4140
+ const booking = await provider2.updateBooking(id, data);
4141
4141
  const state = get();
4142
4142
  if (state.dateRange.start && state.dateRange.end) {
4143
- const bookings = await provider.getBookings(getCurrentBookingQuery(state));
4143
+ const bookings = await provider2.getBookings(getCurrentBookingQuery(state));
4144
4144
  set({ bookings });
4145
4145
  }
4146
4146
  toast.success("Appointment updated");
@@ -4152,10 +4152,10 @@ function createAgendaStore(provider, financialBridge) {
4152
4152
  },
4153
4153
  async deleteBooking(id) {
4154
4154
  try {
4155
- await provider.deleteBooking(id);
4155
+ await provider2.deleteBooking(id);
4156
4156
  const state = get();
4157
4157
  if (state.dateRange.start && state.dateRange.end) {
4158
- const bookings = await provider.getBookings(getCurrentBookingQuery(state));
4158
+ const bookings = await provider2.getBookings(getCurrentBookingQuery(state));
4159
4159
  set({ bookings });
4160
4160
  }
4161
4161
  toast.success("Appointment deleted");
@@ -4168,10 +4168,10 @@ function createAgendaStore(provider, financialBridge) {
4168
4168
  const prev = get().bookings;
4169
4169
  set({ bookings: prev.map((b) => b.id === id ? { ...b, status } : b) });
4170
4170
  try {
4171
- await provider.updateBookingStatus(id, status);
4171
+ await provider2.updateBookingStatus(id, status);
4172
4172
  const state = get();
4173
4173
  if (state.dateRange.start && state.dateRange.end) {
4174
- const bookings = await provider.getBookings(getCurrentBookingQuery(state));
4174
+ const bookings = await provider2.getBookings(getCurrentBookingQuery(state));
4175
4175
  set({ bookings });
4176
4176
  }
4177
4177
  toast.success("Status updated");
@@ -4186,7 +4186,7 @@ function createAgendaStore(provider, financialBridge) {
4186
4186
  const existing = get().bookings.find((b) => b.id === id);
4187
4187
  const assigneeId = newProfessionalId ?? existing?.professionalId;
4188
4188
  if (assigneeId) {
4189
- const hasConflict = await provider.checkConflict({
4189
+ const hasConflict = await provider2.checkConflict({
4190
4190
  assigneeId,
4191
4191
  startsAt: newStart,
4192
4192
  endsAt: newEnd,
@@ -4197,14 +4197,14 @@ function createAgendaStore(provider, financialBridge) {
4197
4197
  throw new Error("Time conflict");
4198
4198
  }
4199
4199
  }
4200
- await provider.updateBooking(id, {
4200
+ await provider2.updateBooking(id, {
4201
4201
  startsAt: newStart,
4202
4202
  endsAt: newEnd,
4203
4203
  ...newProfessionalId && { professionalId: newProfessionalId }
4204
4204
  });
4205
4205
  const state = get();
4206
4206
  if (state.dateRange.start && state.dateRange.end) {
4207
- const bookings = await provider.getBookings(getCurrentBookingQuery(state));
4207
+ const bookings = await provider2.getBookings(getCurrentBookingQuery(state));
4208
4208
  set({ bookings });
4209
4209
  }
4210
4210
  toast.success("Appointment rescheduled");
@@ -4780,7 +4780,7 @@ function computeStatus(movements) {
4780
4780
  if (anyPaid) return "partial";
4781
4781
  return "pending";
4782
4782
  }
4783
- function createFinancialBridge(provider) {
4783
+ function createFinancialBridge(provider2) {
4784
4784
  return {
4785
4785
  async resolvePaymentStatuses(orderIds) {
4786
4786
  const result = /* @__PURE__ */ new Map();
@@ -4865,7 +4865,7 @@ function createFinancialBridge(provider) {
4865
4865
  return orderId;
4866
4866
  },
4867
4867
  async payMovement(input) {
4868
- await provider.payMovement({
4868
+ await provider2.payMovement({
4869
4869
  movementId: input.movementId,
4870
4870
  amount: input.amount,
4871
4871
  paymentDate: input.paymentDate,
@@ -4928,15 +4928,15 @@ function createFinancialBridge(provider) {
4928
4928
  return await this.getPaymentDetail(invoiceId);
4929
4929
  },
4930
4930
  async getPaymentMethodTypes() {
4931
- const types = await provider.getPaymentMethodTypes();
4931
+ const types = await provider2.getPaymentMethodTypes();
4932
4932
  return types.map((t) => ({ id: t.id, name: t.name, transactionType: t.transactionType }));
4933
4933
  },
4934
4934
  async getBankAccounts() {
4935
- const accounts = await provider.getBankAccounts();
4935
+ const accounts = await provider2.getBankAccounts();
4936
4936
  return accounts.filter((a) => a.isActive).map((a) => ({ id: a.id, name: a.name, accountType: a.accountType }));
4937
4937
  },
4938
4938
  async getPaymentMethods(paymentMethodTypeId) {
4939
- const methods = await provider.getPaymentMethods();
4939
+ const methods = await provider2.getPaymentMethods();
4940
4940
  return methods.filter((m) => m.paymentMethodTypeId === paymentMethodTypeId && m.isActive).map((m) => ({ id: m.id, name: m.name }));
4941
4941
  }
4942
4942
  };
@@ -4971,16 +4971,215 @@ function mapInvoiceStatus(status) {
4971
4971
  return "pending";
4972
4972
  }
4973
4973
  }
4974
+ var FUNCTION = "google-calendar-sync";
4975
+ function sb() {
4976
+ const supabase = getSupabaseClientOptional();
4977
+ if (!supabase) throw new Error("Supabase not initialized");
4978
+ return supabase;
4979
+ }
4980
+ function createGoogleCalendarProvider() {
4981
+ return {
4982
+ async getIntegration() {
4983
+ const { data } = await sb().from("calendar_integrations").select("*").eq("provider", "google").limit(1).maybeSingle();
4984
+ if (!data) return null;
4985
+ return {
4986
+ id: data.id,
4987
+ provider: data.provider,
4988
+ calendarId: data.calendar_id ?? "primary",
4989
+ active: data.active ?? true,
4990
+ connected: !!data.oauth_refresh_token,
4991
+ lastSyncAt: data.last_sync_at ?? void 0
4992
+ };
4993
+ },
4994
+ async getConnectUrl(redirectTo) {
4995
+ const { data, error } = await sb().functions.invoke(FUNCTION, {
4996
+ body: { action: "oauth_start", redirectTo: redirectTo ?? window.location.href }
4997
+ });
4998
+ if (error) throw new Error(error.message);
4999
+ return data.url;
5000
+ },
5001
+ async setCalendar(calendarId) {
5002
+ await sb().from("calendar_integrations").update({ calendar_id: calendarId, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("provider", "google");
5003
+ },
5004
+ async disconnect() {
5005
+ await sb().from("calendar_integrations").update({ active: false, oauth_refresh_token: null, oauth_access_token: null }).eq("provider", "google");
5006
+ },
5007
+ async syncNow() {
5008
+ const { data, error } = await sb().functions.invoke(FUNCTION, { body: { action: "pull_events", trigger: "manual" } });
5009
+ if (error) throw new Error(error.message);
5010
+ return data;
5011
+ },
5012
+ async getSyncLog() {
5013
+ const { data } = await sb().from("calendar_sync_log").select("*").order("created_at", { ascending: false }).limit(20);
5014
+ return (data ?? []).map((r) => ({
5015
+ id: r.id,
5016
+ direction: r.direction,
5017
+ trigger: r.trigger ?? void 0,
5018
+ status: r.status,
5019
+ fetched: r.fetched ?? 0,
5020
+ written: r.written ?? 0,
5021
+ error: r.error ?? void 0,
5022
+ createdAt: r.created_at
5023
+ }));
5024
+ }
5025
+ };
5026
+ }
5027
+ var provider = createGoogleCalendarProvider();
5028
+ function GoogleCalendarExtraPanel() {
5029
+ const [connected, setConnected] = useState(false);
5030
+ const [calendarId, setCalendarId] = useState("primary");
5031
+ const [syncing, setSyncing] = useState(false);
5032
+ const [log, setLog] = useState([]);
5033
+ const [loaded, setLoaded] = useState(false);
5034
+ async function load() {
5035
+ const integ = await provider.getIntegration();
5036
+ setConnected(!!integ?.connected);
5037
+ if (integ) {
5038
+ setCalendarId(integ.calendarId);
5039
+ void provider.getSyncLog().then(setLog);
5040
+ }
5041
+ setLoaded(true);
5042
+ }
5043
+ useEffect(() => {
5044
+ void load();
5045
+ }, []);
5046
+ if (!loaded || !connected) return null;
5047
+ async function handleSaveCalendar() {
5048
+ await provider.setCalendar(calendarId);
5049
+ toast.success("Saved");
5050
+ }
5051
+ async function handleSyncNow() {
5052
+ setSyncing(true);
5053
+ try {
5054
+ const r = await provider.syncNow();
5055
+ toast.success(`Synced \u2014 ${r.written} updated`);
5056
+ void provider.getSyncLog().then(setLog);
5057
+ } catch (e) {
5058
+ toast.error(e?.message ?? "Sync failed");
5059
+ } finally {
5060
+ setSyncing(false);
5061
+ }
5062
+ }
5063
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4 border-t pt-4", children: [
5064
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-end gap-3", children: [
5065
+ /* @__PURE__ */ jsxs("label", { className: "block flex-1 min-w-[200px]", children: [
5066
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Calendar ID" }),
5067
+ /* @__PURE__ */ jsx(
5068
+ "input",
5069
+ {
5070
+ value: calendarId,
5071
+ onChange: (e) => setCalendarId(e.target.value),
5072
+ placeholder: "primary",
5073
+ 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"
5074
+ }
5075
+ )
5076
+ ] }),
5077
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: handleSaveCalendar, children: "Save" }),
5078
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: handleSyncNow, disabled: syncing, children: [
5079
+ /* @__PURE__ */ jsx(RefreshCw, { className: `h-3.5 w-3.5 ${syncing ? "animate-spin" : ""}` }),
5080
+ " Sync now"
5081
+ ] })
5082
+ ] }),
5083
+ log.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
5084
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
5085
+ /* @__PURE__ */ jsx(History, { className: "h-4 w-4 text-muted-foreground" }),
5086
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "History" })
5087
+ ] }),
5088
+ /* @__PURE__ */ jsx("div", { className: "rounded-md border divide-y text-sm", children: log.map((r) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-3 py-2", children: [
5089
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: new Date(r.createdAt).toLocaleString() }),
5090
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] uppercase tracking-wide px-1 py-px rounded bg-muted text-muted-foreground", children: r.direction }),
5091
+ /* @__PURE__ */ jsxs("span", { className: "ml-auto text-xs", children: [
5092
+ r.written,
5093
+ " written / ",
5094
+ r.fetched,
5095
+ " fetched"
5096
+ ] }),
5097
+ r.status === "success" ? /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3.5 w-3.5 text-success" }) : /* @__PURE__ */ jsx(AlertCircle, { className: "h-3.5 w-3.5 text-warning" })
5098
+ ] }, r.id)) })
5099
+ ] })
5100
+ ] });
5101
+ }
5102
+ var googleCalendarConnectorDef = {
5103
+ id: "google-calendar",
5104
+ hostPluginId: "agenda",
5105
+ name: "Google Calendar",
5106
+ description: "Two-way sync between your bookings and Google Calendar.",
5107
+ icon: "Calendar",
5108
+ authKind: "oauth",
5109
+ async getStatus() {
5110
+ const integ = await provider.getIntegration();
5111
+ return { connected: !!integ?.connected };
5112
+ },
5113
+ startOAuth: (redirectTo) => provider.getConnectUrl(redirectTo),
5114
+ disconnect: () => provider.disconnect(),
5115
+ ExtraPanel: GoogleCalendarExtraPanel
5116
+ };
5117
+
5118
+ // src/integrations/google-calendar/connector.ts
5119
+ var googleCalendarConnector = {
5120
+ id: "google-calendar",
5121
+ provider: "google",
5122
+ pluginId: "agenda",
5123
+ authKind: "oauth",
5124
+ capabilities: [
5125
+ {
5126
+ entity: "booking",
5127
+ direction: "bidirectional",
5128
+ triggers: ["on-write", "scheduled", "webhook"]
5129
+ }
5130
+ ]
5131
+ };
5132
+
5133
+ // src/integrations/google-calendar/mapping.ts
5134
+ function bookingSummary(b) {
5135
+ const services = (b.services ?? []).map((s) => s.name).filter(Boolean).join(", ");
5136
+ const who = b.clientName ?? "";
5137
+ if (services && who) return `${services} \u2014 ${who}`;
5138
+ return services || who || "Appointment";
5139
+ }
5140
+ function bookingToEvent2(b, timeZone) {
5141
+ if (!b.startsAt || !b.endsAt) return null;
5142
+ const notes = [b.notes, b.professionalName ? `Pro: ${b.professionalName}` : "", b.locationName ?? ""].filter(Boolean).join("\n");
5143
+ return {
5144
+ summary: bookingSummary(b),
5145
+ description: notes || void 0,
5146
+ start: { dateTime: b.startsAt, timeZone },
5147
+ end: { dateTime: b.endsAt, timeZone }
5148
+ };
5149
+ }
5150
+ function eventToBookingPatch(ev) {
5151
+ if (ev.status === "cancelled") return { status: "cancelled" };
5152
+ return {
5153
+ startsAt: ev.start?.dateTime,
5154
+ endsAt: ev.end?.dateTime
5155
+ };
5156
+ }
5157
+
5158
+ // src/integrations/google-calendar/index.ts
5159
+ function createGoogleCalendarPlugin() {
5160
+ return {
5161
+ id: "google-calendar",
5162
+ name: "Google Calendar",
5163
+ icon: "Calendar",
5164
+ version: "0.1.0",
5165
+ scope: "addon",
5166
+ defaultEnabled: true,
5167
+ dependencies: ["agenda"],
5168
+ navigation: [],
5169
+ routes: [],
5170
+ connectors: [googleCalendarConnectorDef]
5171
+ };
5172
+ }
4974
5173
 
4975
5174
  // src/index.ts
4976
5175
  function createAgendaPlugin(options) {
4977
5176
  const config = resolveConfig(options);
4978
5177
  registerTranslations(agendaLocales);
4979
- const provider = options?.dataProvider ?? createSafeDataProvider(
5178
+ const provider2 = options?.dataProvider ?? createSafeDataProvider(
4980
5179
  () => createSupabaseAgendaProvider(),
4981
5180
  () => createMockAgendaProvider()
4982
5181
  );
4983
- const store = createAgendaStore(provider, options?.financialBridge);
5182
+ const store = createAgendaStore(provider2, options?.financialBridge);
4984
5183
  setScheduleBlockConfig({
4985
5184
  defaults: config.scheduleBlockDefaults,
4986
5185
  showServices: !!config.serviceLookup,
@@ -4989,14 +5188,14 @@ function createAgendaPlugin(options) {
4989
5188
  showLocations: config.modules.locationSelection,
4990
5189
  locations: config.locations.map((l) => ({ id: l.id, name: l.name })),
4991
5190
  // Lazy fetcher — called by ScheduleEditor when locations are needed but empty
4992
- fetchLocations: config.modules.locationSelection && provider.getLocations ? async () => {
4993
- const locations = await provider.getLocations();
5191
+ fetchLocations: config.modules.locationSelection && provider2.getLocations ? async () => {
5192
+ const locations = await provider2.getLocations();
4994
5193
  const current = getScheduleBlockConfig();
4995
5194
  if (current) setScheduleBlockConfig({ ...current, locations });
4996
5195
  return locations;
4997
5196
  } : void 0
4998
5197
  });
4999
- const PageComponent = () => React10.createElement(AgendaPage, { config, provider, store });
5198
+ const PageComponent = () => React11.createElement(AgendaPage, { config, provider: provider2, store });
5000
5199
  return {
5001
5200
  id: "agenda",
5002
5201
  name: config.labels.pageTitle,
@@ -5102,8 +5301,15 @@ function createAgendaPlugin(options) {
5102
5301
  id: "agenda",
5103
5302
  label: "Agenda",
5104
5303
  icon: "Calendar",
5304
+ // Standard layout: General | Integrations (Google Calendar addon) | Properties (holidays).
5105
5305
  component: (() => {
5106
- const AgendaSettingsTab = () => React10.createElement(AgendaGeneralSettings);
5306
+ const AgendaSettingsTab = () => React11.createElement(PluginSettingsPanel, {
5307
+ title: "Agenda",
5308
+ generalSettings: React11.createElement(AgendaGeneralSettings),
5309
+ registries: agendaRegistries,
5310
+ hostPluginId: "agenda",
5311
+ routeBase: "/settings/agenda"
5312
+ });
5107
5313
  AgendaSettingsTab.displayName = "AgendaSettingsTab";
5108
5314
  return AgendaSettingsTab;
5109
5315
  })(),
@@ -5115,6 +5321,6 @@ function createAgendaPlugin(options) {
5115
5321
  };
5116
5322
  }
5117
5323
 
5118
- export { computeCommissionAmount, createAgendaPlugin, createFinancialBridge };
5324
+ export { bookingToEvent2 as bookingToEvent, computeCommissionAmount, createAgendaPlugin, createFinancialBridge, createGoogleCalendarPlugin, createGoogleCalendarProvider, eventToBookingPatch, googleCalendarConnector };
5119
5325
  //# sourceMappingURL=index.js.map
5120
5326
  //# sourceMappingURL=index.js.map