@fayz-ai/plugin-agenda 0.9.0 → 0.9.1

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 (153) hide show
  1. package/README.md +3 -3
  2. package/dist/bridges/create-financial-bridge.d.ts +1 -1
  3. package/dist/bridges/create-financial-bridge.d.ts.map +1 -1
  4. package/dist/bridges/financial-bridge.d.ts.map +1 -0
  5. package/dist/{chunk-Q2PAWBPR.js → chunk-76ZOHRBM.js} +2 -2
  6. package/dist/{chunk-Q2PAWBPR.js.map → chunk-76ZOHRBM.js.map} +1 -1
  7. package/dist/components/PaymentStatusBadge.d.ts +1 -1
  8. package/dist/components/PaymentStatusBadge.d.ts.map +1 -1
  9. package/dist/config.d.ts +2 -2
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/{AgendaContext.d.ts → context.d.ts} +1 -1
  12. package/dist/context.d.ts.map +1 -0
  13. package/dist/data/registries.d.ts.map +1 -0
  14. package/dist/index.d.ts +2 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +170 -7
  17. package/dist/index.js.map +1 -1
  18. package/dist/lib/message-events.d.ts +12 -0
  19. package/dist/lib/message-events.d.ts.map +1 -0
  20. package/dist/public/{data.d.ts → data/index.d.ts} +4 -4
  21. package/dist/public/data/index.d.ts.map +1 -0
  22. package/dist/public/{data.supabase.d.ts → data/supabase.d.ts} +2 -2
  23. package/dist/public/data/supabase.d.ts.map +1 -0
  24. package/dist/public/{hooks.d.ts → hooks/index.d.ts} +4 -4
  25. package/dist/public/hooks/index.d.ts.map +1 -0
  26. package/dist/public/index.d.ts +4 -4
  27. package/dist/public/index.d.ts.map +1 -1
  28. package/dist/public/index.js +3 -3
  29. package/dist/public/index.js.map +1 -1
  30. package/dist/public/{format.d.ts → lib/format.d.ts} +1 -1
  31. package/dist/public/lib/format.d.ts.map +1 -0
  32. package/dist/public/views/BookingWidget.d.ts.map +1 -0
  33. package/dist/public/{steps → views/steps}/ContactStep.d.ts +1 -1
  34. package/dist/public/views/steps/ContactStep.d.ts.map +1 -0
  35. package/dist/public/{steps → views/steps}/DateTimeStep.d.ts +3 -3
  36. package/dist/public/views/steps/DateTimeStep.d.ts.map +1 -0
  37. package/dist/public/{steps → views/steps}/OverviewStep.d.ts +1 -1
  38. package/dist/public/views/steps/OverviewStep.d.ts.map +1 -0
  39. package/dist/public/{steps → views/steps}/PaymentStep.d.ts +1 -1
  40. package/dist/public/views/steps/PaymentStep.d.ts.map +1 -0
  41. package/dist/store.d.ts +1 -1
  42. package/dist/store.d.ts.map +1 -1
  43. package/dist/styles/calendar-styles.d.ts.map +1 -0
  44. package/dist/types.d.ts +1 -1
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/{AgendaPage.d.ts → views/AgendaPage.d.ts} +3 -3
  47. package/dist/views/AgendaPage.d.ts.map +1 -0
  48. package/package.json +6 -9
  49. package/dist/AgendaContext.d.ts.map +0 -1
  50. package/dist/AgendaPage.d.ts.map +0 -1
  51. package/dist/calendar-styles.d.ts.map +0 -1
  52. package/dist/financial-bridge.d.ts.map +0 -1
  53. package/dist/public/BookingWidget.d.ts.map +0 -1
  54. package/dist/public/data.d.ts.map +0 -1
  55. package/dist/public/data.supabase.d.ts.map +0 -1
  56. package/dist/public/format.d.ts.map +0 -1
  57. package/dist/public/hooks.d.ts.map +0 -1
  58. package/dist/public/steps/ContactStep.d.ts.map +0 -1
  59. package/dist/public/steps/DateTimeStep.d.ts.map +0 -1
  60. package/dist/public/steps/OverviewStep.d.ts.map +0 -1
  61. package/dist/public/steps/PaymentStep.d.ts.map +0 -1
  62. package/dist/registries.d.ts.map +0 -1
  63. package/src/AgendaContext.tsx +0 -11
  64. package/src/AgendaPage.tsx +0 -33
  65. package/src/bridges/create-financial-bridge.ts +0 -296
  66. package/src/calendar-styles.css +0 -245
  67. package/src/calendar-styles.ts +0 -285
  68. package/src/components/AgendaGeneralSettings.tsx +0 -86
  69. package/src/components/AgendaMobileList.tsx +0 -103
  70. package/src/components/AppointmentModal.tsx +0 -1050
  71. package/src/components/AppointmentPopover.tsx +0 -231
  72. package/src/components/BookingPaymentPanel.tsx +0 -394
  73. package/src/components/CalendarSidebar.tsx +0 -119
  74. package/src/components/CalendarToolbar.tsx +0 -86
  75. package/src/components/EventContextMenu.tsx +0 -125
  76. package/src/components/FloatingPanel.tsx +0 -123
  77. package/src/components/MiniCalendar.tsx +0 -120
  78. package/src/components/PaymentStatusBadge.tsx +0 -62
  79. package/src/components/StatusBadge.tsx +0 -23
  80. package/src/config.ts +0 -368
  81. package/src/data/index.ts +0 -4
  82. package/src/data/mock.ts +0 -344
  83. package/src/data/supabase.ts +0 -636
  84. package/src/data/types.ts +0 -37
  85. package/src/financial-bridge.ts +0 -96
  86. package/src/hooks/useAgendaSettings.ts +0 -17
  87. package/src/index.ts +0 -297
  88. package/src/integrations/google-calendar/GoogleCalendarSettings.tsx +0 -136
  89. package/src/integrations/google-calendar/components/CalendarColorDot.tsx +0 -23
  90. package/src/integrations/google-calendar/components/ChannelMappingList.tsx +0 -329
  91. package/src/integrations/google-calendar/components/ImportEventsWizard.tsx +0 -215
  92. package/src/integrations/google-calendar/components/useAgendaLookups.ts +0 -100
  93. package/src/integrations/google-calendar/connector.ts +0 -20
  94. package/src/integrations/google-calendar/connectorDef.tsx +0 -185
  95. package/src/integrations/google-calendar/data/supabase.ts +0 -170
  96. package/src/integrations/google-calendar/data/tables.ts +0 -12
  97. package/src/integrations/google-calendar/index.ts +0 -45
  98. package/src/integrations/google-calendar/integration.test.ts +0 -199
  99. package/src/integrations/google-calendar/mapping.ts +0 -183
  100. package/src/integrations/google-calendar/migrations/001_google_calendar.sql +0 -97
  101. package/src/integrations/google-calendar/migrations/002_smart_sync.sql +0 -517
  102. package/src/integrations/google-calendar/migrations/003_plg_rename.sql +0 -273
  103. package/src/integrations/google-calendar/types.ts +0 -142
  104. package/src/lib/tenant.ts +0 -3
  105. package/src/locales/en.ts +0 -220
  106. package/src/locales/index.ts +0 -7
  107. package/src/locales/pt-BR.ts +0 -220
  108. package/src/migrations/000b_v_bookings_rename.sql +0 -21
  109. package/src/migrations/000c_dedupe_customer_phones.sql +0 -43
  110. package/src/migrations/001_public_booking.sql +0 -296
  111. package/src/migrations/002_v_bookings_compat.sql +0 -14
  112. package/src/migrations/003_public_client_kind.sql +0 -175
  113. package/src/migrations/004_public_booking_assignee.sql +0 -176
  114. package/src/migrations/005_agent_rpcs.sql +0 -168
  115. package/src/migrations/006_agent_rpc_localtime.sql +0 -181
  116. package/src/migrations/007_schedule_exception_precedence.sql +0 -290
  117. package/src/migrations/008_agent_rpc_exception_precedence.sql +0 -172
  118. package/src/migrations/009_public_booking_location.sql +0 -162
  119. package/src/migrations/010_public_service_curation.sql +0 -31
  120. package/src/migrations/011_default_assignee.sql +0 -167
  121. package/src/migrations/012_drop_legacy_booking_overload.sql +0 -29
  122. package/src/migrations/013_normalize_v_appointments.sql +0 -199
  123. package/src/migrations/014_agent_rpc_update_appointment.sql +0 -199
  124. package/src/migrations/index.ts +0 -2377
  125. package/src/public/BookingWidget.tsx +0 -324
  126. package/src/public/components/BrandHeader.tsx +0 -27
  127. package/src/public/components/ServiceCard.tsx +0 -39
  128. package/src/public/components/SidebarExtras.tsx +0 -30
  129. package/src/public/components/StepRailItem.tsx +0 -27
  130. package/src/public/components/SuccessPanel.tsx +0 -27
  131. package/src/public/components/SummaryRail.tsx +0 -90
  132. package/src/public/components/defaults.ts +0 -14
  133. package/src/public/context.tsx +0 -103
  134. package/src/public/createPublicBookingPlugin.ts +0 -281
  135. package/src/public/data.supabase.ts +0 -97
  136. package/src/public/data.ts +0 -105
  137. package/src/public/format.ts +0 -96
  138. package/src/public/hooks.ts +0 -110
  139. package/src/public/index.ts +0 -69
  140. package/src/public/steps/ContactStep.tsx +0 -184
  141. package/src/public/steps/DateTimeStep.tsx +0 -138
  142. package/src/public/steps/OverviewStep.tsx +0 -81
  143. package/src/public/steps/PaymentStep.tsx +0 -180
  144. package/src/public/types.ts +0 -250
  145. package/src/registries.ts +0 -35
  146. package/src/store.ts +0 -311
  147. package/src/types.ts +0 -273
  148. package/src/views/CalendarView.tsx +0 -1112
  149. package/src/views/ConfirmationsView.tsx +0 -155
  150. /package/dist/{financial-bridge.d.ts → bridges/financial-bridge.d.ts} +0 -0
  151. /package/dist/{registries.d.ts → data/registries.d.ts} +0 -0
  152. /package/dist/public/{BookingWidget.d.ts → views/BookingWidget.d.ts} +0 -0
  153. /package/dist/{calendar-styles.d.ts → styles/calendar-styles.d.ts} +0 -0
