@fayz-ai/plugin-agenda 0.1.8 → 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.
Files changed (41) hide show
  1. package/dist/components/AgendaMobileList.d.ts +10 -0
  2. package/dist/components/AgendaMobileList.d.ts.map +1 -0
  3. package/dist/components/AppointmentModal.d.ts +4 -2
  4. package/dist/components/AppointmentModal.d.ts.map +1 -1
  5. package/dist/components/AppointmentPopover.d.ts.map +1 -1
  6. package/dist/components/EventContextMenu.d.ts.map +1 -1
  7. package/dist/config.d.ts +62 -1
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/data/index.d.ts +1 -0
  10. package/dist/data/index.d.ts.map +1 -1
  11. package/dist/data/mock.d.ts +15 -1
  12. package/dist/data/mock.d.ts.map +1 -1
  13. package/dist/data/supabase.d.ts.map +1 -1
  14. package/dist/index.cjs +614 -308
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +452 -147
  19. package/dist/index.js.map +1 -1
  20. package/dist/locales/en.d.ts.map +1 -1
  21. package/dist/locales/pt-BR.d.ts.map +1 -1
  22. package/dist/store.d.ts +3 -0
  23. package/dist/store.d.ts.map +1 -1
  24. package/dist/types.d.ts +8 -0
  25. package/dist/types.d.ts.map +1 -1
  26. package/dist/views/CalendarView.d.ts.map +1 -1
  27. package/package.json +5 -8
  28. package/src/components/AgendaMobileList.tsx +103 -0
  29. package/src/components/AppointmentModal.tsx +86 -9
  30. package/src/components/AppointmentPopover.tsx +20 -10
  31. package/src/components/EventContextMenu.tsx +3 -0
  32. package/src/config.ts +104 -5
  33. package/src/data/index.ts +1 -0
  34. package/src/data/mock.ts +22 -5
  35. package/src/data/supabase.ts +13 -2
  36. package/src/index.ts +8 -3
  37. package/src/locales/en.ts +12 -0
  38. package/src/locales/pt-BR.ts +12 -0
  39. package/src/store.ts +4 -2
  40. package/src/types.ts +8 -0
  41. 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
- workingHours: options?.modules?.workingHours !== false,
234
- confirmations: options?.modules?.confirmations !== false,
235
- conflictDetection: options?.modules?.conflictDetection !== false,
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: options?.bookingTypes ?? DEFAULT_BOOKING_TYPES,
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
@@ -1,3 +1,4 @@
1
1
  export type { AgendaDataProvider } from './types'
2
2
  export { createMockAgendaProvider } from './mock'
3
+ export type { MockAgendaSeed, MockAgendaProviderOptions } from './mock'
3
4
  export { createSupabaseAgendaProvider } from './supabase'
package/src/data/mock.ts CHANGED
@@ -133,10 +133,26 @@ function createSampleSchedules(): Schedule[] {
133
133
  // Mock provider factory
134
134
  // ---------------------------------------------------------------------------
135
135
 
136
- export function createMockAgendaProvider(): AgendaDataProvider {
137
- const bookings: CalendarBooking[] = createSampleBookings()
138
- const schedules: Schedule[] = createSampleSchedules()
139
- const professionals: Professional[] = [...SAMPLE_PROFESSIONALS]
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 }),
@@ -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
- clientName: client?.name ?? null,
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: agendaRegistries,
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 (holidays).
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: agendaRegistries,
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",
@@ -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