@fayz-ai/plugin-agenda 0.9.0 → 0.10.0
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/README.md +3 -3
- package/dist/bridges/create-financial-bridge.d.ts +1 -1
- package/dist/bridges/create-financial-bridge.d.ts.map +1 -1
- package/dist/bridges/financial-bridge.d.ts.map +1 -0
- package/dist/{chunk-Q2PAWBPR.js → chunk-ISLJXP7O.js} +7 -2
- package/dist/chunk-ISLJXP7O.js.map +1 -0
- package/dist/components/AppointmentModal.d.ts.map +1 -1
- package/dist/components/PaymentStatusBadge.d.ts +1 -1
- package/dist/components/PaymentStatusBadge.d.ts.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/{AgendaContext.d.ts → context.d.ts} +1 -1
- package/dist/context.d.ts.map +1 -0
- package/dist/data/mock.d.ts.map +1 -1
- package/dist/data/realtime.test.d.ts +2 -0
- package/dist/data/realtime.test.d.ts.map +1 -0
- package/dist/data/registries.d.ts.map +1 -0
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/data/types.d.ts +7 -0
- package/dist/data/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2515 -187
- package/dist/index.js.map +1 -1
- package/dist/integrations/google-calendar/conflict.d.ts +25 -0
- package/dist/integrations/google-calendar/conflict.d.ts.map +1 -0
- package/dist/integrations/google-calendar/connectorDef.d.ts +12 -0
- package/dist/integrations/google-calendar/connectorDef.d.ts.map +1 -1
- package/dist/integrations/google-calendar/credentials.d.ts +27 -0
- package/dist/integrations/google-calendar/credentials.d.ts.map +1 -0
- package/dist/integrations/google-calendar/data/supabase.d.ts +10 -2
- package/dist/integrations/google-calendar/data/supabase.d.ts.map +1 -1
- package/dist/integrations/google-calendar/data/tables.d.ts +2 -0
- package/dist/integrations/google-calendar/data/tables.d.ts.map +1 -1
- package/dist/integrations/google-calendar/health.d.ts +23 -0
- package/dist/integrations/google-calendar/health.d.ts.map +1 -0
- package/dist/integrations/google-calendar/health.test.d.ts +2 -0
- package/dist/integrations/google-calendar/health.test.d.ts.map +1 -0
- package/dist/integrations/google-calendar/index.d.ts +6 -0
- package/dist/integrations/google-calendar/index.d.ts.map +1 -1
- package/dist/integrations/google-calendar/migrations/index.d.ts +12 -0
- package/dist/integrations/google-calendar/migrations/index.d.ts.map +1 -0
- package/dist/integrations/google-calendar/panel-data.d.ts +26 -0
- package/dist/integrations/google-calendar/panel-data.d.ts.map +1 -0
- package/dist/integrations/google-calendar/panel-data.test.d.ts +2 -0
- package/dist/integrations/google-calendar/panel-data.test.d.ts.map +1 -0
- package/dist/integrations/google-calendar/types.d.ts +17 -0
- package/dist/integrations/google-calendar/types.d.ts.map +1 -1
- package/dist/lib/booking-events.d.ts +5 -0
- package/dist/lib/booking-events.d.ts.map +1 -0
- package/dist/lib/calendar-events.d.ts +8 -0
- package/dist/lib/calendar-events.d.ts.map +1 -0
- package/dist/lib/calendar-events.test.d.ts +2 -0
- package/dist/lib/calendar-events.test.d.ts.map +1 -0
- package/dist/lib/message-events.d.ts +12 -0
- package/dist/lib/message-events.d.ts.map +1 -0
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/public/{data.d.ts → data/index.d.ts} +4 -4
- package/dist/public/data/index.d.ts.map +1 -0
- package/dist/public/{data.supabase.d.ts → data/supabase.d.ts} +2 -2
- package/dist/public/data/supabase.d.ts.map +1 -0
- package/dist/public/{hooks.d.ts → hooks/index.d.ts} +4 -4
- package/dist/public/hooks/index.d.ts.map +1 -0
- package/dist/public/index.d.ts +4 -4
- package/dist/public/index.d.ts.map +1 -1
- package/dist/public/index.js +3 -3
- package/dist/public/index.js.map +1 -1
- package/dist/public/{format.d.ts → lib/format.d.ts} +1 -1
- package/dist/public/lib/format.d.ts.map +1 -0
- package/dist/public/views/BookingWidget.d.ts.map +1 -0
- package/dist/public/{steps → views/steps}/ContactStep.d.ts +1 -1
- package/dist/public/views/steps/ContactStep.d.ts.map +1 -0
- package/dist/public/{steps → views/steps}/DateTimeStep.d.ts +3 -3
- package/dist/public/views/steps/DateTimeStep.d.ts.map +1 -0
- package/dist/public/{steps → views/steps}/OverviewStep.d.ts +1 -1
- package/dist/public/views/steps/OverviewStep.d.ts.map +1 -0
- package/dist/public/{steps → views/steps}/PaymentStep.d.ts +1 -1
- package/dist/public/views/steps/PaymentStep.d.ts.map +1 -0
- package/dist/store.d.ts +1 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/styles/calendar-styles.d.ts.map +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/{AgendaPage.d.ts → views/AgendaPage.d.ts} +3 -3
- package/dist/views/AgendaPage.d.ts.map +1 -0
- package/dist/views/CalendarView.d.ts.map +1 -1
- package/package.json +9 -11
- package/dist/AgendaContext.d.ts.map +0 -1
- package/dist/AgendaPage.d.ts.map +0 -1
- package/dist/calendar-styles.d.ts.map +0 -1
- package/dist/chunk-Q2PAWBPR.js.map +0 -1
- package/dist/financial-bridge.d.ts.map +0 -1
- package/dist/integrations/google-calendar/GoogleCalendarSettings.d.ts +0 -3
- package/dist/integrations/google-calendar/GoogleCalendarSettings.d.ts.map +0 -1
- package/dist/public/BookingWidget.d.ts.map +0 -1
- package/dist/public/data.d.ts.map +0 -1
- package/dist/public/data.supabase.d.ts.map +0 -1
- package/dist/public/format.d.ts.map +0 -1
- package/dist/public/hooks.d.ts.map +0 -1
- package/dist/public/steps/ContactStep.d.ts.map +0 -1
- package/dist/public/steps/DateTimeStep.d.ts.map +0 -1
- package/dist/public/steps/OverviewStep.d.ts.map +0 -1
- package/dist/public/steps/PaymentStep.d.ts.map +0 -1
- package/dist/registries.d.ts.map +0 -1
- package/src/AgendaContext.tsx +0 -11
- package/src/AgendaPage.tsx +0 -33
- package/src/bridges/create-financial-bridge.ts +0 -296
- package/src/calendar-styles.css +0 -245
- package/src/calendar-styles.ts +0 -285
- package/src/components/AgendaGeneralSettings.tsx +0 -86
- package/src/components/AgendaMobileList.tsx +0 -103
- package/src/components/AppointmentModal.tsx +0 -1050
- package/src/components/AppointmentPopover.tsx +0 -231
- package/src/components/BookingPaymentPanel.tsx +0 -394
- package/src/components/CalendarSidebar.tsx +0 -119
- package/src/components/CalendarToolbar.tsx +0 -86
- package/src/components/EventContextMenu.tsx +0 -125
- package/src/components/FloatingPanel.tsx +0 -123
- package/src/components/MiniCalendar.tsx +0 -120
- package/src/components/PaymentStatusBadge.tsx +0 -62
- package/src/components/StatusBadge.tsx +0 -23
- package/src/config.ts +0 -368
- package/src/data/index.ts +0 -4
- package/src/data/mock.ts +0 -344
- package/src/data/supabase.ts +0 -636
- package/src/data/types.ts +0 -37
- package/src/financial-bridge.ts +0 -96
- package/src/hooks/useAgendaSettings.ts +0 -17
- package/src/index.ts +0 -297
- package/src/integrations/google-calendar/GoogleCalendarSettings.tsx +0 -136
- package/src/integrations/google-calendar/components/CalendarColorDot.tsx +0 -23
- package/src/integrations/google-calendar/components/ChannelMappingList.tsx +0 -329
- package/src/integrations/google-calendar/components/ImportEventsWizard.tsx +0 -215
- package/src/integrations/google-calendar/components/useAgendaLookups.ts +0 -100
- package/src/integrations/google-calendar/connector.ts +0 -20
- package/src/integrations/google-calendar/connectorDef.tsx +0 -185
- package/src/integrations/google-calendar/data/supabase.ts +0 -170
- package/src/integrations/google-calendar/data/tables.ts +0 -12
- package/src/integrations/google-calendar/index.ts +0 -45
- package/src/integrations/google-calendar/integration.test.ts +0 -199
- package/src/integrations/google-calendar/mapping.ts +0 -183
- package/src/integrations/google-calendar/migrations/001_google_calendar.sql +0 -97
- package/src/integrations/google-calendar/migrations/002_smart_sync.sql +0 -517
- package/src/integrations/google-calendar/migrations/003_plg_rename.sql +0 -273
- package/src/integrations/google-calendar/types.ts +0 -142
- package/src/lib/tenant.ts +0 -3
- package/src/locales/en.ts +0 -220
- package/src/locales/index.ts +0 -7
- package/src/locales/pt-BR.ts +0 -220
- package/src/migrations/000b_v_bookings_rename.sql +0 -21
- package/src/migrations/000c_dedupe_customer_phones.sql +0 -43
- package/src/migrations/001_public_booking.sql +0 -296
- package/src/migrations/002_v_bookings_compat.sql +0 -14
- package/src/migrations/003_public_client_kind.sql +0 -175
- package/src/migrations/004_public_booking_assignee.sql +0 -176
- package/src/migrations/005_agent_rpcs.sql +0 -168
- package/src/migrations/006_agent_rpc_localtime.sql +0 -181
- package/src/migrations/007_schedule_exception_precedence.sql +0 -290
- package/src/migrations/008_agent_rpc_exception_precedence.sql +0 -172
- package/src/migrations/009_public_booking_location.sql +0 -162
- package/src/migrations/010_public_service_curation.sql +0 -31
- package/src/migrations/011_default_assignee.sql +0 -167
- package/src/migrations/012_drop_legacy_booking_overload.sql +0 -29
- package/src/migrations/013_normalize_v_appointments.sql +0 -199
- package/src/migrations/014_agent_rpc_update_appointment.sql +0 -199
- package/src/migrations/index.ts +0 -2377
- package/src/public/BookingWidget.tsx +0 -324
- package/src/public/components/BrandHeader.tsx +0 -27
- package/src/public/components/ServiceCard.tsx +0 -39
- package/src/public/components/SidebarExtras.tsx +0 -30
- package/src/public/components/StepRailItem.tsx +0 -27
- package/src/public/components/SuccessPanel.tsx +0 -27
- package/src/public/components/SummaryRail.tsx +0 -90
- package/src/public/components/defaults.ts +0 -14
- package/src/public/context.tsx +0 -103
- package/src/public/createPublicBookingPlugin.ts +0 -281
- package/src/public/data.supabase.ts +0 -97
- package/src/public/data.ts +0 -105
- package/src/public/format.ts +0 -96
- package/src/public/hooks.ts +0 -110
- package/src/public/index.ts +0 -69
- package/src/public/steps/ContactStep.tsx +0 -184
- package/src/public/steps/DateTimeStep.tsx +0 -138
- package/src/public/steps/OverviewStep.tsx +0 -81
- package/src/public/steps/PaymentStep.tsx +0 -180
- package/src/public/types.ts +0 -250
- package/src/registries.ts +0 -35
- package/src/store.ts +0 -311
- package/src/types.ts +0 -273
- package/src/views/CalendarView.tsx +0 -1112
- package/src/views/ConfirmationsView.tsx +0 -155
- /package/dist/{financial-bridge.d.ts → bridges/financial-bridge.d.ts} +0 -0
- /package/dist/{registries.d.ts → data/registries.d.ts} +0 -0
- /package/dist/public/{BookingWidget.d.ts → views/BookingWidget.d.ts} +0 -0
- /package/dist/{calendar-styles.d.ts → styles/calendar-styles.d.ts} +0 -0
package/src/store.ts
DELETED
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
import { createStore, type StoreApi } from 'zustand/vanilla'
|
|
2
|
-
import { emitDataChanged } from '@fayz-ai/core'
|
|
3
|
-
import { dedup } from '@fayz-ai/saas'
|
|
4
|
-
import { toast } from '@fayz-ai/ui'
|
|
5
|
-
import { getScheduleBlockConfig, setScheduleBlockConfig } from '@fayz-ai/saas'
|
|
6
|
-
import type { AgendaDataProvider } from './data/types'
|
|
7
|
-
import type { AgendaFinancialBridge } from './financial-bridge'
|
|
8
|
-
import type { ExternalBookingSource } from './config'
|
|
9
|
-
import type {
|
|
10
|
-
CalendarBooking, Professional, Schedule,
|
|
11
|
-
CreateBookingInput, UpdateBookingInput, BookingQuery,
|
|
12
|
-
} from './types'
|
|
13
|
-
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
// Store state
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
|
|
18
|
-
export interface AgendaUIState {
|
|
19
|
-
// Calendar data
|
|
20
|
-
bookings: CalendarBooking[]
|
|
21
|
-
bookingsLoading: boolean
|
|
22
|
-
dateRange: { start: string; end: string }
|
|
23
|
-
currentView: string
|
|
24
|
-
selectedDate: string
|
|
25
|
-
|
|
26
|
-
// Resources
|
|
27
|
-
professionals: Professional[]
|
|
28
|
-
professionalsLoading: boolean
|
|
29
|
-
|
|
30
|
-
// Filters
|
|
31
|
-
selectedProfessionalIds: string[]
|
|
32
|
-
selectedLocationId: string | null
|
|
33
|
-
selectedStatuses: string[]
|
|
34
|
-
|
|
35
|
-
// Locations
|
|
36
|
-
locations: Array<{ id: string; name: string }>
|
|
37
|
-
|
|
38
|
-
// Schedules
|
|
39
|
-
schedules: Schedule[]
|
|
40
|
-
schedulesLoading: boolean
|
|
41
|
-
|
|
42
|
-
// Confirmations
|
|
43
|
-
pendingConfirmations: CalendarBooking[]
|
|
44
|
-
confirmationsLoading: boolean
|
|
45
|
-
|
|
46
|
-
// Modal state
|
|
47
|
-
appointmentModal: {
|
|
48
|
-
open: boolean
|
|
49
|
-
mode: 'create' | 'edit'
|
|
50
|
-
bookingId?: string
|
|
51
|
-
prefill?: Partial<CreateBookingInput> & { endsAt?: string }
|
|
52
|
-
initialTab?: string
|
|
53
|
-
externalSource?: ExternalBookingSource
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Actions
|
|
57
|
-
fetchBookings(range: { start: string; end: string }): Promise<void>
|
|
58
|
-
fetchProfessionals(): Promise<void>
|
|
59
|
-
fetchLocations(): Promise<void>
|
|
60
|
-
fetchSchedules(professionalId?: string): Promise<void>
|
|
61
|
-
fetchConfirmations(): Promise<void>
|
|
62
|
-
createBooking(input: CreateBookingInput): Promise<CalendarBooking>
|
|
63
|
-
updateBooking(id: string, data: UpdateBookingInput): Promise<CalendarBooking>
|
|
64
|
-
deleteBooking(id: string): Promise<void>
|
|
65
|
-
updateBookingStatus(id: string, status: string): Promise<void>
|
|
66
|
-
rescheduleBooking(id: string, newStart: string, newEnd: string, newProfessionalId?: string): Promise<void>
|
|
67
|
-
setFilters(filters: Partial<{ professionalIds: string[]; locationId: string | null; statuses: string[] }>): void
|
|
68
|
-
setView(view: string): void
|
|
69
|
-
setSelectedDate(date: string): void
|
|
70
|
-
openAppointmentModal(mode: 'create' | 'edit', options?: { bookingId?: string; prefill?: Partial<CreateBookingInput> & { endsAt?: string }; initialTab?: string; externalSource?: ExternalBookingSource }): void
|
|
71
|
-
closeAppointmentModal(): void
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
// Store factory
|
|
76
|
-
// ---------------------------------------------------------------------------
|
|
77
|
-
|
|
78
|
-
function getCurrentBookingQuery(state: AgendaUIState, dateRange = state.dateRange): BookingQuery {
|
|
79
|
-
return {
|
|
80
|
-
dateRange,
|
|
81
|
-
professionalIds: state.selectedProfessionalIds.length ? state.selectedProfessionalIds : undefined,
|
|
82
|
-
locationId: state.selectedLocationId ?? undefined,
|
|
83
|
-
statuses: state.selectedStatuses.length ? state.selectedStatuses : undefined,
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function createAgendaStore(provider: AgendaDataProvider, financialBridge?: AgendaFinancialBridge): StoreApi<AgendaUIState> {
|
|
88
|
-
return createStore<AgendaUIState>((set, get) => ({
|
|
89
|
-
bookings: [],
|
|
90
|
-
bookingsLoading: false,
|
|
91
|
-
dateRange: { start: '', end: '' },
|
|
92
|
-
currentView: 'resourceTimeGridDay',
|
|
93
|
-
selectedDate: new Date().toISOString().slice(0, 10),
|
|
94
|
-
|
|
95
|
-
professionals: [],
|
|
96
|
-
professionalsLoading: false,
|
|
97
|
-
|
|
98
|
-
selectedProfessionalIds: [],
|
|
99
|
-
selectedLocationId: null,
|
|
100
|
-
selectedStatuses: ['scheduled', 'confirmed', 'in_progress', 'completed'],
|
|
101
|
-
|
|
102
|
-
locations: [],
|
|
103
|
-
|
|
104
|
-
schedules: [],
|
|
105
|
-
schedulesLoading: false,
|
|
106
|
-
|
|
107
|
-
pendingConfirmations: [],
|
|
108
|
-
confirmationsLoading: false,
|
|
109
|
-
|
|
110
|
-
appointmentModal: { open: false, mode: 'create' },
|
|
111
|
-
|
|
112
|
-
async fetchBookings(range) {
|
|
113
|
-
const key = `agenda:bookings:${range.start}:${range.end}`
|
|
114
|
-
return dedup(key, async () => {
|
|
115
|
-
set({ bookingsLoading: true, dateRange: range })
|
|
116
|
-
const state = get()
|
|
117
|
-
const query = getCurrentBookingQuery(state, range)
|
|
118
|
-
let bookings = await provider.getBookings(query)
|
|
119
|
-
|
|
120
|
-
// Enrich with payment status if financial bridge is available
|
|
121
|
-
if (financialBridge) {
|
|
122
|
-
const orderIds = bookings.map((b) => b.orderId).filter((id): id is string => !!id)
|
|
123
|
-
if (orderIds.length > 0) {
|
|
124
|
-
try {
|
|
125
|
-
const statuses = await financialBridge.resolvePaymentStatuses(orderIds)
|
|
126
|
-
bookings = bookings.map((b) => {
|
|
127
|
-
if (!b.orderId) return b
|
|
128
|
-
const summary = statuses.get(b.orderId)
|
|
129
|
-
return summary ? { ...b, paymentStatus: summary.status } : b
|
|
130
|
-
})
|
|
131
|
-
} catch { /* non-blocking */ }
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
set({ bookings, bookingsLoading: false })
|
|
136
|
-
})
|
|
137
|
-
},
|
|
138
|
-
|
|
139
|
-
async fetchProfessionals() {
|
|
140
|
-
return dedup('agenda:professionals', async () => {
|
|
141
|
-
set({ professionalsLoading: true })
|
|
142
|
-
const professionals = await provider.getProfessionals()
|
|
143
|
-
set({ professionals, professionalsLoading: false })
|
|
144
|
-
})
|
|
145
|
-
},
|
|
146
|
-
|
|
147
|
-
async fetchLocations() {
|
|
148
|
-
return dedup('agenda:locations', async () => {
|
|
149
|
-
if (!provider.getLocations) return
|
|
150
|
-
const locations = await provider.getLocations()
|
|
151
|
-
set({ locations })
|
|
152
|
-
// Update global schedule block config so ScheduleEditor picks up locations
|
|
153
|
-
const current = getScheduleBlockConfig()
|
|
154
|
-
if (current) {
|
|
155
|
-
setScheduleBlockConfig({ ...current, locations })
|
|
156
|
-
}
|
|
157
|
-
})
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
async fetchSchedules(professionalId) {
|
|
161
|
-
return dedup('agenda:schedules:' + (professionalId ?? 'all'), async () => {
|
|
162
|
-
set({ schedulesLoading: true })
|
|
163
|
-
const schedules = await provider.getSchedules(professionalId)
|
|
164
|
-
set({ schedules, schedulesLoading: false })
|
|
165
|
-
})
|
|
166
|
-
},
|
|
167
|
-
|
|
168
|
-
async fetchConfirmations() {
|
|
169
|
-
return dedup('agenda:confirmations', async () => {
|
|
170
|
-
set({ confirmationsLoading: true })
|
|
171
|
-
const pendingConfirmations = await provider.getConfirmationsPending()
|
|
172
|
-
set({ pendingConfirmations, confirmationsLoading: false })
|
|
173
|
-
})
|
|
174
|
-
},
|
|
175
|
-
|
|
176
|
-
async createBooking(input) {
|
|
177
|
-
try {
|
|
178
|
-
const booking = await provider.createBooking(input)
|
|
179
|
-
// Refresh bookings for current range
|
|
180
|
-
const state = get()
|
|
181
|
-
if (state.dateRange.start && state.dateRange.end) {
|
|
182
|
-
const bookings = await provider.getBookings(getCurrentBookingQuery(state))
|
|
183
|
-
set({ bookings })
|
|
184
|
-
}
|
|
185
|
-
emitDataChanged({ table: 'appointments', op: 'create', id: booking.id, source: 'ui' })
|
|
186
|
-
toast.success('Appointment created')
|
|
187
|
-
return booking
|
|
188
|
-
} catch (err: any) {
|
|
189
|
-
toast.error('Failed to create appointment', { description: err?.message })
|
|
190
|
-
throw err
|
|
191
|
-
}
|
|
192
|
-
},
|
|
193
|
-
|
|
194
|
-
async updateBooking(id, data) {
|
|
195
|
-
try {
|
|
196
|
-
const booking = await provider.updateBooking(id, data)
|
|
197
|
-
const state = get()
|
|
198
|
-
if (state.dateRange.start && state.dateRange.end) {
|
|
199
|
-
const bookings = await provider.getBookings(getCurrentBookingQuery(state))
|
|
200
|
-
set({ bookings })
|
|
201
|
-
}
|
|
202
|
-
emitDataChanged({ table: 'appointments', op: 'update', id, source: 'ui' })
|
|
203
|
-
toast.success('Appointment updated')
|
|
204
|
-
return booking
|
|
205
|
-
} catch (err: any) {
|
|
206
|
-
toast.error('Failed to update appointment', { description: err?.message })
|
|
207
|
-
throw err
|
|
208
|
-
}
|
|
209
|
-
},
|
|
210
|
-
|
|
211
|
-
async deleteBooking(id) {
|
|
212
|
-
try {
|
|
213
|
-
await provider.deleteBooking(id)
|
|
214
|
-
const state = get()
|
|
215
|
-
if (state.dateRange.start && state.dateRange.end) {
|
|
216
|
-
const bookings = await provider.getBookings(getCurrentBookingQuery(state))
|
|
217
|
-
set({ bookings })
|
|
218
|
-
}
|
|
219
|
-
emitDataChanged({ table: 'appointments', op: 'delete', id, source: 'ui' })
|
|
220
|
-
toast.success('Appointment deleted')
|
|
221
|
-
} catch (err: any) {
|
|
222
|
-
toast.error('Failed to delete appointment', { description: err?.message })
|
|
223
|
-
throw err
|
|
224
|
-
}
|
|
225
|
-
},
|
|
226
|
-
|
|
227
|
-
async updateBookingStatus(id, status) {
|
|
228
|
-
// Optimistic update — change color immediately
|
|
229
|
-
const prev = get().bookings
|
|
230
|
-
set({ bookings: prev.map((b) => b.id === id ? { ...b, status: status as any } : b) })
|
|
231
|
-
try {
|
|
232
|
-
await provider.updateBookingStatus(id, status)
|
|
233
|
-
// Refresh from server to get full data
|
|
234
|
-
const state = get()
|
|
235
|
-
if (state.dateRange.start && state.dateRange.end) {
|
|
236
|
-
const bookings = await provider.getBookings(getCurrentBookingQuery(state))
|
|
237
|
-
set({ bookings })
|
|
238
|
-
}
|
|
239
|
-
emitDataChanged({ table: 'appointments', op: 'update', id, source: 'ui' })
|
|
240
|
-
toast.success('Status updated')
|
|
241
|
-
} catch (err: any) {
|
|
242
|
-
// Rollback on failure
|
|
243
|
-
set({ bookings: prev })
|
|
244
|
-
toast.error('Failed to update status', { description: err?.message })
|
|
245
|
-
throw err
|
|
246
|
-
}
|
|
247
|
-
},
|
|
248
|
-
|
|
249
|
-
async rescheduleBooking(id, newStart, newEnd, newProfessionalId) {
|
|
250
|
-
try {
|
|
251
|
-
// Check for conflicts
|
|
252
|
-
const existing = get().bookings.find((b) => b.id === id)
|
|
253
|
-
const assigneeId = newProfessionalId ?? existing?.professionalId
|
|
254
|
-
if (assigneeId) {
|
|
255
|
-
const hasConflict = await provider.checkConflict({
|
|
256
|
-
assigneeId,
|
|
257
|
-
startsAt: newStart,
|
|
258
|
-
endsAt: newEnd,
|
|
259
|
-
excludeBookingId: id,
|
|
260
|
-
})
|
|
261
|
-
if (hasConflict) {
|
|
262
|
-
toast.error('Time conflict detected', { description: 'This professional already has an appointment at this time.' })
|
|
263
|
-
throw new Error('Time conflict')
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
await provider.updateBooking(id, {
|
|
267
|
-
startsAt: newStart,
|
|
268
|
-
endsAt: newEnd,
|
|
269
|
-
...(newProfessionalId && { professionalId: newProfessionalId }),
|
|
270
|
-
})
|
|
271
|
-
const state = get()
|
|
272
|
-
if (state.dateRange.start && state.dateRange.end) {
|
|
273
|
-
const bookings = await provider.getBookings(getCurrentBookingQuery(state))
|
|
274
|
-
set({ bookings })
|
|
275
|
-
}
|
|
276
|
-
toast.success('Appointment rescheduled')
|
|
277
|
-
} catch (err: any) {
|
|
278
|
-
if (err?.message !== 'Time conflict') {
|
|
279
|
-
toast.error('Failed to reschedule', { description: err?.message })
|
|
280
|
-
}
|
|
281
|
-
throw err
|
|
282
|
-
}
|
|
283
|
-
},
|
|
284
|
-
|
|
285
|
-
setFilters(filters) {
|
|
286
|
-
set({
|
|
287
|
-
...(filters.professionalIds !== undefined && { selectedProfessionalIds: filters.professionalIds }),
|
|
288
|
-
...(filters.locationId !== undefined && { selectedLocationId: filters.locationId }),
|
|
289
|
-
...(filters.statuses !== undefined && { selectedStatuses: filters.statuses }),
|
|
290
|
-
})
|
|
291
|
-
// Re-fetch with new filters
|
|
292
|
-
const { dateRange } = get()
|
|
293
|
-
if (dateRange.start && dateRange.end) {
|
|
294
|
-
get().fetchBookings(dateRange)
|
|
295
|
-
}
|
|
296
|
-
},
|
|
297
|
-
|
|
298
|
-
setView(view) { set({ currentView: view }) },
|
|
299
|
-
setSelectedDate(date) { set({ selectedDate: date }) },
|
|
300
|
-
|
|
301
|
-
openAppointmentModal(mode, options) {
|
|
302
|
-
set({ appointmentModal: { open: true, mode, bookingId: options?.bookingId, prefill: options?.prefill, initialTab: options?.initialTab, externalSource: options?.externalSource } })
|
|
303
|
-
},
|
|
304
|
-
|
|
305
|
-
closeAppointmentModal() {
|
|
306
|
-
// Only toggle open — keep mode/bookingId/prefill intact so layout
|
|
307
|
-
// doesn't shift during the close animation
|
|
308
|
-
set((state) => ({ appointmentModal: { ...state.appointmentModal, open: false } }))
|
|
309
|
-
},
|
|
310
|
-
}))
|
|
311
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Agenda Plugin — Pure TypeScript types
|
|
3
|
-
// Zero dependencies. Maps to public archetype tables.
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
|
|
6
|
-
// ============================================================
|
|
7
|
-
// ENUMS / LITERALS
|
|
8
|
-
// ============================================================
|
|
9
|
-
|
|
10
|
-
/** Booking status lifecycle */
|
|
11
|
-
export type BookingStatus =
|
|
12
|
-
| 'scheduled'
|
|
13
|
-
| 'confirmed'
|
|
14
|
-
| 'in_progress'
|
|
15
|
-
| 'completed'
|
|
16
|
-
| 'cancelled'
|
|
17
|
-
| 'no_show'
|
|
18
|
-
|
|
19
|
-
/** Confirmation outreach status */
|
|
20
|
-
export type ConfirmationStatus = 'pending' | 'sent' | 'confirmed' | 'declined'
|
|
21
|
-
|
|
22
|
-
/** Booking type identifier — extensible via string literal union */
|
|
23
|
-
export type BookingTypeId = 'appointment' | 'task' | 'out_of_office' | 'block' | (string & {})
|
|
24
|
-
|
|
25
|
-
/** Configuration for a booking type (event type) */
|
|
26
|
-
export interface BookingTypeConfig {
|
|
27
|
-
id: BookingTypeId
|
|
28
|
-
label: string
|
|
29
|
-
icon: string
|
|
30
|
-
color: string
|
|
31
|
-
fields: {
|
|
32
|
-
/** Free-text event title (maps to the booking's display name). Used by
|
|
33
|
-
* simple / Google-Calendar-style events that have no client party. */
|
|
34
|
-
title?: boolean
|
|
35
|
-
client: boolean
|
|
36
|
-
professional: boolean
|
|
37
|
-
services: boolean
|
|
38
|
-
location: boolean
|
|
39
|
-
status: boolean
|
|
40
|
-
}
|
|
41
|
-
requiresServices: boolean
|
|
42
|
-
requiresClient: boolean
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ============================================================
|
|
46
|
-
// CORE ENTITIES
|
|
47
|
-
// ============================================================
|
|
48
|
-
|
|
49
|
-
/** A booking service line item (from public.appointment_items) */
|
|
50
|
-
export interface BookingService {
|
|
51
|
-
id: string
|
|
52
|
-
serviceId: string | null
|
|
53
|
-
name: string
|
|
54
|
-
durationMinutes: number
|
|
55
|
-
price: number
|
|
56
|
-
assigneeId: string | null
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Calendar booking — the main entity rendered on the calendar (from v_appointments view on orders) */
|
|
60
|
-
export interface CalendarBooking {
|
|
61
|
-
id: string
|
|
62
|
-
tenantId: string
|
|
63
|
-
kind: string
|
|
64
|
-
/** Lifecycle stage: booked, invoiced, paid, etc. */
|
|
65
|
-
stage?: string
|
|
66
|
-
/** Financial direction: credit, debit, null */
|
|
67
|
-
direction?: string | null
|
|
68
|
-
startsAt: string
|
|
69
|
-
endsAt: string | null
|
|
70
|
-
status: BookingStatus
|
|
71
|
-
notes: string | null
|
|
72
|
-
/** In unified model, orderId === id (the booking IS the order) */
|
|
73
|
-
orderId: string | null
|
|
74
|
-
locationId: string | null
|
|
75
|
-
metadata: Record<string, unknown>
|
|
76
|
-
|
|
77
|
-
// Client (party)
|
|
78
|
-
clientId: string | null
|
|
79
|
-
clientName: string | null
|
|
80
|
-
clientPhone: string | null
|
|
81
|
-
clientEmail: string | null
|
|
82
|
-
clientAvatarUrl: string | null
|
|
83
|
-
|
|
84
|
-
// Professional (assignee)
|
|
85
|
-
professionalId: string | null
|
|
86
|
-
professionalName: string | null
|
|
87
|
-
professionalAvatarUrl: string | null
|
|
88
|
-
|
|
89
|
-
// Location
|
|
90
|
-
locationName: string | null
|
|
91
|
-
|
|
92
|
-
// Order totals
|
|
93
|
-
orderTotal: number | null
|
|
94
|
-
orderStatus: string | null
|
|
95
|
-
/** Payment status from financial bridge (enriched at runtime) */
|
|
96
|
-
paymentStatus?: import('./financial-bridge').BookingPaymentStatus
|
|
97
|
-
|
|
98
|
-
// Aggregated services
|
|
99
|
-
services: BookingService[] | null
|
|
100
|
-
totalDurationMinutes: number | null
|
|
101
|
-
|
|
102
|
-
createdAt: string
|
|
103
|
-
updatedAt: string
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Professional / staff resource for the calendar */
|
|
107
|
-
export interface Professional {
|
|
108
|
-
id: string
|
|
109
|
-
name: string
|
|
110
|
-
avatarUrl: string | null
|
|
111
|
-
locationId: string | null
|
|
112
|
-
locationName: string | null
|
|
113
|
-
isActive: boolean
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** Working hours schedule (from public.schedules) */
|
|
117
|
-
export interface Schedule {
|
|
118
|
-
id: string
|
|
119
|
-
tenantId: string
|
|
120
|
-
kind: string
|
|
121
|
-
assigneeId: string | null
|
|
122
|
-
locationId: string | null
|
|
123
|
-
dayOfWeek: number | null
|
|
124
|
-
specificDate: string | null
|
|
125
|
-
startsAt: string
|
|
126
|
-
endsAt: string
|
|
127
|
-
isActive: boolean
|
|
128
|
-
metadata: Record<string, unknown>
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/** Available time slot */
|
|
132
|
-
export interface TimeSlot {
|
|
133
|
-
start: string
|
|
134
|
-
end: string
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** Confirmation record stored in booking metadata */
|
|
138
|
-
export interface ConfirmationRecord {
|
|
139
|
-
channel: string
|
|
140
|
-
sentAt: string
|
|
141
|
-
status: ConfirmationStatus
|
|
142
|
-
confirmedAt?: string
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// ============================================================
|
|
146
|
-
// STATUS CONFIGURATION
|
|
147
|
-
// ============================================================
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* When a status is available for selection relative to the booking date.
|
|
151
|
-
* - 'always' — any date (e.g. scheduled, confirmed, cancelled)
|
|
152
|
-
* - 'today_or_past' — booking is today or earlier (e.g. completed, no_show)
|
|
153
|
-
* - 'today_only' — booking is today (e.g. in_progress)
|
|
154
|
-
*/
|
|
155
|
-
export type StatusAvailability = 'always' | 'today_or_past' | 'today_only'
|
|
156
|
-
|
|
157
|
-
export interface StatusConfig {
|
|
158
|
-
value: string
|
|
159
|
-
label: string
|
|
160
|
-
color: string
|
|
161
|
-
/** When this status can be selected (default: 'always') */
|
|
162
|
-
availableWhen?: StatusAvailability
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/** Check if a status is available for a given booking date */
|
|
166
|
-
export function isStatusAvailable(status: StatusConfig, bookingStartsAt: string): boolean {
|
|
167
|
-
const when = status.availableWhen ?? 'always'
|
|
168
|
-
if (when === 'always') return true
|
|
169
|
-
const bookingDate = bookingStartsAt.slice(0, 10)
|
|
170
|
-
const today = new Date().toISOString().slice(0, 10)
|
|
171
|
-
if (when === 'today_or_past') return bookingDate <= today
|
|
172
|
-
if (when === 'today_only') return bookingDate === today
|
|
173
|
-
return true
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// ============================================================
|
|
177
|
-
// MODAL TAB EXTENSION
|
|
178
|
-
// ============================================================
|
|
179
|
-
|
|
180
|
-
/** Props passed to every booking modal tab component */
|
|
181
|
-
export interface BookingModalTabProps {
|
|
182
|
-
bookingId?: string
|
|
183
|
-
clientId: string
|
|
184
|
-
orderId: string | null
|
|
185
|
-
services: Array<{ name: string; price: number; durationMinutes: number }>
|
|
186
|
-
formatCurrency: (amount: number) => string
|
|
187
|
-
onDataChange?: () => void
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/** A tab registered in the booking edit modal */
|
|
191
|
-
export interface BookingModalTab {
|
|
192
|
-
id: string
|
|
193
|
-
label: string
|
|
194
|
-
icon: string
|
|
195
|
-
order: number
|
|
196
|
-
component: React.ComponentType<BookingModalTabProps>
|
|
197
|
-
visible?: (ctx: { mode: 'create' | 'edit'; clientId?: string; orderId?: string }) => boolean
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// ============================================================
|
|
201
|
-
// INPUT TYPES (create / mutate)
|
|
202
|
-
// ============================================================
|
|
203
|
-
|
|
204
|
-
export interface CreateBookingInput {
|
|
205
|
-
kind?: BookingTypeId
|
|
206
|
-
clientId: string
|
|
207
|
-
professionalId: string
|
|
208
|
-
locationId?: string
|
|
209
|
-
startsAt: string
|
|
210
|
-
notes?: string
|
|
211
|
-
/** Free-text event title for simple events (no client party). Maps to the
|
|
212
|
-
* booking's display name (clientName in the read model). */
|
|
213
|
-
title?: string
|
|
214
|
-
services: CreateBookingServiceInput[]
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export interface CreateBookingServiceInput {
|
|
218
|
-
serviceId: string
|
|
219
|
-
name: string
|
|
220
|
-
durationMinutes: number
|
|
221
|
-
price: number
|
|
222
|
-
assigneeId?: string
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export interface UpdateBookingInput {
|
|
226
|
-
clientId?: string
|
|
227
|
-
professionalId?: string
|
|
228
|
-
locationId?: string
|
|
229
|
-
startsAt?: string
|
|
230
|
-
endsAt?: string
|
|
231
|
-
status?: BookingStatus
|
|
232
|
-
notes?: string
|
|
233
|
-
/** Free-text event title for simple events (no client party). */
|
|
234
|
-
title?: string
|
|
235
|
-
services?: CreateBookingServiceInput[]
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
export interface SaveScheduleInput {
|
|
239
|
-
assigneeId: string
|
|
240
|
-
locationId?: string
|
|
241
|
-
dayOfWeek?: number
|
|
242
|
-
specificDate?: string
|
|
243
|
-
startsAt: string
|
|
244
|
-
endsAt: string
|
|
245
|
-
isActive?: boolean
|
|
246
|
-
metadata?: Record<string, unknown>
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// ============================================================
|
|
250
|
-
// QUERY / FILTER TYPES
|
|
251
|
-
// ============================================================
|
|
252
|
-
|
|
253
|
-
export interface BookingQuery {
|
|
254
|
-
dateRange: { start: string; end: string }
|
|
255
|
-
professionalIds?: string[]
|
|
256
|
-
locationId?: string
|
|
257
|
-
statuses?: string[]
|
|
258
|
-
kind?: string
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
export interface ConflictCheckParams {
|
|
262
|
-
assigneeId: string
|
|
263
|
-
startsAt: string
|
|
264
|
-
endsAt: string
|
|
265
|
-
excludeBookingId?: string
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
export interface AvailableSlotsParams {
|
|
269
|
-
assigneeId: string
|
|
270
|
-
date: string
|
|
271
|
-
durationMinutes: number
|
|
272
|
-
slotInterval?: number
|
|
273
|
-
}
|