@@ -1,220 +0,0 @@
1
- // agenda.* — merged plugin-own + saas-core central i18n
2
- export const ptBR: Record<string, string> = {
3
- "agenda.appointment.addDescription": "Adicionar descrição...",
4
- "agenda.appointment.addService": "Adicionar serviço",
5
- "agenda.appointment.cancel": "Cancelar",
6
- "agenda.appointment.clientTab": "Cliente",
7
- "agenda.appointment.noClientData": "Dados do cliente não disponíveis",
8
- "agenda.appointment.client": "Cliente",
9
- "agenda.appointment.conflict": "Conflito de horário — este profissional já está reservado neste horário.",
10
- "agenda.appointment.create": "Criar",
11
- "agenda.appointment.createClientFailed": "Falha ao criar cliente",
12
- "agenda.appointment.creating": "Criando...",
13
- "agenda.appointment.delete": "Excluir",
14
- "agenda.appointment.deleteConfirm": "Excluir este agendamento?",
15
- "agenda.appointment.confirmDelete": "Excluir",
16
- "agenda.appointment.deleting": "Excluindo…",
17
- "agenda.appointment.edit": "Editar Agendamento",
18
- "agenda.appointment.emailOptional": "E-mail (opcional)",
19
- "agenda.appointment.location": "Local",
20
- "agenda.appointment.name": "Nome",
21
- "agenda.appointment.new": "Novo Agendamento",
22
- "agenda.appointment.newClient": "Novo cliente",
23
- "agenda.appointment.notes": "Observações",
24
- "agenda.appointment.phone": "Telefone",
25
- "agenda.appointment.professional": "Profissional",
26
- "agenda.appointment.save": "Salvar",
27
- "agenda.appointment.saving": "Salvando...",
28
- "agenda.appointment.searchClient": "Buscar cliente...",
29
- "agenda.appointment.searchService": "Buscar serviço para adicionar...",
30
- "agenda.appointment.selectLocation": "Selecionar local...",
31
- "agenda.appointment.selectProfessional": "Selecionar profissional...",
32
- "agenda.appointment.selectClientHint": "Selecione o cliente na lista ou use Novo cliente para habilitar o salvamento.",
33
- "agenda.appointment.services": "Serviços",
34
- "agenda.appointment.status": "Status",
35
- "agenda.appointment.to": "até",
36
- "agenda.appointment.total": "Total",
37
- "agenda.appointment.update": "Atualizar",
38
- "agenda.payment.title": "Recebimento",
39
- "agenda.payment.generateInvoice": "Gerar recebimento",
40
- "agenda.payment.generating": "Gerando…",
41
- "agenda.payment.pending": "Pendente",
42
- "agenda.payment.partial": "Parcial",
43
- "agenda.payment.paid": "Pago",
44
- "agenda.payment.collect": "Receber",
45
- "agenda.payment.overdue": "Vencido",
46
- "agenda.payment.cancelled": "Cancelado",
47
- "agenda.payment.none": "Recebimento",
48
- "agenda.payment.pay": "Pagar",
49
- "agenda.payment.paying": "Pagando…",
50
- "agenda.payment.remaining": "Restante",
51
- "agenda.payment.installment": "Parcela",
52
- "agenda.payment.total": "Total",
53
- "agenda.payment.noInvoice": "Sem recebimento gerado",
54
- "agenda.payment.selectAccount": "Selecionar conta",
55
- "agenda.payment.registerPayment": "Registrar recebimento",
56
- "agenda.payment.registerPaymentHint": "Registrar pagamento deste agendamento",
57
- "agenda.payment.pendingHint": "Fatura gerada, aguardando pagamento",
58
- "agenda.payment.partialHint": "Parcialmente pago, toque para completar",
59
- "agenda.payment.paidHint": "Pagamento realizado",
60
- "agenda.payment.overdueHint": "Pagamento vencido",
61
- "agenda.payment.backToAppointment": "Voltar ao agendamento",
62
- "agenda.payment.checkout": "Confirmar",
63
- "agenda.payment.checkingOut": "Processando…",
64
- "agenda.payment.serviceSummary": "Resumo do atendimento",
65
- "agenda.payment.paymentMethod": "Forma de pagamento",
66
- "agenda.payment.noMethods": "Nenhuma forma de pagamento configurada.",
67
- "agenda.payment.configureMethods": "Configurar formas de pagamento",
68
- "agenda.payment.checkoutComplete": "Pagamento confirmado",
69
- "agenda.payment.card": "Cartão",
70
- "agenda.payment.method": "Método",
71
- "agenda.payment.select": "Selecionar…",
72
- "agenda.payment.cardBrand": "Bandeira",
73
- "agenda.payment.selectBrand": "Selecionar bandeira…",
74
- "agenda.payment.cardInstallments": "Parcelas",
75
- "agenda.payment.bankAccount": "Conta",
76
- "agenda.payment.viewInvoice": "Ver detalhes da fatura",
77
- "agenda.calendar.agendas": "Agendas",
78
- "agenda.calendar.create": "Criar",
79
- "agenda.calendar.next": "Próximo",
80
- "agenda.calendar.previous": "Anterior",
81
- "agenda.calendar.status": "Status",
82
- "agenda.calendar.today": "Hoje",
83
- "agenda.calendar.viewAgenda": "Agenda",
84
- "agenda.calendar.viewDay": "Dia",
85
- "agenda.calendar.viewList": "Lista",
86
- "agenda.calendar.viewMonth": "Mês",
87
- "agenda.calendar.viewWeek": "Semana",
88
- "agenda.list.today": "Hoje",
89
- "agenda.list.tomorrow": "Amanhã",
90
- "agenda.list.empty": "Nenhum item próximo",
91
- "agenda.confirmations.confirmed": "Confirmado:",
92
- "agenda.confirmations.empty": "Nenhuma confirmação pendente",
93
- "agenda.confirmations.emptySubtitle": "Todos os agendamentos futuros foram confirmados",
94
- "agenda.confirmations.loading": "Carregando...",
95
- "agenda.confirmations.subtitle": "Agendamentos futuros pendentes de confirmação",
96
- "agenda.confirmations.unknownClient": "Cliente desconhecido",
97
- "agenda.confirmations.sendWhatsApp": "Enviar WhatsApp",
98
- "agenda.confirmations.call": "Ligar",
99
- "agenda.confirmations.markConfirmed": "Marcar Confirmado",
100
- "agenda.confirmations.markCancelled": "Marcar Cancelado",
101
- "agenda.cancellation.loadingReasons": "Carregando motivos...",
102
- "agenda.cancellation.notesOptional": "Observações do cancelamento (opcional)",
103
- "agenda.cancellation.notesRequired": "Observações do cancelamento obrigatórias",
104
- "agenda.cancellation.selectReason": "Selecionar motivo do cancelamento",
105
- "agenda.contextMenu.changeStatus": "Alterar status",
106
- "agenda.contextMenu.delete": "Excluir",
107
- "agenda.contextMenu.edit": "Editar",
108
- "agenda.contextMenu.notAvailable": "(indisponível)",
109
- "agenda.index.settingsSubtitle": "Horários de funcionamento, preferências de agendamento e confirmações",
110
- "agenda.index.settingsTitle": "Configurações da Agenda",
111
- "settings.plugin.agenda-working-hours": "Horário de Funcionamento",
112
- "agenda.popover.unknown": "Desconhecido",
113
- "agenda.settings.autoConfirm": "Confirmar Automaticamente",
114
- "agenda.settings.autoConfirmDesc": "Confirmar automaticamente novos agendamentos",
115
- "agenda.settings.autoCreateOrder": "Criar Pedido Automaticamente",
116
- "agenda.settings.autoCreateOrderDesc": "Criar automaticamente um pedido financeiro para novos agendamentos",
117
- "agenda.settings.blockOutside": "Bloquear Fora do Horário",
118
- "agenda.settings.blockOutsideDesc": "Impedir agendamentos fora do horário de funcionamento",
119
- "agenda.settings.calendarDisplay": "Exibição do Calendário",
120
- "agenda.settings.calendarDisplayDesc": "Configurações visuais do calendário",
121
- "agenda.settings.cancelAlerts": "Alertas de Cancelamento",
122
- "agenda.settings.cancelAlertsDesc": "Notificar quando um agendamento é cancelado",
123
- "agenda.settings.compact": "Modo Compacto",
124
- "agenda.settings.compactDesc": "Usar blocos de eventos menores",
125
- "agenda.settings.confirmations": "Confirmações",
126
- "agenda.settings.confirmationsDesc": "Configurações de confirmação de agendamento",
127
- "agenda.settings.conflictDetection": "Detecção de Conflitos",
128
- "agenda.settings.conflictDetectionDesc": "Alertar quando agendamentos se sobrepõem",
129
- "agenda.settings.dragDrop": "Arrastar e Soltar",
130
- "agenda.settings.dragDropDesc": "Permitir reagendamento arrastando eventos",
131
- "agenda.settings.enableConfirmations": "Habilitar Confirmações",
132
- "agenda.settings.enableConfirmationsDesc": "Exigir confirmação para novos agendamentos",
133
- "agenda.settings.enableLocation": "Habilitar Seleção de Local",
134
- "agenda.settings.enableLocationDesc": "Mostrar seletor de local no modal de agendamento",
135
- "agenda.settings.enableWorkingHours": "Habilitar Horário de Funcionamento",
136
- "agenda.settings.enableWorkingHoursDesc": "Mostrar sobreposição de horário de funcionamento no calendário",
137
- "agenda.settings.locations": "Locais",
138
- "agenda.settings.locationsDesc": "Configurações de múltiplos locais",
139
- "agenda.settings.newBookingNotif": "Alertas de Novo Agendamento",
140
- "agenda.settings.newBookingNotifDesc": "Notificar quando um novo agendamento é criado",
141
- "agenda.settings.noShowTracking": "Rastreamento de Ausências",
142
- "agenda.settings.noShowTrackingDesc": "Rastrear e alertar sobre ausências",
143
- "agenda.settings.notifications": "Notificações",
144
- "agenda.settings.notificationsDesc": "Preferências de notificação",
145
- "agenda.settings.reminder": "Lembretes",
146
- "agenda.settings.reminderDesc": "Enviar lembretes antes dos compromissos",
147
- "agenda.settings.scheduling": "Agendamento",
148
- "agenda.settings.schedulingDesc": "Configurar comportamento do agendamento",
149
- "agenda.settings.showCancelled": "Mostrar Cancelados",
150
- "agenda.settings.showCancelledDesc": "Exibir agendamentos cancelados no calendário",
151
- "agenda.settings.showNoShow": "Mostrar Ausências",
152
- "agenda.settings.showNoShowDesc": "Exibir ausências no calendário",
153
- "agenda.settings.workingHours": "Horário de Funcionamento",
154
- "agenda.settings.workingHoursDesc": "Horários e disponibilidade",
155
- "agenda.sidebar.agendas": "Agendas",
156
- "agenda.sidebar.allLocations": "Todos os locais",
157
- "agenda.sidebar.create": "Criar",
158
- "agenda.sidebar.location": "Local",
159
- "agenda.sidebar.status": "Status",
160
- "agenda.toolbar.next": "Próximo",
161
- "agenda.toolbar.previous": "Anterior",
162
- "agenda.toolbar.today": "Hoje",
163
- "agenda.workingHours.addBreak": "Adicionar intervalo",
164
- "agenda.workingHours.copyToAll": "Copiar para todos",
165
- "agenda.workingHours.dayOff": "Folga",
166
- "agenda.workingHours.removePeriod": "Remover período",
167
- "agenda.workingHours.professional": "Profissional",
168
- "agenda.workingHours.save": "Salvar",
169
- "agenda.workingHours.saveFailed": "Falha ao salvar",
170
- "agenda.workingHours.saved": "Horários de trabalho salvos",
171
- "agenda.workingHours.saving": "Salvando...",
172
- "agenda.workingHours.subtitle": "Gerenciar horários de trabalho por profissional",
173
- "agenda.workingHours.to": "até",
174
- "agenda.title": "Agenda",
175
- "agenda.calendar.title": "Calendário",
176
- "agenda.calendar.noScheduleConfirm": "Este horário não possui agenda aberta para este profissional. Deseja agendar mesmo assim?",
177
- "agenda.calendar.newAppointment": "Novo Agendamento",
178
- "agenda.booking.create": "Novo Agendamento",
179
- "agenda.booking.edit": "Editar Agendamento",
180
- "agenda.booking.delete": "Excluir Agendamento",
181
- "agenda.booking.deleteConfirm": "Tem certeza que deseja excluir este agendamento?",
182
- "agenda.booking.client": "Cliente",
183
- "agenda.booking.professional": "Profissional",
184
- "agenda.booking.services": "Serviços",
185
- "agenda.booking.date": "Data",
186
- "agenda.booking.time": "Horário",
187
- "agenda.booking.duration": "Duração",
188
- "agenda.booking.notes": "Observações",
189
- "agenda.booking.status": "Status",
190
- "agenda.booking.save": "Salvar Agendamento",
191
- "agenda.booking.saving": "Salvando...",
192
- "agenda.confirmations.title": "Confirmações",
193
- "agenda.confirmations.pending": "Confirmações Pendentes",
194
- "agenda.confirmations.emptyDescription": "Todos os agendamentos futuros foram confirmados",
195
- "agenda.confirmations.confirm": "Confirmar",
196
- "agenda.confirmations.cancel": "Cancelar",
197
- "agenda.workingHours.title": "Horário de Trabalho",
198
- "agenda.workingHours.addPeriod": "Adicionar período",
199
- "agenda.workingHours.loading": "Carregando...",
200
- "agenda.appointment.addClient": "Adicionar cliente",
201
- "agenda.appointment.addServiceFirst": "Adicionar serviço",
202
- "agenda.appointment.addNotes": "Adicionar observações",
203
- "agenda.appointment.duration": "Duração (minutos)",
204
- "agenda.appointment.price": "Preço",
205
- "agenda.appointment.title": "Título",
206
- "agenda.appointment.addTitle": "Adicionar um título",
207
- "agenda.appointment.untitled": "(sem título)",
208
- "agenda.bookingType.appointment": "Agendamento",
209
- "agenda.bookingType.event": "Evento",
210
- "agenda.bookingType.task": "Tarefa",
211
- "agenda.bookingType.out_of_office": "Ausência",
212
- "agenda.bookingType.block": "Bloqueio",
213
- "agenda.settings.title": "Configurações da Agenda",
214
- "agenda.settings.slotDuration": "Duração do Intervalo",
215
- "agenda.settings.bookingKind": "Tipo de Agendamento",
216
- "agenda.settings.dayStartTime": "Horário de Início",
217
- "agenda.settings.dayStartTimeDesc": "Primeiro horário exibido no calendário",
218
- "agenda.settings.dayEndTime": "Horário de Término",
219
- "agenda.settings.dayEndTimeDesc": "Último horário exibido no calendário",
220
- }
@@ -1,21 +0,0 @@
1
- -- Salon-only unblock: the pre-pools beauty deployment built rep_* report
2
- -- views on top of its (wider) v_bookings view, so 001's cleanup DROP cannot
3
- -- run there. Renaming keeps the dependents attached (they track the OID) and
4
- -- frees the name for 001; 002_v_bookings_compat re-creates v_bookings as a
5
- -- thin alias for live pre-M5 clients. No-op on fresh pools and on pools whose
6
- -- v_bookings has no dependents (001's DROP handles those).
7
-
8
- DO $rename$
9
- BEGIN
10
- IF to_regclass('public.v_bookings') IS NOT NULL
11
- AND to_regclass('public.v_bookings_legacy_beauty') IS NULL
12
- AND EXISTS (
13
- SELECT 1 FROM pg_depend d
14
- JOIN pg_rewrite rw ON rw.oid = d.objid
15
- JOIN pg_class dep ON dep.oid = rw.ev_class
16
- JOIN pg_class ref ON ref.oid = d.refobjid
17
- WHERE ref.relname = 'v_bookings' AND dep.relname <> 'v_bookings'
18
- ) THEN
19
- EXECUTE 'ALTER VIEW public.v_bookings RENAME TO v_bookings_legacy_beauty';
20
- END IF;
21
- END $rename$;
@@ -1,43 +0,0 @@
1
- -- 001 needs the customer-phone upsert key UNIQUE(tenant_id, phone). Converted
2
- -- pools can carry duplicates (found live on salon: 9 dogfood test records all
3
- -- sharing the founder's phone). Non-destructive dedupe: per (tenant_id, phone)
4
- -- customer group keep the row with most appointments (tie: oldest), NULL the
5
- -- phone on the rest and stash the original in metadata->'dedupedPhone'.
6
- -- Idempotent: reruns find no groups > 1.
7
-
8
- DO $dedupe$
9
- DECLARE
10
- r record;
11
- BEGIN
12
- IF to_regclass('public.people') IS NULL THEN
13
- RETURN;
14
- END IF;
15
-
16
- FOR r IN
17
- SELECT p.id, p.phone
18
- FROM public.people p
19
- JOIN LATERAL (
20
- SELECT count(*) AS appts
21
- FROM public.appointments a WHERE a.party_id = p.id
22
- ) ac ON true
23
- WHERE p.kind = 'customer' AND p.phone IS NOT NULL
24
- AND p.id NOT IN (
25
- SELECT DISTINCT ON (p2.tenant_id, p2.phone) p2.id
26
- FROM public.people p2
27
- WHERE p2.kind = 'customer' AND p2.phone IS NOT NULL
28
- ORDER BY p2.tenant_id, p2.phone,
29
- (SELECT count(*) FROM public.appointments a2 WHERE a2.party_id = p2.id) DESC,
30
- p2.created_at ASC
31
- )
32
- AND (p.tenant_id, p.phone) IN (
33
- SELECT tenant_id, phone FROM public.people
34
- WHERE kind = 'customer' AND phone IS NOT NULL
35
- GROUP BY tenant_id, phone HAVING count(*) > 1
36
- )
37
- LOOP
38
- UPDATE public.people
39
- SET metadata = COALESCE(metadata, '{}'::jsonb) || jsonb_build_object('dedupedPhone', r.phone),
40
- phone = NULL
41
- WHERE id = r.id;
42
- END LOOP;
43
- END $dedupe$;
@@ -1,296 +0,0 @@
1
- -- ============================================================================
2
- -- plugin-agenda 001: booking read model + public (anon) booking surface
3
- --
4
- -- Ships the objects the agenda providers reference but the spine (packages/db
5
- -- migrations 001–010) does not define:
6
- -- §0 converted-pool cleanup — drop the pre-rename v_bookings view / indexes
7
- -- §1 order_items columns the admin provider writes (duration/assignee)
8
- -- §2 public.v_appointments — admin read model (authenticated, RLS applies)
9
- -- §3 public.v_public_services — anon-readable service catalog
10
- -- §4 public.get_available_slots — anon RPC (mirrors the mock algorithm)
11
- -- §5 public.create_public_booking — anon RPC (validated, race-safe write)
12
- --
13
- -- Core v1 lives directly in PUBLIC (no separate core schema): people (was
14
- -- persons), appointments (was bookings). This migration is written against public
15
- -- names and is idempotent + safe on both fresh and converted pools.
16
- --
17
- -- Anon-access design: public booking pages run with the publishable key and no
18
- -- session, so the canonical tenant RLS yields nothing. Anon access is granted
19
- -- ONLY via the column-whitelisted view (§3) and SECURITY DEFINER RPCs (§4, §5)
20
- -- with tight input validation and hardcoded statuses.
21
- -- ============================================================================
22
-
23
- -- §0 — converted-pool cleanup. The pre-industry-pool shape shipped a v_bookings
24
- -- view and persons_/bookings_-named indexes; drop them so this file re-emits the
25
- -- canonical v_appointments view + people_/appointments_ indexes without leaving
26
- -- duplicates. All guarded with IF EXISTS → no-ops on a fresh pool.
27
- DROP VIEW IF EXISTS public.v_bookings;
28
- DROP INDEX IF EXISTS public.persons_tenant_customer_phone;
29
- DROP INDEX IF EXISTS public.bookings_assignee_time;
30
-
31
- -- §1 — columns the agenda provider writes but 004_archetypes lacks
32
- ALTER TABLE public.order_items
33
- ADD COLUMN IF NOT EXISTS duration_minutes integer,
34
- ADD COLUMN IF NOT EXISTS assignee_id uuid REFERENCES public.people(id) ON DELETE SET NULL;
35
-
36
- -- Upsert key for public customers (per tenant, by normalized phone)
37
- CREATE UNIQUE INDEX IF NOT EXISTS people_tenant_customer_phone
38
- ON public.people(tenant_id, phone)
39
- WHERE kind = 'customer' AND phone IS NOT NULL;
40
-
41
- -- Slot-conflict scan
42
- CREATE INDEX IF NOT EXISTS appointments_assignee_time
43
- ON public.appointments(assignee_id, starts_at, ends_at)
44
- WHERE status NOT IN ('cancelled', 'no_show');
45
-
46
- -- §2 — admin read model (security_invoker → underlying RLS applies; anon sees 0 rows)
47
- -- DROP first: 013 widens this view and CREATE OR REPLACE cannot narrow a live
48
- -- view on replay ("cannot drop columns from view"). 013 replays right after.
49
- DROP VIEW IF EXISTS public.v_appointments;
50
- CREATE VIEW public.v_appointments
51
- WITH (security_invoker = true) AS
52
- SELECT
53
- b.id, b.tenant_id, b.kind, b.starts_at, b.ends_at, b.status, b.notes,
54
- b.order_id, b.location_id, b.metadata, b.created_at, b.updated_at,
55
- b.party_id AS client_id,
56
- COALESCE(pc.name, b.metadata->>'title') AS client_name,
57
- pc.phone AS client_phone,
58
- pc.email AS client_email,
59
- pc.avatar_url AS client_avatar_url,
60
- b.assignee_id AS professional_id,
61
- ps.name AS professional_name,
62
- ps.avatar_url AS professional_avatar_url,
63
- l.name AS location_name,
64
- o.total AS order_total,
65
- o.status AS order_status,
66
- (EXTRACT(EPOCH FROM (COALESCE(b.ends_at, b.starts_at) - b.starts_at))::int / 60)
67
- AS total_duration_minutes
68
- FROM public.appointments b
69
- LEFT JOIN public.people pc ON pc.id = b.party_id
70
- LEFT JOIN public.people ps ON ps.id = b.assignee_id
71
- LEFT JOIN public.locations l ON l.id = b.location_id
72
- LEFT JOIN public.orders o ON o.id = b.order_id;
73
-
74
- GRANT SELECT ON public.v_appointments TO authenticated, service_role;
75
- REVOKE SELECT ON public.v_appointments FROM anon;
76
-
77
- -- §3 — anon-readable public catalog (owner-rights view = deliberate, whitelisted)
78
- -- DROP first: 010 widens this view (curation columns) — same replay rule as §2.
79
- DROP VIEW IF EXISTS public.v_public_services;
80
- CREATE VIEW public.v_public_services AS
81
- SELECT
82
- s.id, s.tenant_id, s.name, s.description, s.price, s.currency,
83
- COALESCE(s.duration_minutes, 30) AS duration_minutes,
84
- s.image_url,
85
- COALESCE((s.metadata->>'sort_order')::int, 0) AS sort_order
86
- FROM public.services s
87
- WHERE s.is_active AND s.status = 'active';
88
-
89
- GRANT SELECT ON public.v_public_services TO anon, authenticated, service_role;
90
-
91
- -- §4 — availability RPC (mirrors plugin-agenda/src/data/mock.ts getAvailableSlots)
92
- CREATE OR REPLACE FUNCTION public.get_available_slots(
93
- p_tenant_id uuid,
94
- p_date date,
95
- p_duration_minutes integer,
96
- p_assignee_id uuid DEFAULT NULL,
97
- p_slot_interval integer DEFAULT 30
98
- ) RETURNS TABLE (slot_start timestamptz, slot_end timestamptz)
99
- LANGUAGE plpgsql STABLE SECURITY DEFINER
100
- SET search_path = public
101
- AS $$
102
- DECLARE
103
- v_tz text;
104
- BEGIN
105
- -- tight validation: this function is anon-callable
106
- IF p_duration_minutes IS NULL OR p_duration_minutes NOT BETWEEN 5 AND 480 THEN
107
- RAISE EXCEPTION 'invalid duration';
108
- END IF;
109
- IF p_slot_interval IS NULL OR p_slot_interval NOT BETWEEN 5 AND 240 THEN
110
- RAISE EXCEPTION 'invalid interval';
111
- END IF;
112
- IF p_date IS NULL OR p_date < current_date OR p_date > current_date + 90 THEN
113
- RETURN;
114
- END IF;
115
-
116
- SELECT COALESCE(t.settings->>'timezone', 'America/Sao_Paulo') INTO v_tz
117
- FROM tenants t WHERE t.id = p_tenant_id;
118
- IF v_tz IS NULL THEN RETURN; END IF; -- unknown tenant → empty, not an error
119
-
120
- RETURN QUERY
121
- WITH day_schedules AS (
122
- SELECT sc.assignee_id, sc.starts_at AS win_start, sc.ends_at AS win_end
123
- FROM schedules sc
124
- WHERE sc.tenant_id = p_tenant_id
125
- AND sc.kind = 'working_hours'
126
- AND sc.is_active
127
- AND (p_assignee_id IS NULL OR sc.assignee_id = p_assignee_id)
128
- AND (
129
- sc.specific_date = p_date
130
- OR (sc.specific_date IS NULL AND sc.day_of_week = EXTRACT(dow FROM p_date)::smallint)
131
- )
132
- ),
133
- candidate AS (
134
- SELECT
135
- ds.assignee_id,
136
- gs AS s_start,
137
- gs + make_interval(mins => p_duration_minutes) AS s_end
138
- FROM day_schedules ds
139
- CROSS JOIN LATERAL generate_series(
140
- (p_date + ds.win_start) AT TIME ZONE v_tz,
141
- ((p_date + ds.win_end) AT TIME ZONE v_tz) - make_interval(mins => p_duration_minutes),
142
- make_interval(mins => p_slot_interval)
143
- ) AS gs
144
- )
145
- SELECT DISTINCT c.s_start, c.s_end
146
- FROM candidate c
147
- WHERE NOT EXISTS (
148
- SELECT 1 FROM appointments b
149
- WHERE b.tenant_id = p_tenant_id
150
- AND b.assignee_id = c.assignee_id
151
- AND b.status NOT IN ('cancelled', 'no_show')
152
- AND b.starts_at < c.s_end
153
- AND COALESCE(b.ends_at, b.starts_at) > c.s_start
154
- )
155
- ORDER BY 1;
156
- END;
157
- $$;
158
-
159
- REVOKE ALL ON FUNCTION public.get_available_slots(uuid, date, integer, uuid, integer) FROM public;
160
- GRANT EXECUTE ON FUNCTION public.get_available_slots(uuid, date, integer, uuid, integer)
161
- TO anon, authenticated, service_role;
162
-
163
- -- §5 — public booking write RPC (validated, race-safe, hardcoded statuses)
164
- CREATE OR REPLACE FUNCTION public.create_public_booking(
165
- p_tenant_id uuid,
166
- p_service_id uuid,
167
- p_starts_at timestamptz,
168
- p_name text,
169
- p_phone text,
170
- p_email text DEFAULT NULL,
171
- p_notes text DEFAULT NULL
172
- ) RETURNS TABLE (booking_id uuid, starts_at timestamptz, ends_at timestamptz)
173
- LANGUAGE plpgsql SECURITY DEFINER
174
- SET search_path = public
175
- AS $$
176
- DECLARE
177
- v_service record;
178
- v_person_id uuid;
179
- v_assignee uuid;
180
- v_ends timestamptz;
181
- v_order uuid;
182
- v_booking uuid;
183
- v_phone text;
184
- v_tz text;
185
- v_notes text;
186
- BEGIN
187
- -- validation: anon-callable, everything tight
188
- IF p_name IS NULL OR length(trim(p_name)) NOT BETWEEN 2 AND 120 THEN
189
- RAISE EXCEPTION 'invalid name';
190
- END IF;
191
- v_phone := regexp_replace(COALESCE(p_phone, ''), '\D', '', 'g');
192
- IF length(v_phone) NOT BETWEEN 8 AND 15 THEN
193
- RAISE EXCEPTION 'invalid phone';
194
- END IF;
195
- IF p_email IS NOT NULL AND (length(p_email) > 254 OR p_email !~ '^[^@\s]+@[^@\s]+\.[^@\s]+$') THEN
196
- RAISE EXCEPTION 'invalid email';
197
- END IF;
198
- v_notes := left(p_notes, 500);
199
- IF p_starts_at IS NULL OR p_starts_at <= now() OR p_starts_at > now() + interval '90 days' THEN
200
- RAISE EXCEPTION 'invalid start';
201
- END IF;
202
-
203
- SELECT s.id, s.name, s.price, COALESCE(s.duration_minutes, 30) AS duration_minutes
204
- INTO v_service
205
- FROM services s
206
- WHERE s.id = p_service_id AND s.tenant_id = p_tenant_id
207
- AND s.is_active AND s.status = 'active';
208
- IF NOT FOUND THEN RAISE EXCEPTION 'unknown service'; END IF;
209
-
210
- v_ends := p_starts_at + make_interval(mins => v_service.duration_minutes);
211
- SELECT COALESCE(t.settings->>'timezone', 'America/Sao_Paulo') INTO v_tz
212
- FROM tenants t WHERE t.id = p_tenant_id;
213
- IF v_tz IS NULL THEN RAISE EXCEPTION 'unknown tenant'; END IF;
214
-
215
- -- pick the assignee whose working hours cover the slot
216
- SELECT sc.assignee_id INTO v_assignee
217
- FROM schedules sc
218
- WHERE sc.tenant_id = p_tenant_id
219
- AND sc.kind = 'working_hours'
220
- AND sc.is_active
221
- AND (
222
- sc.specific_date = (p_starts_at AT TIME ZONE v_tz)::date
223
- OR (sc.specific_date IS NULL
224
- AND sc.day_of_week = EXTRACT(dow FROM p_starts_at AT TIME ZONE v_tz)::smallint)
225
- )
226
- AND (p_starts_at AT TIME ZONE v_tz)::time >= sc.starts_at
227
- AND (v_ends AT TIME ZONE v_tz)::time <= sc.ends_at
228
- ORDER BY sc.assignee_id
229
- LIMIT 1;
230
- IF v_assignee IS NULL THEN RAISE EXCEPTION 'slot outside working hours'; END IF;
231
-
232
- -- serialize per (tenant, assignee) to kill the double-book race
233
- PERFORM pg_advisory_xact_lock(hashtextextended(p_tenant_id::text || v_assignee::text, 0));
234
-
235
- IF EXISTS (
236
- SELECT 1 FROM appointments b
237
- WHERE b.tenant_id = p_tenant_id
238
- AND b.assignee_id = v_assignee
239
- AND b.status NOT IN ('cancelled', 'no_show')
240
- AND b.starts_at < v_ends
241
- AND COALESCE(b.ends_at, b.starts_at) > p_starts_at
242
- ) THEN
243
- RAISE EXCEPTION 'slot no longer available';
244
- END IF;
245
-
246
- -- anti-spam: cap open future public bookings per phone per tenant
247
- IF (
248
- SELECT count(*) FROM appointments b
249
- JOIN people p ON p.id = b.party_id
250
- WHERE b.tenant_id = p_tenant_id
251
- AND p.phone = v_phone
252
- AND b.starts_at > now()
253
- AND b.status = 'scheduled'
254
- ) >= 3 THEN
255
- RAISE EXCEPTION 'too many open bookings for this phone';
256
- END IF;
257
-
258
- -- upsert the customer by (tenant, phone) — matches people_tenant_customer_phone
259
- INSERT INTO people (tenant_id, kind, name, phone, email, metadata)
260
- VALUES (p_tenant_id, 'customer', trim(p_name), v_phone, p_email,
261
- jsonb_build_object('source', 'public_booking'))
262
- ON CONFLICT (tenant_id, phone) WHERE kind = 'customer' AND phone IS NOT NULL
263
- DO UPDATE SET
264
- name = EXCLUDED.name,
265
- email = COALESCE(EXCLUDED.email, people.email),
266
- updated_at = now()
267
- RETURNING id INTO v_person_id;
268
-
269
- -- order + booking + order_item (statuses hardcoded — caller cannot set)
270
- INSERT INTO orders (tenant_id, kind, status, party_id, assignee_id, subtotal, total, notes, metadata)
271
- VALUES (p_tenant_id, 'appointment', 'scheduled', v_person_id, v_assignee,
272
- v_service.price, v_service.price, v_notes,
273
- jsonb_build_object('source', 'public_booking',
274
- 'serviceNames', v_service.name,
275
- 'contactName', trim(p_name)))
276
- RETURNING id INTO v_order;
277
-
278
- INSERT INTO appointments (tenant_id, kind, party_id, assignee_id, order_id,
279
- starts_at, ends_at, status, notes, metadata)
280
- VALUES (p_tenant_id, 'appointment', v_person_id, v_assignee, v_order,
281
- p_starts_at, v_ends, 'scheduled', v_notes,
282
- jsonb_build_object('source', 'public_booking', 'serviceNames', v_service.name))
283
- RETURNING id INTO v_booking;
284
-
285
- INSERT INTO order_items (order_id, service_id, name, quantity, unit_price, total,
286
- sort_order, duration_minutes, assignee_id)
287
- VALUES (v_order, v_service.id, v_service.name, 1, v_service.price, v_service.price,
288
- 0, v_service.duration_minutes, v_assignee);
289
-
290
- RETURN QUERY SELECT v_booking, p_starts_at, v_ends;
291
- END;
292
- $$;
293
-
294
- REVOKE ALL ON FUNCTION public.create_public_booking(uuid, uuid, timestamptz, text, text, text, text) FROM public;
295
- GRANT EXECUTE ON FUNCTION public.create_public_booking(uuid, uuid, timestamptz, text, text, text, text)
296
- TO anon, authenticated, service_role;
@@ -1,14 +0,0 @@
1
- -- Where 000b preserved the legacy beauty view, re-create v_bookings as a
2
- -- thin alias over it so live pre-M5 clients that SELECT v_bookings by name
3
- -- keep their full legacy column set. Dropped for good once the beauty app
4
- -- ships on v_appointments (M5 follow-up).
5
-
6
- DO $compat$
7
- BEGIN
8
- IF to_regclass('public.v_bookings_legacy_beauty') IS NOT NULL
9
- AND to_regclass('public.v_bookings') IS NULL THEN
10
- EXECUTE 'CREATE VIEW public.v_bookings WITH (security_invoker = true) AS SELECT * FROM public.v_bookings_legacy_beauty';
11
- EXECUTE 'GRANT SELECT ON public.v_bookings TO authenticated, service_role';
12
- EXECUTE 'REVOKE SELECT ON public.v_bookings FROM anon';
13
- END IF;
14
- END $compat$;