@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.
- package/LICENSE +21 -0
- package/dist/index.cjs +412 -201
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +254 -48
- package/dist/index.js.map +1 -1
- package/dist/integrations/google-calendar/GoogleCalendarSettings.d.ts +3 -0
- package/dist/integrations/google-calendar/GoogleCalendarSettings.d.ts.map +1 -0
- package/dist/integrations/google-calendar/connector.d.ts +3 -0
- package/dist/integrations/google-calendar/connector.d.ts.map +1 -0
- package/dist/integrations/google-calendar/connectorDef.d.ts +3 -0
- package/dist/integrations/google-calendar/connectorDef.d.ts.map +1 -0
- package/dist/integrations/google-calendar/data/supabase.d.ts +16 -0
- package/dist/integrations/google-calendar/data/supabase.d.ts.map +1 -0
- package/dist/integrations/google-calendar/index.d.ts +8 -0
- package/dist/integrations/google-calendar/index.d.ts.map +1 -0
- package/dist/integrations/google-calendar/integration.test.d.ts +2 -0
- package/dist/integrations/google-calendar/integration.test.d.ts.map +1 -0
- package/dist/integrations/google-calendar/mapping.d.ts +25 -0
- package/dist/integrations/google-calendar/mapping.d.ts.map +1 -0
- package/dist/integrations/google-calendar/types.d.ts +36 -0
- package/dist/integrations/google-calendar/types.d.ts.map +1 -0
- package/package.json +10 -7
- package/src/index.ts +12 -2
- package/src/integrations/google-calendar/GoogleCalendarSettings.tsx +136 -0
- package/src/integrations/google-calendar/connector.ts +20 -0
- package/src/integrations/google-calendar/connectorDef.tsx +94 -0
- package/src/integrations/google-calendar/data/supabase.ts +74 -0
- package/src/integrations/google-calendar/index.ts +36 -0
- package/src/integrations/google-calendar/integration.test.ts +74 -0
- package/src/integrations/google-calendar/mapping.ts +43 -0
- package/src/integrations/google-calendar/migrations/001_google_calendar.sql +94 -0
- 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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
|
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 {
|
|
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 {
|
|
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] =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
1658
|
-
const supabase =
|
|
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:
|
|
1805
|
+
const { getSupabaseClientOptional: getSupabaseClientOptional4 } = await import('@fayz-ai/core');
|
|
1806
1806
|
const { getAgendaTenantId: getAgendaTenantId2 } = await import('./tenant-LAUWVUYB.js');
|
|
1807
|
-
const supabase =
|
|
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:
|
|
1831
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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 (!
|
|
4101
|
-
const locations = await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4155
|
+
await provider2.deleteBooking(id);
|
|
4156
4156
|
const state = get();
|
|
4157
4157
|
if (state.dateRange.start && state.dateRange.end) {
|
|
4158
|
-
const bookings = await
|
|
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
|
|
4171
|
+
await provider2.updateBookingStatus(id, status);
|
|
4172
4172
|
const state = get();
|
|
4173
4173
|
if (state.dateRange.start && state.dateRange.end) {
|
|
4174
|
-
const bookings = await
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
5178
|
+
const provider2 = options?.dataProvider ?? createSafeDataProvider(
|
|
4980
5179
|
() => createSupabaseAgendaProvider(),
|
|
4981
5180
|
() => createMockAgendaProvider()
|
|
4982
5181
|
);
|
|
4983
|
-
const store = createAgendaStore(
|
|
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 &&
|
|
4993
|
-
const locations = await
|
|
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 = () =>
|
|
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 = () =>
|
|
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
|