@fayz-ai/plugin-agenda 0.1.7 → 0.2.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/dist/components/AgendaMobileList.d.ts +10 -0
- package/dist/components/AgendaMobileList.d.ts.map +1 -0
- package/dist/components/AppointmentModal.d.ts +4 -2
- package/dist/components/AppointmentModal.d.ts.map +1 -1
- package/dist/components/AppointmentPopover.d.ts.map +1 -1
- package/dist/components/EventContextMenu.d.ts.map +1 -1
- package/dist/config.d.ts +62 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/data/index.d.ts +1 -0
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/mock.d.ts +15 -1
- package/dist/data/mock.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/index.cjs +614 -308
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +452 -147
- package/dist/index.js.map +1 -1
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/store.d.ts +3 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/views/CalendarView.d.ts.map +1 -1
- package/package.json +5 -8
- package/src/components/AgendaMobileList.tsx +103 -0
- package/src/components/AppointmentModal.tsx +86 -9
- package/src/components/AppointmentPopover.tsx +20 -10
- package/src/components/EventContextMenu.tsx +3 -0
- package/src/config.ts +104 -5
- package/src/data/index.ts +1 -0
- package/src/data/mock.ts +22 -5
- package/src/data/supabase.ts +13 -2
- package/src/index.ts +8 -3
- package/src/locales/en.ts +12 -0
- package/src/locales/pt-BR.ts +12 -0
- package/src/store.ts +4 -2
- package/src/types.ts +8 -0
- package/src/views/CalendarView.tsx +220 -54
package/src/config.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PluginScope, VerticalId } from '@fayz-ai/core'
|
|
1
|
+
import type { PluginRegistryDef, PluginScope, VerticalId } from '@fayz-ai/core'
|
|
2
2
|
import type { EntityLookup } from '@fayz-ai/saas'
|
|
3
3
|
import type { AgendaDataProvider } from './data/types'
|
|
4
4
|
import type { StatusConfig, BookingTypeConfig } from './types'
|
|
@@ -29,6 +29,28 @@ export interface ConfirmationChannelOption {
|
|
|
29
29
|
icon: string
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
export interface CancellationReasonOption {
|
|
33
|
+
id: string
|
|
34
|
+
label: string
|
|
35
|
+
requiresNotes?: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface CancellationDetailsProvider {
|
|
39
|
+
listReasons(): Promise<CancellationReasonOption[]>
|
|
40
|
+
saveCancellationDetails(input: {
|
|
41
|
+
bookingId: string
|
|
42
|
+
reasonId: string
|
|
43
|
+
notes?: string
|
|
44
|
+
cancelledAt?: string
|
|
45
|
+
}): Promise<void>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ExternalBookingSource {
|
|
49
|
+
type: string
|
|
50
|
+
id?: string
|
|
51
|
+
[key: string]: unknown
|
|
52
|
+
}
|
|
53
|
+
|
|
32
54
|
export interface LocationOption {
|
|
33
55
|
id: string
|
|
34
56
|
name: string
|
|
@@ -60,6 +82,14 @@ export interface AgendaPluginOptions {
|
|
|
60
82
|
/** Booking type tabs shown in the appointment modal */
|
|
61
83
|
bookingTypes?: BookingTypeConfig[]
|
|
62
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Simplifies the agenda to Google-Calendar-style events (no client /
|
|
87
|
+
* professional / service). When set to 'simple', the plugin exposes a single
|
|
88
|
+
* "Evento" booking type whose modal shows only title / date-time / notes.
|
|
89
|
+
* Ignored when an explicit `bookingTypes` override is provided.
|
|
90
|
+
*/
|
|
91
|
+
eventMode?: 'simple'
|
|
92
|
+
|
|
63
93
|
/** Booking status options */
|
|
64
94
|
statuses?: StatusConfig[]
|
|
65
95
|
|
|
@@ -105,6 +135,15 @@ export interface AgendaPluginOptions {
|
|
|
105
135
|
/** Confirmation channel options */
|
|
106
136
|
confirmationChannels?: ConfirmationChannelOption[]
|
|
107
137
|
|
|
138
|
+
/** Optional app/plugin bridge for cancel-time reason capture. */
|
|
139
|
+
cancellationDetails?: CancellationDetailsProvider
|
|
140
|
+
|
|
141
|
+
/** Called after a booking is created from an external operational source. */
|
|
142
|
+
onBookingCreated?: (input: { bookingId: string; source?: ExternalBookingSource }) => void | Promise<void>
|
|
143
|
+
|
|
144
|
+
/** App-owned registries shown under /settings/agenda Properties. */
|
|
145
|
+
settingsRegistries?: PluginRegistryDef[]
|
|
146
|
+
|
|
108
147
|
/** Financial bridge for agenda × financial cross-plugin integration */
|
|
109
148
|
financialBridge?: AgendaFinancialBridge
|
|
110
149
|
|
|
@@ -172,6 +211,17 @@ const DEFAULT_BOOKING_TYPES: BookingTypeConfig[] = [
|
|
|
172
211
|
},
|
|
173
212
|
]
|
|
174
213
|
|
|
214
|
+
// Simple / Google-Calendar-style event — a single "Evento" type with just a
|
|
215
|
+
// free-text title, date-time and notes. No client / professional / service.
|
|
216
|
+
// Used by personal-finance-style agendas (see eventMode: 'simple').
|
|
217
|
+
const SIMPLE_EVENT_BOOKING_TYPES: BookingTypeConfig[] = [
|
|
218
|
+
{
|
|
219
|
+
id: 'event', label: 'Event', icon: 'Calendar', color: '#6366f1',
|
|
220
|
+
fields: { title: true, client: false, professional: false, services: false, location: false, status: false },
|
|
221
|
+
requiresServices: false, requiresClient: false,
|
|
222
|
+
},
|
|
223
|
+
]
|
|
224
|
+
|
|
175
225
|
// ---------------------------------------------------------------------------
|
|
176
226
|
// Resolved config — fully merged, no optionals
|
|
177
227
|
// ---------------------------------------------------------------------------
|
|
@@ -185,6 +235,24 @@ export interface AgendaModules {
|
|
|
185
235
|
financial: boolean
|
|
186
236
|
}
|
|
187
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Derived UI capabilities — computed ONCE in resolveConfig from the resolved
|
|
240
|
+
* bookingTypes so no component ever re-derives "is this a simple event agenda?"
|
|
241
|
+
* ad-hoc. A capability is enabled when ANY booking type uses that field. In
|
|
242
|
+
* eventMode:'simple' the single "Evento" type disables
|
|
243
|
+
* client/professional/services/location/status, so every consumer (popover,
|
|
244
|
+
* filter rail, modal) hides the professional/service/status machinery in one
|
|
245
|
+
* place instead of scattering `eventMode === 'simple'` checks.
|
|
246
|
+
*/
|
|
247
|
+
export interface AgendaCapabilities {
|
|
248
|
+
title: boolean
|
|
249
|
+
client: boolean
|
|
250
|
+
professional: boolean
|
|
251
|
+
services: boolean
|
|
252
|
+
location: boolean
|
|
253
|
+
status: boolean
|
|
254
|
+
}
|
|
255
|
+
|
|
188
256
|
export interface AgendaCurrency {
|
|
189
257
|
code: string
|
|
190
258
|
locale: string
|
|
@@ -193,6 +261,10 @@ export interface AgendaCurrency {
|
|
|
193
261
|
|
|
194
262
|
export interface ResolvedAgendaConfig {
|
|
195
263
|
modules: AgendaModules
|
|
264
|
+
/** Whether this is a simple Google-Calendar-style event agenda (eventMode:'simple'). */
|
|
265
|
+
simpleMode: boolean
|
|
266
|
+
/** Derived once from bookingTypes — see AgendaCapabilities. */
|
|
267
|
+
capabilities: AgendaCapabilities
|
|
196
268
|
labels: AgendaPluginLabels
|
|
197
269
|
currency: AgendaCurrency
|
|
198
270
|
bookingKind: string
|
|
@@ -207,6 +279,8 @@ export interface ResolvedAgendaConfig {
|
|
|
207
279
|
autoCreateOrder: boolean
|
|
208
280
|
locations: LocationOption[]
|
|
209
281
|
confirmationChannels: ConfirmationChannelOption[]
|
|
282
|
+
cancellationDetails?: CancellationDetailsProvider
|
|
283
|
+
onBookingCreated?: (input: { bookingId: string; source?: ExternalBookingSource }) => void | Promise<void>
|
|
210
284
|
contactLookup?: EntityLookup
|
|
211
285
|
clientEntityDef?: EntityDef
|
|
212
286
|
serviceLookup?: EntityLookup
|
|
@@ -228,22 +302,45 @@ export interface ResolvedAgendaConfig {
|
|
|
228
302
|
// ---------------------------------------------------------------------------
|
|
229
303
|
|
|
230
304
|
export function resolveConfig(options?: AgendaPluginOptions): ResolvedAgendaConfig {
|
|
305
|
+
// ── eventMode:'simple' derives ALL module + capability defaults ONCE, here. ──
|
|
306
|
+
// Simple = Google-Calendar-style events (title/description + timestamp). It
|
|
307
|
+
// flips professional/service machinery OFF by default: no working-hours /
|
|
308
|
+
// schedule checks, no confirmations outreach, no conflict (double-booking)
|
|
309
|
+
// detection, no location. Explicit `modules.*` re-enable individually. Full
|
|
310
|
+
// mode (beauty) is unchanged — every default below matches the old behavior
|
|
311
|
+
// when `simple` is false. Field-level machinery (client/professional/
|
|
312
|
+
// services/location/status/title) is derived into `capabilities` from the
|
|
313
|
+
// resolved bookingTypes, so components never test eventMode themselves.
|
|
314
|
+
const simple = options?.eventMode === 'simple'
|
|
315
|
+
const bookingTypes = options?.bookingTypes
|
|
316
|
+
?? (simple ? SIMPLE_EVENT_BOOKING_TYPES : DEFAULT_BOOKING_TYPES)
|
|
317
|
+
const capabilities: AgendaCapabilities = {
|
|
318
|
+
title: bookingTypes.some((bt) => bt.fields.title === true),
|
|
319
|
+
client: bookingTypes.some((bt) => bt.fields.client),
|
|
320
|
+
professional: bookingTypes.some((bt) => bt.fields.professional),
|
|
321
|
+
services: bookingTypes.some((bt) => bt.fields.services),
|
|
322
|
+
location: bookingTypes.some((bt) => bt.fields.location),
|
|
323
|
+
status: bookingTypes.some((bt) => bt.fields.status),
|
|
324
|
+
}
|
|
231
325
|
return {
|
|
232
326
|
modules: {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
327
|
+
// Off-unless-explicit in simple mode (`?? !simple`); default-on in full.
|
|
328
|
+
workingHours: options?.modules?.workingHours ?? !simple,
|
|
329
|
+
confirmations: options?.modules?.confirmations ?? !simple,
|
|
330
|
+
conflictDetection: options?.modules?.conflictDetection ?? !simple,
|
|
236
331
|
dragAndDrop: options?.modules?.dragAndDrop !== false,
|
|
237
332
|
locationSelection: options?.modules?.locationSelection === true,
|
|
238
333
|
financial: options?.modules?.financial ?? !!options?.financialBridge,
|
|
239
334
|
},
|
|
335
|
+
simpleMode: simple,
|
|
336
|
+
capabilities,
|
|
240
337
|
labels: { ...DEFAULT_LABELS, ...options?.labels },
|
|
241
338
|
currency: { ...DEFAULT_CURRENCY, ...options?.currency },
|
|
242
339
|
bookingKind: options?.bookingKind ?? 'appointment',
|
|
243
340
|
orderKind: options?.orderKind ?? 'service_order',
|
|
244
341
|
scheduleKind: options?.scheduleKind ?? 'working_hours',
|
|
245
342
|
statuses: options?.statuses ?? DEFAULT_STATUSES,
|
|
246
|
-
bookingTypes
|
|
343
|
+
bookingTypes,
|
|
247
344
|
businessHours: options?.businessHours ?? { startTime: '07:00', endTime: '21:00' },
|
|
248
345
|
slotDuration: options?.slotDuration ?? 30,
|
|
249
346
|
professionalKind: options?.professionalKind ?? 'staff',
|
|
@@ -251,6 +348,8 @@ export function resolveConfig(options?: AgendaPluginOptions): ResolvedAgendaConf
|
|
|
251
348
|
autoCreateOrder: options?.autoCreateOrder !== false,
|
|
252
349
|
locations: options?.locations ?? [],
|
|
253
350
|
confirmationChannels: options?.confirmationChannels ?? [],
|
|
351
|
+
cancellationDetails: options?.cancellationDetails,
|
|
352
|
+
onBookingCreated: options?.onBookingCreated,
|
|
254
353
|
contactLookup: options?.contactLookup,
|
|
255
354
|
clientEntityDef: options?.clientEntityDef,
|
|
256
355
|
serviceLookup: options?.serviceLookup,
|
package/src/data/index.ts
CHANGED
package/src/data/mock.ts
CHANGED
|
@@ -133,10 +133,26 @@ function createSampleSchedules(): Schedule[] {
|
|
|
133
133
|
// Mock provider factory
|
|
134
134
|
// ---------------------------------------------------------------------------
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
/**
|
|
137
|
+
* Pre-populated agenda data. Any omitted collection falls back to the built-in
|
|
138
|
+
* salon-style sample. Pass `bookings` (and optionally an empty `professionals`)
|
|
139
|
+
* to render a calendar seeded with app-specific events.
|
|
140
|
+
*/
|
|
141
|
+
export interface MockAgendaSeed {
|
|
142
|
+
bookings?: CalendarBooking[]
|
|
143
|
+
professionals?: Professional[]
|
|
144
|
+
schedules?: Schedule[]
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface MockAgendaProviderOptions {
|
|
148
|
+
seed?: MockAgendaSeed
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function createMockAgendaProvider(options?: MockAgendaProviderOptions): AgendaDataProvider {
|
|
152
|
+
const seed = options?.seed
|
|
153
|
+
const bookings: CalendarBooking[] = seed?.bookings ? [...seed.bookings] : createSampleBookings()
|
|
154
|
+
const schedules: Schedule[] = seed?.schedules ? [...seed.schedules] : createSampleSchedules()
|
|
155
|
+
const professionals: Professional[] = seed?.professionals ? [...seed.professionals] : [...SAMPLE_PROFESSIONALS]
|
|
140
156
|
|
|
141
157
|
return {
|
|
142
158
|
async getLocations(): Promise<Array<{ id: string; name: string }>> {
|
|
@@ -167,7 +183,7 @@ export function createMockAgendaProvider(): AgendaDataProvider {
|
|
|
167
183
|
startsAt: input.startsAt, endsAt: addMinutes(input.startsAt, totalDuration || 30),
|
|
168
184
|
status: 'scheduled', notes: input.notes ?? null,
|
|
169
185
|
orderId: input.services.length > 0 ? uid() : null, locationId: input.locationId ?? null, metadata: {},
|
|
170
|
-
clientId: input.clientId, clientName: 'New Client', clientPhone: null, clientEmail: null, clientAvatarUrl: null,
|
|
186
|
+
clientId: input.clientId, clientName: input.title?.trim() || 'New Client', clientPhone: null, clientEmail: null, clientAvatarUrl: null,
|
|
171
187
|
professionalId: input.professionalId,
|
|
172
188
|
professionalName: professionals.find((p) => p.id === input.professionalId)?.name ?? 'Unknown',
|
|
173
189
|
professionalAvatarUrl: null,
|
|
@@ -201,6 +217,7 @@ export function createMockAgendaProvider(): AgendaDataProvider {
|
|
|
201
217
|
...existing,
|
|
202
218
|
...(data.startsAt && { startsAt: data.startsAt }),
|
|
203
219
|
...(data.endsAt && { endsAt: data.endsAt }),
|
|
220
|
+
...(data.title !== undefined && { clientName: data.title }),
|
|
204
221
|
...(data.status && { status: data.status }),
|
|
205
222
|
...(data.notes !== undefined && { notes: data.notes ?? null }),
|
|
206
223
|
...(data.professionalId && { professionalId: data.professionalId }),
|
package/src/data/supabase.ts
CHANGED
|
@@ -150,7 +150,8 @@ async function hydrateCanonicalBookings(core: any, bookings: any[]): Promise<Cal
|
|
|
150
150
|
locationId: booking.location_id,
|
|
151
151
|
metadata: booking.metadata ?? {},
|
|
152
152
|
clientId: booking.party_id,
|
|
153
|
-
|
|
153
|
+
// Simple events (no party) fall back to the title stored in metadata.
|
|
154
|
+
clientName: client?.name ?? (booking.metadata?.title as string | undefined) ?? null,
|
|
154
155
|
clientPhone: client?.phone ?? null,
|
|
155
156
|
clientEmail: client?.email ?? null,
|
|
156
157
|
clientAvatarUrl: client?.avatar_url ?? null,
|
|
@@ -253,6 +254,10 @@ export function createSupabaseAgendaProvider(): AgendaDataProvider {
|
|
|
253
254
|
contactName = person?.name as string | undefined
|
|
254
255
|
}
|
|
255
256
|
|
|
257
|
+
// Simple / Google-Calendar-style events have no client party — persist the
|
|
258
|
+
// free-text title in metadata so the read model can surface it as the name.
|
|
259
|
+
const eventTitle = input.title?.trim() || undefined
|
|
260
|
+
|
|
256
261
|
const { data: order, error: orderErr } = await core.from('orders').insert({
|
|
257
262
|
tenant_id: tenantId,
|
|
258
263
|
kind: input.kind ?? 'appointment',
|
|
@@ -267,6 +272,7 @@ export function createSupabaseAgendaProvider(): AgendaDataProvider {
|
|
|
267
272
|
source: 'agenda',
|
|
268
273
|
...(serviceNames ? { serviceNames, itemsSummary: serviceNames } : {}),
|
|
269
274
|
...(contactName ? { contactName } : {}),
|
|
275
|
+
...(eventTitle ? { title: eventTitle } : {}),
|
|
270
276
|
},
|
|
271
277
|
}).select('id').single()
|
|
272
278
|
if (orderErr) throw orderErr
|
|
@@ -286,6 +292,7 @@ export function createSupabaseAgendaProvider(): AgendaDataProvider {
|
|
|
286
292
|
source: 'agenda',
|
|
287
293
|
...(serviceNames ? { serviceNames, itemsSummary: serviceNames } : {}),
|
|
288
294
|
...(contactName ? { contactName } : {}),
|
|
295
|
+
...(eventTitle ? { title: eventTitle } : {}),
|
|
289
296
|
},
|
|
290
297
|
}).select('id').single()
|
|
291
298
|
if (bookingErr) throw bookingErr
|
|
@@ -315,7 +322,7 @@ export function createSupabaseAgendaProvider(): AgendaDataProvider {
|
|
|
315
322
|
async updateBooking(id: string, data: UpdateBookingInput): Promise<CalendarBooking> {
|
|
316
323
|
const { core } = getClients()
|
|
317
324
|
const { data: existing, error: existingErr } = await core.from('bookings')
|
|
318
|
-
.select('id, order_id, starts_at, assignee_id')
|
|
325
|
+
.select('id, order_id, starts_at, assignee_id, metadata')
|
|
319
326
|
.eq('id', id)
|
|
320
327
|
.single()
|
|
321
328
|
if (existingErr) throw existingErr
|
|
@@ -323,6 +330,10 @@ export function createSupabaseAgendaProvider(): AgendaDataProvider {
|
|
|
323
330
|
const updates: Record<string, unknown> = {}
|
|
324
331
|
if (data.startsAt) updates.starts_at = data.startsAt
|
|
325
332
|
if (data.endsAt) updates.ends_at = data.endsAt
|
|
333
|
+
// Simple-event title lives in metadata (no client party) — merge, don't clobber.
|
|
334
|
+
if (data.title !== undefined) {
|
|
335
|
+
updates.metadata = { ...(existing?.metadata as Record<string, unknown> ?? {}), title: data.title }
|
|
336
|
+
}
|
|
326
337
|
if (data.status) {
|
|
327
338
|
// Only update the agenda status — never touch the financial stage
|
|
328
339
|
updates.status = data.status
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,7 @@ export function createAgendaPlugin(options?: AgendaPluginOptions): PluginManifes
|
|
|
25
25
|
() => createMockAgendaProvider(),
|
|
26
26
|
)
|
|
27
27
|
const store = createAgendaStore(provider, options?.financialBridge)
|
|
28
|
+
const registries = [...agendaRegistries, ...(options?.settingsRegistries ?? [])]
|
|
28
29
|
|
|
29
30
|
// Register schedule block config globally so ScheduleEditor can read it
|
|
30
31
|
setScheduleBlockConfig({
|
|
@@ -152,19 +153,19 @@ export function createAgendaPlugin(options?: AgendaPluginOptions): PluginManifes
|
|
|
152
153
|
},
|
|
153
154
|
],
|
|
154
155
|
|
|
155
|
-
registries
|
|
156
|
+
registries,
|
|
156
157
|
|
|
157
158
|
settings: [
|
|
158
159
|
{
|
|
159
160
|
id: 'agenda',
|
|
160
161
|
label: 'Agenda',
|
|
161
162
|
icon: 'Calendar',
|
|
162
|
-
// Standard layout: General | Integrations (Google Calendar addon) | Properties
|
|
163
|
+
// Standard layout: General | Integrations (Google Calendar addon) | Properties.
|
|
163
164
|
component: (() => {
|
|
164
165
|
const AgendaSettingsTab: React.FC = () => React.createElement(PluginSettingsPanel, {
|
|
165
166
|
title: 'Agenda',
|
|
166
167
|
generalSettings: React.createElement(AgendaGeneralSettings),
|
|
167
|
-
registries
|
|
168
|
+
registries,
|
|
168
169
|
hostPluginId: 'agenda',
|
|
169
170
|
routeBase: '/settings/agenda',
|
|
170
171
|
})
|
|
@@ -183,6 +184,10 @@ export function createAgendaPlugin(options?: AgendaPluginOptions): PluginManifes
|
|
|
183
184
|
export type { AgendaPluginOptions } from './config'
|
|
184
185
|
export type { ResolvedAgendaConfig } from './config'
|
|
185
186
|
export type { AgendaDataProvider } from './data/types'
|
|
187
|
+
// Seedable in-memory provider — apps pass a seed to render the calendar with
|
|
188
|
+
// app-specific events instead of the built-in salon sample.
|
|
189
|
+
export { createMockAgendaProvider } from './data/mock'
|
|
190
|
+
export type { MockAgendaSeed, MockAgendaProviderOptions } from './data/mock'
|
|
186
191
|
export type { AgendaFinancialBridge, BookingPaymentStatus, BookingPaymentSummary, BookingPaymentDetail } from './financial-bridge'
|
|
187
192
|
export { createFinancialBridge, computeCommissionAmount } from './bridges/create-financial-bridge'
|
|
188
193
|
|
package/src/locales/en.ts
CHANGED
|
@@ -81,8 +81,12 @@ export const en: Record<string, string> = {
|
|
|
81
81
|
"agenda.calendar.today": "Today",
|
|
82
82
|
"agenda.calendar.viewAgenda": "Agenda",
|
|
83
83
|
"agenda.calendar.viewDay": "Day",
|
|
84
|
+
"agenda.calendar.viewList": "List",
|
|
84
85
|
"agenda.calendar.viewMonth": "Month",
|
|
85
86
|
"agenda.calendar.viewWeek": "Week",
|
|
87
|
+
"agenda.list.today": "Today",
|
|
88
|
+
"agenda.list.tomorrow": "Tomorrow",
|
|
89
|
+
"agenda.list.empty": "No upcoming items",
|
|
86
90
|
"agenda.confirmations.confirmed": "Confirmed:",
|
|
87
91
|
"agenda.confirmations.empty": "No pending confirmations",
|
|
88
92
|
"agenda.confirmations.emptySubtitle": "All upcoming appointments have been confirmed",
|
|
@@ -93,6 +97,10 @@ export const en: Record<string, string> = {
|
|
|
93
97
|
"agenda.confirmations.call": "Call",
|
|
94
98
|
"agenda.confirmations.markConfirmed": "Mark Confirmed",
|
|
95
99
|
"agenda.confirmations.markCancelled": "Mark Cancelled",
|
|
100
|
+
"agenda.cancellation.loadingReasons": "Loading reasons...",
|
|
101
|
+
"agenda.cancellation.notesOptional": "Cancellation notes (optional)",
|
|
102
|
+
"agenda.cancellation.notesRequired": "Cancellation notes required",
|
|
103
|
+
"agenda.cancellation.selectReason": "Select cancellation reason",
|
|
96
104
|
"agenda.contextMenu.changeStatus": "Change status",
|
|
97
105
|
"agenda.contextMenu.delete": "Delete",
|
|
98
106
|
"agenda.contextMenu.edit": "Edit",
|
|
@@ -193,7 +201,11 @@ export const en: Record<string, string> = {
|
|
|
193
201
|
"agenda.appointment.addNotes": "Add notes",
|
|
194
202
|
"agenda.appointment.duration": "Duration (minutes)",
|
|
195
203
|
"agenda.appointment.price": "Price",
|
|
204
|
+
"agenda.appointment.title": "Title",
|
|
205
|
+
"agenda.appointment.addTitle": "Add a title",
|
|
206
|
+
"agenda.appointment.untitled": "(no title)",
|
|
196
207
|
"agenda.bookingType.appointment": "Appointment",
|
|
208
|
+
"agenda.bookingType.event": "Event",
|
|
197
209
|
"agenda.bookingType.task": "Task",
|
|
198
210
|
"agenda.bookingType.out_of_office": "Out of Office",
|
|
199
211
|
"agenda.bookingType.block": "Block",
|
package/src/locales/pt-BR.ts
CHANGED
|
@@ -81,8 +81,12 @@ export const ptBR: Record<string, string> = {
|
|
|
81
81
|
"agenda.calendar.today": "Hoje",
|
|
82
82
|
"agenda.calendar.viewAgenda": "Agenda",
|
|
83
83
|
"agenda.calendar.viewDay": "Dia",
|
|
84
|
+
"agenda.calendar.viewList": "Lista",
|
|
84
85
|
"agenda.calendar.viewMonth": "Mês",
|
|
85
86
|
"agenda.calendar.viewWeek": "Semana",
|
|
87
|
+
"agenda.list.today": "Hoje",
|
|
88
|
+
"agenda.list.tomorrow": "Amanhã",
|
|
89
|
+
"agenda.list.empty": "Nenhum item próximo",
|
|
86
90
|
"agenda.confirmations.confirmed": "Confirmado:",
|
|
87
91
|
"agenda.confirmations.empty": "Nenhuma confirmação pendente",
|
|
88
92
|
"agenda.confirmations.emptySubtitle": "Todos os agendamentos futuros foram confirmados",
|
|
@@ -93,6 +97,10 @@ export const ptBR: Record<string, string> = {
|
|
|
93
97
|
"agenda.confirmations.call": "Ligar",
|
|
94
98
|
"agenda.confirmations.markConfirmed": "Marcar Confirmado",
|
|
95
99
|
"agenda.confirmations.markCancelled": "Marcar Cancelado",
|
|
100
|
+
"agenda.cancellation.loadingReasons": "Carregando motivos...",
|
|
101
|
+
"agenda.cancellation.notesOptional": "Observações do cancelamento (opcional)",
|
|
102
|
+
"agenda.cancellation.notesRequired": "Observações do cancelamento obrigatórias",
|
|
103
|
+
"agenda.cancellation.selectReason": "Selecionar motivo do cancelamento",
|
|
96
104
|
"agenda.contextMenu.changeStatus": "Alterar status",
|
|
97
105
|
"agenda.contextMenu.delete": "Excluir",
|
|
98
106
|
"agenda.contextMenu.edit": "Editar",
|
|
@@ -193,7 +201,11 @@ export const ptBR: Record<string, string> = {
|
|
|
193
201
|
"agenda.appointment.addNotes": "Adicionar observações",
|
|
194
202
|
"agenda.appointment.duration": "Duração (minutos)",
|
|
195
203
|
"agenda.appointment.price": "Preço",
|
|
204
|
+
"agenda.appointment.title": "Título",
|
|
205
|
+
"agenda.appointment.addTitle": "Adicionar um título",
|
|
206
|
+
"agenda.appointment.untitled": "(sem título)",
|
|
196
207
|
"agenda.bookingType.appointment": "Agendamento",
|
|
208
|
+
"agenda.bookingType.event": "Evento",
|
|
197
209
|
"agenda.bookingType.task": "Tarefa",
|
|
198
210
|
"agenda.bookingType.out_of_office": "Ausência",
|
|
199
211
|
"agenda.bookingType.block": "Bloqueio",
|
package/src/store.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { toast } from '@fayz-ai/ui'
|
|
|
4
4
|
import { getScheduleBlockConfig, setScheduleBlockConfig } from '@fayz-ai/saas'
|
|
5
5
|
import type { AgendaDataProvider } from './data/types'
|
|
6
6
|
import type { AgendaFinancialBridge } from './financial-bridge'
|
|
7
|
+
import type { ExternalBookingSource } from './config'
|
|
7
8
|
import type {
|
|
8
9
|
CalendarBooking, Professional, Schedule,
|
|
9
10
|
CreateBookingInput, UpdateBookingInput, BookingQuery,
|
|
@@ -48,6 +49,7 @@ export interface AgendaUIState {
|
|
|
48
49
|
bookingId?: string
|
|
49
50
|
prefill?: Partial<CreateBookingInput> & { endsAt?: string }
|
|
50
51
|
initialTab?: string
|
|
52
|
+
externalSource?: ExternalBookingSource
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
// Actions
|
|
@@ -64,7 +66,7 @@ export interface AgendaUIState {
|
|
|
64
66
|
setFilters(filters: Partial<{ professionalIds: string[]; locationId: string | null; statuses: string[] }>): void
|
|
65
67
|
setView(view: string): void
|
|
66
68
|
setSelectedDate(date: string): void
|
|
67
|
-
openAppointmentModal(mode: 'create' | 'edit', options?: { bookingId?: string; prefill?: Partial<CreateBookingInput> & { endsAt?: string }; initialTab?: string }): void
|
|
69
|
+
openAppointmentModal(mode: 'create' | 'edit', options?: { bookingId?: string; prefill?: Partial<CreateBookingInput> & { endsAt?: string }; initialTab?: string; externalSource?: ExternalBookingSource }): void
|
|
68
70
|
closeAppointmentModal(): void
|
|
69
71
|
}
|
|
70
72
|
|
|
@@ -292,7 +294,7 @@ export function createAgendaStore(provider: AgendaDataProvider, financialBridge?
|
|
|
292
294
|
setSelectedDate(date) { set({ selectedDate: date }) },
|
|
293
295
|
|
|
294
296
|
openAppointmentModal(mode, options) {
|
|
295
|
-
set({ appointmentModal: { open: true, mode, bookingId: options?.bookingId, prefill: options?.prefill, initialTab: options?.initialTab } })
|
|
297
|
+
set({ appointmentModal: { open: true, mode, bookingId: options?.bookingId, prefill: options?.prefill, initialTab: options?.initialTab, externalSource: options?.externalSource } })
|
|
296
298
|
},
|
|
297
299
|
|
|
298
300
|
closeAppointmentModal() {
|
package/src/types.ts
CHANGED
|
@@ -29,6 +29,9 @@ export interface BookingTypeConfig {
|
|
|
29
29
|
icon: string
|
|
30
30
|
color: string
|
|
31
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
|
|
32
35
|
client: boolean
|
|
33
36
|
professional: boolean
|
|
34
37
|
services: boolean
|
|
@@ -205,6 +208,9 @@ export interface CreateBookingInput {
|
|
|
205
208
|
locationId?: string
|
|
206
209
|
startsAt: string
|
|
207
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
|
|
208
214
|
services: CreateBookingServiceInput[]
|
|
209
215
|
}
|
|
210
216
|
|
|
@@ -224,6 +230,8 @@ export interface UpdateBookingInput {
|
|
|
224
230
|
endsAt?: string
|
|
225
231
|
status?: BookingStatus
|
|
226
232
|
notes?: string
|
|
233
|
+
/** Free-text event title for simple events (no client party). */
|
|
234
|
+
title?: string
|
|
227
235
|
services?: CreateBookingServiceInput[]
|
|
228
236
|
}
|
|
229
237
|
|