@fayz-ai/plugin-agenda 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -0
- package/dist/AgendaContext.d.ts +13 -0
- package/dist/AgendaContext.d.ts.map +1 -0
- package/dist/AgendaPage.d.ts +11 -0
- package/dist/AgendaPage.d.ts.map +1 -0
- package/dist/bridges/create-financial-bridge.d.ts +19 -0
- package/dist/bridges/create-financial-bridge.d.ts.map +1 -0
- package/dist/calendar-styles.d.ts +8 -0
- package/dist/calendar-styles.d.ts.map +1 -0
- package/dist/chunk-3LJKZQK4.js +12 -0
- package/dist/chunk-3LJKZQK4.js.map +1 -0
- package/dist/chunk-SYDBDI3Q.cjs +15 -0
- package/dist/chunk-SYDBDI3Q.cjs.map +1 -0
- package/dist/components/AgendaGeneralSettings.d.ts +3 -0
- package/dist/components/AgendaGeneralSettings.d.ts.map +1 -0
- package/dist/components/AppointmentModal.d.ts +16 -0
- package/dist/components/AppointmentModal.d.ts.map +1 -0
- package/dist/components/AppointmentPopover.d.ts +14 -0
- package/dist/components/AppointmentPopover.d.ts.map +1 -0
- package/dist/components/BookingPaymentPanel.d.ts +18 -0
- package/dist/components/BookingPaymentPanel.d.ts.map +1 -0
- package/dist/components/CalendarSidebar.d.ts +3 -0
- package/dist/components/CalendarSidebar.d.ts.map +1 -0
- package/dist/components/CalendarToolbar.d.ts +13 -0
- package/dist/components/CalendarToolbar.d.ts.map +1 -0
- package/dist/components/EventContextMenu.d.ts +14 -0
- package/dist/components/EventContextMenu.d.ts.map +1 -0
- package/dist/components/FloatingPanel.d.ts +27 -0
- package/dist/components/FloatingPanel.d.ts.map +1 -0
- package/dist/components/MiniCalendar.d.ts +9 -0
- package/dist/components/MiniCalendar.d.ts.map +1 -0
- package/dist/components/PaymentStatusBadge.d.ts +13 -0
- package/dist/components/PaymentStatusBadge.d.ts.map +1 -0
- package/dist/components/SearchCombobox.d.ts +38 -0
- package/dist/components/SearchCombobox.d.ts.map +1 -0
- package/dist/components/StatusBadge.d.ts +5 -0
- package/dist/components/StatusBadge.d.ts.map +1 -0
- package/dist/config.d.ts +151 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/data/index.d.ts +4 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/mock.d.ts +3 -0
- package/dist/data/mock.d.ts.map +1 -0
- package/dist/data/supabase.d.ts +3 -0
- package/dist/data/supabase.d.ts.map +1 -0
- package/dist/data/types.d.ts +26 -0
- package/dist/data/types.d.ts.map +1 -0
- package/dist/financial-bridge.d.ts +92 -0
- package/dist/financial-bridge.d.ts.map +1 -0
- package/dist/hooks/useAgendaSettings.d.ts +7 -0
- package/dist/hooks/useAgendaSettings.d.ts.map +1 -0
- package/dist/index.cjs +5135 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5120 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/tenant.d.ts +3 -0
- package/dist/lib/tenant.d.ts.map +1 -0
- package/dist/locales/en.d.ts +2 -0
- package/dist/locales/en.d.ts.map +1 -0
- package/dist/locales/index.d.ts +2 -0
- package/dist/locales/index.d.ts.map +1 -0
- package/dist/locales/pt-BR.d.ts +2 -0
- package/dist/locales/pt-BR.d.ts.map +1 -0
- package/dist/registries.d.ts +3 -0
- package/dist/registries.d.ts.map +1 -0
- package/dist/store.d.ts +66 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/tenant-222EHYUC.cjs +16 -0
- package/dist/tenant-222EHYUC.cjs.map +1 -0
- package/dist/tenant-LAUWVUYB.js +3 -0
- package/dist/tenant-LAUWVUYB.js.map +1 -0
- package/dist/types.d.ts +202 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/views/CalendarView.d.ts +3 -0
- package/dist/views/CalendarView.d.ts.map +1 -0
- package/dist/views/ConfirmationsView.d.ts +3 -0
- package/dist/views/ConfirmationsView.d.ts.map +1 -0
- package/package.json +63 -0
- package/src/AgendaContext.tsx +11 -0
- package/src/AgendaPage.tsx +33 -0
- package/src/bridges/create-financial-bridge.ts +298 -0
- package/src/calendar-styles.css +245 -0
- package/src/calendar-styles.ts +285 -0
- package/src/components/AgendaGeneralSettings.tsx +64 -0
- package/src/components/AppointmentModal.tsx +1031 -0
- package/src/components/AppointmentPopover.tsx +221 -0
- package/src/components/BookingPaymentPanel.tsx +394 -0
- package/src/components/CalendarSidebar.tsx +119 -0
- package/src/components/CalendarToolbar.tsx +83 -0
- package/src/components/EventContextMenu.tsx +122 -0
- package/src/components/FloatingPanel.tsx +123 -0
- package/src/components/MiniCalendar.tsx +120 -0
- package/src/components/PaymentStatusBadge.tsx +62 -0
- package/src/components/SearchCombobox.tsx +216 -0
- package/src/components/StatusBadge.tsx +23 -0
- package/src/config.ts +269 -0
- package/src/data/index.ts +3 -0
- package/src/data/mock.ts +324 -0
- package/src/data/supabase.ts +570 -0
- package/src/data/types.ts +37 -0
- package/src/financial-bridge.ts +96 -0
- package/src/hooks/useAgendaSettings.ts +17 -0
- package/src/index.ts +180 -0
- package/src/lib/tenant.ts +3 -0
- package/src/locales/en.ts +207 -0
- package/src/locales/index.ts +7 -0
- package/src/locales/pt-BR.ts +207 -0
- package/src/registries.ts +36 -0
- package/src/store.ts +304 -0
- package/src/types.ts +265 -0
- package/src/views/CalendarView.tsx +904 -0
- package/src/views/ConfirmationsView.tsx +155 -0
|
@@ -0,0 +1,207 @@
|
|
|
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.services": "Serviços",
|
|
33
|
+
"agenda.appointment.status": "Status",
|
|
34
|
+
"agenda.appointment.to": "até",
|
|
35
|
+
"agenda.appointment.total": "Total",
|
|
36
|
+
"agenda.appointment.update": "Atualizar",
|
|
37
|
+
"agenda.payment.title": "Recebimento",
|
|
38
|
+
"agenda.payment.generateInvoice": "Gerar recebimento",
|
|
39
|
+
"agenda.payment.generating": "Gerando…",
|
|
40
|
+
"agenda.payment.pending": "Pendente",
|
|
41
|
+
"agenda.payment.partial": "Parcial",
|
|
42
|
+
"agenda.payment.paid": "Pago",
|
|
43
|
+
"agenda.payment.collect": "Receber",
|
|
44
|
+
"agenda.payment.overdue": "Vencido",
|
|
45
|
+
"agenda.payment.cancelled": "Cancelado",
|
|
46
|
+
"agenda.payment.none": "Recebimento",
|
|
47
|
+
"agenda.payment.pay": "Pagar",
|
|
48
|
+
"agenda.payment.paying": "Pagando…",
|
|
49
|
+
"agenda.payment.remaining": "Restante",
|
|
50
|
+
"agenda.payment.installment": "Parcela",
|
|
51
|
+
"agenda.payment.total": "Total",
|
|
52
|
+
"agenda.payment.noInvoice": "Sem recebimento gerado",
|
|
53
|
+
"agenda.payment.selectAccount": "Selecionar conta",
|
|
54
|
+
"agenda.payment.registerPayment": "Registrar recebimento",
|
|
55
|
+
"agenda.payment.registerPaymentHint": "Registrar pagamento deste agendamento",
|
|
56
|
+
"agenda.payment.pendingHint": "Fatura gerada, aguardando pagamento",
|
|
57
|
+
"agenda.payment.partialHint": "Parcialmente pago, toque para completar",
|
|
58
|
+
"agenda.payment.paidHint": "Pagamento realizado",
|
|
59
|
+
"agenda.payment.overdueHint": "Pagamento vencido",
|
|
60
|
+
"agenda.payment.backToAppointment": "Voltar ao agendamento",
|
|
61
|
+
"agenda.payment.checkout": "Confirmar",
|
|
62
|
+
"agenda.payment.checkingOut": "Processando…",
|
|
63
|
+
"agenda.payment.serviceSummary": "Resumo do atendimento",
|
|
64
|
+
"agenda.payment.paymentMethod": "Forma de pagamento",
|
|
65
|
+
"agenda.payment.noMethods": "Nenhuma forma de pagamento configurada.",
|
|
66
|
+
"agenda.payment.configureMethods": "Configurar formas de pagamento",
|
|
67
|
+
"agenda.payment.checkoutComplete": "Pagamento confirmado",
|
|
68
|
+
"agenda.payment.card": "Cartão",
|
|
69
|
+
"agenda.payment.method": "Método",
|
|
70
|
+
"agenda.payment.select": "Selecionar…",
|
|
71
|
+
"agenda.payment.cardBrand": "Bandeira",
|
|
72
|
+
"agenda.payment.selectBrand": "Selecionar bandeira…",
|
|
73
|
+
"agenda.payment.cardInstallments": "Parcelas",
|
|
74
|
+
"agenda.payment.bankAccount": "Conta",
|
|
75
|
+
"agenda.payment.viewInvoice": "Ver detalhes da fatura",
|
|
76
|
+
"agenda.calendar.agendas": "Agendas",
|
|
77
|
+
"agenda.calendar.create": "Criar",
|
|
78
|
+
"agenda.calendar.next": "Próximo",
|
|
79
|
+
"agenda.calendar.previous": "Anterior",
|
|
80
|
+
"agenda.calendar.status": "Status",
|
|
81
|
+
"agenda.calendar.today": "Hoje",
|
|
82
|
+
"agenda.calendar.viewAgenda": "Agenda",
|
|
83
|
+
"agenda.calendar.viewDay": "Dia",
|
|
84
|
+
"agenda.calendar.viewMonth": "Mês",
|
|
85
|
+
"agenda.calendar.viewWeek": "Semana",
|
|
86
|
+
"agenda.confirmations.confirmed": "Confirmado:",
|
|
87
|
+
"agenda.confirmations.empty": "Nenhuma confirmação pendente",
|
|
88
|
+
"agenda.confirmations.emptySubtitle": "Todos os agendamentos futuros foram confirmados",
|
|
89
|
+
"agenda.confirmations.loading": "Carregando...",
|
|
90
|
+
"agenda.confirmations.subtitle": "Agendamentos futuros pendentes de confirmação",
|
|
91
|
+
"agenda.confirmations.unknownClient": "Cliente desconhecido",
|
|
92
|
+
"agenda.confirmations.sendWhatsApp": "Enviar WhatsApp",
|
|
93
|
+
"agenda.confirmations.call": "Ligar",
|
|
94
|
+
"agenda.confirmations.markConfirmed": "Marcar Confirmado",
|
|
95
|
+
"agenda.confirmations.markCancelled": "Marcar Cancelado",
|
|
96
|
+
"agenda.contextMenu.changeStatus": "Alterar status",
|
|
97
|
+
"agenda.contextMenu.delete": "Excluir",
|
|
98
|
+
"agenda.contextMenu.edit": "Editar",
|
|
99
|
+
"agenda.contextMenu.notAvailable": "(indisponível)",
|
|
100
|
+
"agenda.index.settingsSubtitle": "Horários de funcionamento, preferências de agendamento e confirmações",
|
|
101
|
+
"agenda.index.settingsTitle": "Configurações da Agenda",
|
|
102
|
+
"settings.plugin.agenda-working-hours": "Horário de Funcionamento",
|
|
103
|
+
"agenda.popover.unknown": "Desconhecido",
|
|
104
|
+
"agenda.settings.autoConfirm": "Confirmar Automaticamente",
|
|
105
|
+
"agenda.settings.autoConfirmDesc": "Confirmar automaticamente novos agendamentos",
|
|
106
|
+
"agenda.settings.autoCreateOrder": "Criar Pedido Automaticamente",
|
|
107
|
+
"agenda.settings.autoCreateOrderDesc": "Criar automaticamente um pedido financeiro para novos agendamentos",
|
|
108
|
+
"agenda.settings.blockOutside": "Bloquear Fora do Horário",
|
|
109
|
+
"agenda.settings.blockOutsideDesc": "Impedir agendamentos fora do horário de funcionamento",
|
|
110
|
+
"agenda.settings.calendarDisplay": "Exibição do Calendário",
|
|
111
|
+
"agenda.settings.calendarDisplayDesc": "Configurações visuais do calendário",
|
|
112
|
+
"agenda.settings.cancelAlerts": "Alertas de Cancelamento",
|
|
113
|
+
"agenda.settings.cancelAlertsDesc": "Notificar quando um agendamento é cancelado",
|
|
114
|
+
"agenda.settings.compact": "Modo Compacto",
|
|
115
|
+
"agenda.settings.compactDesc": "Usar blocos de eventos menores",
|
|
116
|
+
"agenda.settings.confirmations": "Confirmações",
|
|
117
|
+
"agenda.settings.confirmationsDesc": "Configurações de confirmação de agendamento",
|
|
118
|
+
"agenda.settings.conflictDetection": "Detecção de Conflitos",
|
|
119
|
+
"agenda.settings.conflictDetectionDesc": "Alertar quando agendamentos se sobrepõem",
|
|
120
|
+
"agenda.settings.dragDrop": "Arrastar e Soltar",
|
|
121
|
+
"agenda.settings.dragDropDesc": "Permitir reagendamento arrastando eventos",
|
|
122
|
+
"agenda.settings.enableConfirmations": "Habilitar Confirmações",
|
|
123
|
+
"agenda.settings.enableConfirmationsDesc": "Exigir confirmação para novos agendamentos",
|
|
124
|
+
"agenda.settings.enableLocation": "Habilitar Seleção de Local",
|
|
125
|
+
"agenda.settings.enableLocationDesc": "Mostrar seletor de local no modal de agendamento",
|
|
126
|
+
"agenda.settings.enableWorkingHours": "Habilitar Horário de Funcionamento",
|
|
127
|
+
"agenda.settings.enableWorkingHoursDesc": "Mostrar sobreposição de horário de funcionamento no calendário",
|
|
128
|
+
"agenda.settings.locations": "Locais",
|
|
129
|
+
"agenda.settings.locationsDesc": "Configurações de múltiplos locais",
|
|
130
|
+
"agenda.settings.newBookingNotif": "Alertas de Novo Agendamento",
|
|
131
|
+
"agenda.settings.newBookingNotifDesc": "Notificar quando um novo agendamento é criado",
|
|
132
|
+
"agenda.settings.noShowTracking": "Rastreamento de Ausências",
|
|
133
|
+
"agenda.settings.noShowTrackingDesc": "Rastrear e alertar sobre ausências",
|
|
134
|
+
"agenda.settings.notifications": "Notificações",
|
|
135
|
+
"agenda.settings.notificationsDesc": "Preferências de notificação",
|
|
136
|
+
"agenda.settings.reminder": "Lembretes",
|
|
137
|
+
"agenda.settings.reminderDesc": "Enviar lembretes antes dos compromissos",
|
|
138
|
+
"agenda.settings.scheduling": "Agendamento",
|
|
139
|
+
"agenda.settings.schedulingDesc": "Configurar comportamento do agendamento",
|
|
140
|
+
"agenda.settings.showCancelled": "Mostrar Cancelados",
|
|
141
|
+
"agenda.settings.showCancelledDesc": "Exibir agendamentos cancelados no calendário",
|
|
142
|
+
"agenda.settings.showNoShow": "Mostrar Ausências",
|
|
143
|
+
"agenda.settings.showNoShowDesc": "Exibir ausências no calendário",
|
|
144
|
+
"agenda.settings.workingHours": "Horário de Funcionamento",
|
|
145
|
+
"agenda.settings.workingHoursDesc": "Horários e disponibilidade",
|
|
146
|
+
"agenda.sidebar.agendas": "Agendas",
|
|
147
|
+
"agenda.sidebar.allLocations": "Todos os locais",
|
|
148
|
+
"agenda.sidebar.create": "Criar",
|
|
149
|
+
"agenda.sidebar.location": "Local",
|
|
150
|
+
"agenda.sidebar.status": "Status",
|
|
151
|
+
"agenda.toolbar.next": "Próximo",
|
|
152
|
+
"agenda.toolbar.previous": "Anterior",
|
|
153
|
+
"agenda.toolbar.today": "Hoje",
|
|
154
|
+
"agenda.workingHours.addBreak": "Adicionar intervalo",
|
|
155
|
+
"agenda.workingHours.copyToAll": "Copiar para todos",
|
|
156
|
+
"agenda.workingHours.dayOff": "Folga",
|
|
157
|
+
"agenda.workingHours.removePeriod": "Remover período",
|
|
158
|
+
"agenda.workingHours.professional": "Profissional",
|
|
159
|
+
"agenda.workingHours.save": "Salvar",
|
|
160
|
+
"agenda.workingHours.saveFailed": "Falha ao salvar",
|
|
161
|
+
"agenda.workingHours.saved": "Horários de trabalho salvos",
|
|
162
|
+
"agenda.workingHours.saving": "Salvando...",
|
|
163
|
+
"agenda.workingHours.subtitle": "Gerenciar horários de trabalho por profissional",
|
|
164
|
+
"agenda.workingHours.to": "até",
|
|
165
|
+
"agenda.title": "Agenda",
|
|
166
|
+
"agenda.calendar.title": "Calendário",
|
|
167
|
+
"agenda.calendar.noScheduleConfirm": "Este horário não possui agenda aberta para este profissional. Deseja agendar mesmo assim?",
|
|
168
|
+
"agenda.calendar.newAppointment": "Novo Agendamento",
|
|
169
|
+
"agenda.booking.create": "Novo Agendamento",
|
|
170
|
+
"agenda.booking.edit": "Editar Agendamento",
|
|
171
|
+
"agenda.booking.delete": "Excluir Agendamento",
|
|
172
|
+
"agenda.booking.deleteConfirm": "Tem certeza que deseja excluir este agendamento?",
|
|
173
|
+
"agenda.booking.client": "Cliente",
|
|
174
|
+
"agenda.booking.professional": "Profissional",
|
|
175
|
+
"agenda.booking.services": "Serviços",
|
|
176
|
+
"agenda.booking.date": "Data",
|
|
177
|
+
"agenda.booking.time": "Horário",
|
|
178
|
+
"agenda.booking.duration": "Duração",
|
|
179
|
+
"agenda.booking.notes": "Observações",
|
|
180
|
+
"agenda.booking.status": "Status",
|
|
181
|
+
"agenda.booking.save": "Salvar Agendamento",
|
|
182
|
+
"agenda.booking.saving": "Salvando...",
|
|
183
|
+
"agenda.confirmations.title": "Confirmações",
|
|
184
|
+
"agenda.confirmations.pending": "Confirmações Pendentes",
|
|
185
|
+
"agenda.confirmations.emptyDescription": "Todos os agendamentos futuros foram confirmados",
|
|
186
|
+
"agenda.confirmations.confirm": "Confirmar",
|
|
187
|
+
"agenda.confirmations.cancel": "Cancelar",
|
|
188
|
+
"agenda.workingHours.title": "Horário de Trabalho",
|
|
189
|
+
"agenda.workingHours.addPeriod": "Adicionar período",
|
|
190
|
+
"agenda.workingHours.loading": "Carregando...",
|
|
191
|
+
"agenda.appointment.addClient": "Adicionar cliente",
|
|
192
|
+
"agenda.appointment.addServiceFirst": "Adicionar serviço",
|
|
193
|
+
"agenda.appointment.addNotes": "Adicionar observações",
|
|
194
|
+
"agenda.appointment.duration": "Duração (minutos)",
|
|
195
|
+
"agenda.appointment.price": "Preço",
|
|
196
|
+
"agenda.bookingType.appointment": "Agendamento",
|
|
197
|
+
"agenda.bookingType.task": "Tarefa",
|
|
198
|
+
"agenda.bookingType.out_of_office": "Ausência",
|
|
199
|
+
"agenda.bookingType.block": "Bloqueio",
|
|
200
|
+
"agenda.settings.title": "Configurações da Agenda",
|
|
201
|
+
"agenda.settings.slotDuration": "Duração do Intervalo",
|
|
202
|
+
"agenda.settings.bookingKind": "Tipo de Agendamento",
|
|
203
|
+
"agenda.settings.dayStartTime": "Horário de Início",
|
|
204
|
+
"agenda.settings.dayStartTimeDesc": "Primeiro horário exibido no calendário",
|
|
205
|
+
"agenda.settings.dayEndTime": "Horário de Término",
|
|
206
|
+
"agenda.settings.dayEndTimeDesc": "Último horário exibido no calendário",
|
|
207
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PluginRegistryDef } from '@fayz-ai/core'
|
|
2
|
+
import type { EntityDef } from '@fayz-ai/core'
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Holidays (saas_core.holidays — tenant-scoped)
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
const holidayEntity: EntityDef = {
|
|
9
|
+
name: 'Holiday',
|
|
10
|
+
namePlural: 'Holidays',
|
|
11
|
+
icon: 'TreePalm',
|
|
12
|
+
displayField: 'name',
|
|
13
|
+
defaultSort: 'date',
|
|
14
|
+
defaultSortDir: 'asc',
|
|
15
|
+
fields: [
|
|
16
|
+
{ key: 'name', label: 'Name', type: 'text', required: true, showInTable: true },
|
|
17
|
+
{ key: 'date', label: 'Date', type: 'date', required: true, showInTable: true, sortable: true },
|
|
18
|
+
{ key: 'recurring', label: 'Recurring Annually', type: 'boolean', showInTable: true, defaultValue: false },
|
|
19
|
+
{ key: 'description', label: 'Description', type: 'text', showInTable: false },
|
|
20
|
+
{ key: 'isActive', label: 'Active', type: 'boolean', showInTable: true, defaultValue: true },
|
|
21
|
+
],
|
|
22
|
+
data: {
|
|
23
|
+
table: 'holidays',
|
|
24
|
+
schema: 'saas_core',
|
|
25
|
+
tenantScoped: true,
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const agendaRegistries: PluginRegistryDef[] = [
|
|
30
|
+
{
|
|
31
|
+
id: 'holidays',
|
|
32
|
+
entity: holidayEntity,
|
|
33
|
+
icon: 'TreePalm',
|
|
34
|
+
description: 'Non-working days and recurring holidays',
|
|
35
|
+
},
|
|
36
|
+
]
|
package/src/store.ts
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { createStore, type StoreApi } from 'zustand/vanilla'
|
|
2
|
+
import { dedup } from '@fayz-ai/saas'
|
|
3
|
+
import { toast } from '@fayz-ai/ui'
|
|
4
|
+
import { getScheduleBlockConfig, setScheduleBlockConfig } from '@fayz-ai/saas'
|
|
5
|
+
import type { AgendaDataProvider } from './data/types'
|
|
6
|
+
import type { AgendaFinancialBridge } from './financial-bridge'
|
|
7
|
+
import type {
|
|
8
|
+
CalendarBooking, Professional, Schedule,
|
|
9
|
+
CreateBookingInput, UpdateBookingInput, BookingQuery,
|
|
10
|
+
} from './types'
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Store state
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
export interface AgendaUIState {
|
|
17
|
+
// Calendar data
|
|
18
|
+
bookings: CalendarBooking[]
|
|
19
|
+
bookingsLoading: boolean
|
|
20
|
+
dateRange: { start: string; end: string }
|
|
21
|
+
currentView: string
|
|
22
|
+
selectedDate: string
|
|
23
|
+
|
|
24
|
+
// Resources
|
|
25
|
+
professionals: Professional[]
|
|
26
|
+
professionalsLoading: boolean
|
|
27
|
+
|
|
28
|
+
// Filters
|
|
29
|
+
selectedProfessionalIds: string[]
|
|
30
|
+
selectedLocationId: string | null
|
|
31
|
+
selectedStatuses: string[]
|
|
32
|
+
|
|
33
|
+
// Locations
|
|
34
|
+
locations: Array<{ id: string; name: string }>
|
|
35
|
+
|
|
36
|
+
// Schedules
|
|
37
|
+
schedules: Schedule[]
|
|
38
|
+
schedulesLoading: boolean
|
|
39
|
+
|
|
40
|
+
// Confirmations
|
|
41
|
+
pendingConfirmations: CalendarBooking[]
|
|
42
|
+
confirmationsLoading: boolean
|
|
43
|
+
|
|
44
|
+
// Modal state
|
|
45
|
+
appointmentModal: {
|
|
46
|
+
open: boolean
|
|
47
|
+
mode: 'create' | 'edit'
|
|
48
|
+
bookingId?: string
|
|
49
|
+
prefill?: Partial<CreateBookingInput> & { endsAt?: string }
|
|
50
|
+
initialTab?: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Actions
|
|
54
|
+
fetchBookings(range: { start: string; end: string }): Promise<void>
|
|
55
|
+
fetchProfessionals(): Promise<void>
|
|
56
|
+
fetchLocations(): Promise<void>
|
|
57
|
+
fetchSchedules(professionalId?: string): Promise<void>
|
|
58
|
+
fetchConfirmations(): Promise<void>
|
|
59
|
+
createBooking(input: CreateBookingInput): Promise<CalendarBooking>
|
|
60
|
+
updateBooking(id: string, data: UpdateBookingInput): Promise<CalendarBooking>
|
|
61
|
+
deleteBooking(id: string): Promise<void>
|
|
62
|
+
updateBookingStatus(id: string, status: string): Promise<void>
|
|
63
|
+
rescheduleBooking(id: string, newStart: string, newEnd: string, newProfessionalId?: string): Promise<void>
|
|
64
|
+
setFilters(filters: Partial<{ professionalIds: string[]; locationId: string | null; statuses: string[] }>): void
|
|
65
|
+
setView(view: string): void
|
|
66
|
+
setSelectedDate(date: string): void
|
|
67
|
+
openAppointmentModal(mode: 'create' | 'edit', options?: { bookingId?: string; prefill?: Partial<CreateBookingInput> & { endsAt?: string }; initialTab?: string }): void
|
|
68
|
+
closeAppointmentModal(): void
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Store factory
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
function getCurrentBookingQuery(state: AgendaUIState, dateRange = state.dateRange): BookingQuery {
|
|
76
|
+
return {
|
|
77
|
+
dateRange,
|
|
78
|
+
professionalIds: state.selectedProfessionalIds.length ? state.selectedProfessionalIds : undefined,
|
|
79
|
+
locationId: state.selectedLocationId ?? undefined,
|
|
80
|
+
statuses: state.selectedStatuses.length ? state.selectedStatuses : undefined,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function createAgendaStore(provider: AgendaDataProvider, financialBridge?: AgendaFinancialBridge): StoreApi<AgendaUIState> {
|
|
85
|
+
return createStore<AgendaUIState>((set, get) => ({
|
|
86
|
+
bookings: [],
|
|
87
|
+
bookingsLoading: false,
|
|
88
|
+
dateRange: { start: '', end: '' },
|
|
89
|
+
currentView: 'resourceTimeGridDay',
|
|
90
|
+
selectedDate: new Date().toISOString().slice(0, 10),
|
|
91
|
+
|
|
92
|
+
professionals: [],
|
|
93
|
+
professionalsLoading: false,
|
|
94
|
+
|
|
95
|
+
selectedProfessionalIds: [],
|
|
96
|
+
selectedLocationId: null,
|
|
97
|
+
selectedStatuses: ['scheduled', 'confirmed', 'in_progress', 'completed'],
|
|
98
|
+
|
|
99
|
+
locations: [],
|
|
100
|
+
|
|
101
|
+
schedules: [],
|
|
102
|
+
schedulesLoading: false,
|
|
103
|
+
|
|
104
|
+
pendingConfirmations: [],
|
|
105
|
+
confirmationsLoading: false,
|
|
106
|
+
|
|
107
|
+
appointmentModal: { open: false, mode: 'create' },
|
|
108
|
+
|
|
109
|
+
async fetchBookings(range) {
|
|
110
|
+
const key = `agenda:bookings:${range.start}:${range.end}`
|
|
111
|
+
return dedup(key, async () => {
|
|
112
|
+
set({ bookingsLoading: true, dateRange: range })
|
|
113
|
+
const state = get()
|
|
114
|
+
const query = getCurrentBookingQuery(state, range)
|
|
115
|
+
let bookings = await provider.getBookings(query)
|
|
116
|
+
|
|
117
|
+
// Enrich with payment status if financial bridge is available
|
|
118
|
+
if (financialBridge) {
|
|
119
|
+
const orderIds = bookings.map((b) => b.orderId).filter((id): id is string => !!id)
|
|
120
|
+
if (orderIds.length > 0) {
|
|
121
|
+
try {
|
|
122
|
+
const statuses = await financialBridge.resolvePaymentStatuses(orderIds)
|
|
123
|
+
bookings = bookings.map((b) => {
|
|
124
|
+
if (!b.orderId) return b
|
|
125
|
+
const summary = statuses.get(b.orderId)
|
|
126
|
+
return summary ? { ...b, paymentStatus: summary.status } : b
|
|
127
|
+
})
|
|
128
|
+
} catch { /* non-blocking */ }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
set({ bookings, bookingsLoading: false })
|
|
133
|
+
})
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
async fetchProfessionals() {
|
|
137
|
+
return dedup('agenda:professionals', async () => {
|
|
138
|
+
set({ professionalsLoading: true })
|
|
139
|
+
const professionals = await provider.getProfessionals()
|
|
140
|
+
set({ professionals, professionalsLoading: false })
|
|
141
|
+
})
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
async fetchLocations() {
|
|
145
|
+
return dedup('agenda:locations', async () => {
|
|
146
|
+
if (!provider.getLocations) return
|
|
147
|
+
const locations = await provider.getLocations()
|
|
148
|
+
set({ locations })
|
|
149
|
+
// Update global schedule block config so ScheduleEditor picks up locations
|
|
150
|
+
const current = getScheduleBlockConfig()
|
|
151
|
+
if (current) {
|
|
152
|
+
setScheduleBlockConfig({ ...current, locations })
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
async fetchSchedules(professionalId) {
|
|
158
|
+
return dedup('agenda:schedules:' + (professionalId ?? 'all'), async () => {
|
|
159
|
+
set({ schedulesLoading: true })
|
|
160
|
+
const schedules = await provider.getSchedules(professionalId)
|
|
161
|
+
set({ schedules, schedulesLoading: false })
|
|
162
|
+
})
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
async fetchConfirmations() {
|
|
166
|
+
return dedup('agenda:confirmations', async () => {
|
|
167
|
+
set({ confirmationsLoading: true })
|
|
168
|
+
const pendingConfirmations = await provider.getConfirmationsPending()
|
|
169
|
+
set({ pendingConfirmations, confirmationsLoading: false })
|
|
170
|
+
})
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
async createBooking(input) {
|
|
174
|
+
try {
|
|
175
|
+
const booking = await provider.createBooking(input)
|
|
176
|
+
// Refresh bookings for current range
|
|
177
|
+
const state = get()
|
|
178
|
+
if (state.dateRange.start && state.dateRange.end) {
|
|
179
|
+
const bookings = await provider.getBookings(getCurrentBookingQuery(state))
|
|
180
|
+
set({ bookings })
|
|
181
|
+
}
|
|
182
|
+
toast.success('Appointment created')
|
|
183
|
+
return booking
|
|
184
|
+
} catch (err: any) {
|
|
185
|
+
toast.error('Failed to create appointment', { description: err?.message })
|
|
186
|
+
throw err
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
async updateBooking(id, data) {
|
|
191
|
+
try {
|
|
192
|
+
const booking = await provider.updateBooking(id, data)
|
|
193
|
+
const state = get()
|
|
194
|
+
if (state.dateRange.start && state.dateRange.end) {
|
|
195
|
+
const bookings = await provider.getBookings(getCurrentBookingQuery(state))
|
|
196
|
+
set({ bookings })
|
|
197
|
+
}
|
|
198
|
+
toast.success('Appointment updated')
|
|
199
|
+
return booking
|
|
200
|
+
} catch (err: any) {
|
|
201
|
+
toast.error('Failed to update appointment', { description: err?.message })
|
|
202
|
+
throw err
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
async deleteBooking(id) {
|
|
207
|
+
try {
|
|
208
|
+
await provider.deleteBooking(id)
|
|
209
|
+
const state = get()
|
|
210
|
+
if (state.dateRange.start && state.dateRange.end) {
|
|
211
|
+
const bookings = await provider.getBookings(getCurrentBookingQuery(state))
|
|
212
|
+
set({ bookings })
|
|
213
|
+
}
|
|
214
|
+
toast.success('Appointment deleted')
|
|
215
|
+
} catch (err: any) {
|
|
216
|
+
toast.error('Failed to delete appointment', { description: err?.message })
|
|
217
|
+
throw err
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
async updateBookingStatus(id, status) {
|
|
222
|
+
// Optimistic update — change color immediately
|
|
223
|
+
const prev = get().bookings
|
|
224
|
+
set({ bookings: prev.map((b) => b.id === id ? { ...b, status: status as any } : b) })
|
|
225
|
+
try {
|
|
226
|
+
await provider.updateBookingStatus(id, status)
|
|
227
|
+
// Refresh from server to get full data
|
|
228
|
+
const state = get()
|
|
229
|
+
if (state.dateRange.start && state.dateRange.end) {
|
|
230
|
+
const bookings = await provider.getBookings(getCurrentBookingQuery(state))
|
|
231
|
+
set({ bookings })
|
|
232
|
+
}
|
|
233
|
+
toast.success('Status updated')
|
|
234
|
+
} catch (err: any) {
|
|
235
|
+
// Rollback on failure
|
|
236
|
+
set({ bookings: prev })
|
|
237
|
+
toast.error('Failed to update status', { description: err?.message })
|
|
238
|
+
throw err
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
async rescheduleBooking(id, newStart, newEnd, newProfessionalId) {
|
|
243
|
+
try {
|
|
244
|
+
// Check for conflicts
|
|
245
|
+
const existing = get().bookings.find((b) => b.id === id)
|
|
246
|
+
const assigneeId = newProfessionalId ?? existing?.professionalId
|
|
247
|
+
if (assigneeId) {
|
|
248
|
+
const hasConflict = await provider.checkConflict({
|
|
249
|
+
assigneeId,
|
|
250
|
+
startsAt: newStart,
|
|
251
|
+
endsAt: newEnd,
|
|
252
|
+
excludeBookingId: id,
|
|
253
|
+
})
|
|
254
|
+
if (hasConflict) {
|
|
255
|
+
toast.error('Time conflict detected', { description: 'This professional already has an appointment at this time.' })
|
|
256
|
+
throw new Error('Time conflict')
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
await provider.updateBooking(id, {
|
|
260
|
+
startsAt: newStart,
|
|
261
|
+
endsAt: newEnd,
|
|
262
|
+
...(newProfessionalId && { professionalId: newProfessionalId }),
|
|
263
|
+
})
|
|
264
|
+
const state = get()
|
|
265
|
+
if (state.dateRange.start && state.dateRange.end) {
|
|
266
|
+
const bookings = await provider.getBookings(getCurrentBookingQuery(state))
|
|
267
|
+
set({ bookings })
|
|
268
|
+
}
|
|
269
|
+
toast.success('Appointment rescheduled')
|
|
270
|
+
} catch (err: any) {
|
|
271
|
+
if (err?.message !== 'Time conflict') {
|
|
272
|
+
toast.error('Failed to reschedule', { description: err?.message })
|
|
273
|
+
}
|
|
274
|
+
throw err
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
setFilters(filters) {
|
|
279
|
+
set({
|
|
280
|
+
...(filters.professionalIds !== undefined && { selectedProfessionalIds: filters.professionalIds }),
|
|
281
|
+
...(filters.locationId !== undefined && { selectedLocationId: filters.locationId }),
|
|
282
|
+
...(filters.statuses !== undefined && { selectedStatuses: filters.statuses }),
|
|
283
|
+
})
|
|
284
|
+
// Re-fetch with new filters
|
|
285
|
+
const { dateRange } = get()
|
|
286
|
+
if (dateRange.start && dateRange.end) {
|
|
287
|
+
get().fetchBookings(dateRange)
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
setView(view) { set({ currentView: view }) },
|
|
292
|
+
setSelectedDate(date) { set({ selectedDate: date }) },
|
|
293
|
+
|
|
294
|
+
openAppointmentModal(mode, options) {
|
|
295
|
+
set({ appointmentModal: { open: true, mode, bookingId: options?.bookingId, prefill: options?.prefill, initialTab: options?.initialTab } })
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
closeAppointmentModal() {
|
|
299
|
+
// Only toggle open — keep mode/bookingId/prefill intact so layout
|
|
300
|
+
// doesn't shift during the close animation
|
|
301
|
+
set((state) => ({ appointmentModal: { ...state.appointmentModal, open: false } }))
|
|
302
|
+
},
|
|
303
|
+
}))
|
|
304
|
+
}
|