@adatechnology/scheduling-ui 0.1.0-rc.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.
- package/README.md +430 -0
- package/dist/index.d.ts +316 -0
- package/dist/index.js +1917 -0
- package/package.json +47 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1917 @@
|
|
|
1
|
+
// src/providers/types.ts
|
|
2
|
+
var DEFAULT_SCHEDULING_UI_CONFIG = {
|
|
3
|
+
locale: "pt-BR",
|
|
4
|
+
weekStartsOn: 1,
|
|
5
|
+
agendaStartHour: 7,
|
|
6
|
+
agendaEndHour: 20
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// src/providers/SchedulingProvider.tsx
|
|
10
|
+
import { createContext, useContext, useMemo } from "react";
|
|
11
|
+
import { jsx } from "react/jsx-runtime";
|
|
12
|
+
var SchedulingContext = createContext(null);
|
|
13
|
+
function SchedulingProvider({ api, config, children }) {
|
|
14
|
+
const value = useMemo(
|
|
15
|
+
() => ({ api, config: { ...DEFAULT_SCHEDULING_UI_CONFIG, ...config } }),
|
|
16
|
+
[api, config]
|
|
17
|
+
);
|
|
18
|
+
return /* @__PURE__ */ jsx(SchedulingContext.Provider, { value, children });
|
|
19
|
+
}
|
|
20
|
+
function useScheduling() {
|
|
21
|
+
return useSchedulingContext().api;
|
|
22
|
+
}
|
|
23
|
+
function useSchedulingConfig() {
|
|
24
|
+
return useSchedulingContext().config;
|
|
25
|
+
}
|
|
26
|
+
function useSchedulingContext() {
|
|
27
|
+
const value = useContext(SchedulingContext);
|
|
28
|
+
if (!value) {
|
|
29
|
+
throw new Error("useScheduling() must be used within a <SchedulingProvider>");
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/hooks/queryKeys.ts
|
|
35
|
+
var SCHEDULING_QUERY_KEYS = {
|
|
36
|
+
resources: {
|
|
37
|
+
all: ["scheduling", "resources"],
|
|
38
|
+
list: (params) => ["scheduling", "resources", "list", params]
|
|
39
|
+
},
|
|
40
|
+
services: {
|
|
41
|
+
all: ["scheduling", "services"],
|
|
42
|
+
list: (params) => ["scheduling", "services", "list", params]
|
|
43
|
+
},
|
|
44
|
+
availabilityRules: {
|
|
45
|
+
all: (resourceId) => ["scheduling", "availability-rules", resourceId]
|
|
46
|
+
},
|
|
47
|
+
availabilityExceptions: {
|
|
48
|
+
all: (resourceId) => ["scheduling", "availability-exceptions", resourceId]
|
|
49
|
+
},
|
|
50
|
+
availableSlots: {
|
|
51
|
+
list: (params) => ["scheduling", "available-slots", params]
|
|
52
|
+
},
|
|
53
|
+
bookings: {
|
|
54
|
+
all: ["scheduling", "bookings"],
|
|
55
|
+
list: (params) => ["scheduling", "bookings", "list", params],
|
|
56
|
+
detail: (id) => ["scheduling", "bookings", "detail", id]
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// src/hooks/useResources.query.ts
|
|
61
|
+
import { useQuery } from "@tanstack/react-query";
|
|
62
|
+
function useResources(options = {}) {
|
|
63
|
+
const api = useScheduling();
|
|
64
|
+
return useQuery({
|
|
65
|
+
queryKey: SCHEDULING_QUERY_KEYS.resources.list(options),
|
|
66
|
+
queryFn: () => api.listResources(options)
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/hooks/useResourceMutations.mutation.ts
|
|
71
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
72
|
+
function useCreateResource() {
|
|
73
|
+
const api = useScheduling();
|
|
74
|
+
const queryClient = useQueryClient();
|
|
75
|
+
return useMutation({
|
|
76
|
+
mutationFn: (input) => api.createResource(input),
|
|
77
|
+
onSuccess() {
|
|
78
|
+
void queryClient.invalidateQueries({ queryKey: SCHEDULING_QUERY_KEYS.resources.all });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function useUpdateResource() {
|
|
83
|
+
const api = useScheduling();
|
|
84
|
+
const queryClient = useQueryClient();
|
|
85
|
+
return useMutation({
|
|
86
|
+
mutationFn: ({ id, input }) => api.updateResource(id, input),
|
|
87
|
+
onSuccess() {
|
|
88
|
+
void queryClient.invalidateQueries({ queryKey: SCHEDULING_QUERY_KEYS.resources.all });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function useDeleteResource() {
|
|
93
|
+
const api = useScheduling();
|
|
94
|
+
const queryClient = useQueryClient();
|
|
95
|
+
return useMutation({
|
|
96
|
+
mutationFn: (id) => api.deleteResource(id),
|
|
97
|
+
onSuccess() {
|
|
98
|
+
void queryClient.invalidateQueries({ queryKey: SCHEDULING_QUERY_KEYS.resources.all });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/hooks/useServices.query.ts
|
|
104
|
+
import { useQuery as useQuery2 } from "@tanstack/react-query";
|
|
105
|
+
function useServices(options = {}) {
|
|
106
|
+
const api = useScheduling();
|
|
107
|
+
return useQuery2({
|
|
108
|
+
queryKey: SCHEDULING_QUERY_KEYS.services.list(options),
|
|
109
|
+
queryFn: () => api.listServices(options)
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/hooks/useServiceMutations.mutation.ts
|
|
114
|
+
import { useMutation as useMutation2, useQueryClient as useQueryClient2 } from "@tanstack/react-query";
|
|
115
|
+
function useCreateService() {
|
|
116
|
+
const api = useScheduling();
|
|
117
|
+
const queryClient = useQueryClient2();
|
|
118
|
+
return useMutation2({
|
|
119
|
+
mutationFn: (input) => api.createService(input),
|
|
120
|
+
onSuccess() {
|
|
121
|
+
void queryClient.invalidateQueries({ queryKey: SCHEDULING_QUERY_KEYS.services.all });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function useUpdateService() {
|
|
126
|
+
const api = useScheduling();
|
|
127
|
+
const queryClient = useQueryClient2();
|
|
128
|
+
return useMutation2({
|
|
129
|
+
mutationFn: ({ id, input }) => api.updateService(id, input),
|
|
130
|
+
onSuccess() {
|
|
131
|
+
void queryClient.invalidateQueries({ queryKey: SCHEDULING_QUERY_KEYS.services.all });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function useDeleteService() {
|
|
136
|
+
const api = useScheduling();
|
|
137
|
+
const queryClient = useQueryClient2();
|
|
138
|
+
return useMutation2({
|
|
139
|
+
mutationFn: (id) => api.deleteService(id),
|
|
140
|
+
onSuccess() {
|
|
141
|
+
void queryClient.invalidateQueries({ queryKey: SCHEDULING_QUERY_KEYS.services.all });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/hooks/useAvailabilityRules.query.ts
|
|
147
|
+
import { useQuery as useQuery3 } from "@tanstack/react-query";
|
|
148
|
+
function useAvailabilityRules(resourceId) {
|
|
149
|
+
const api = useScheduling();
|
|
150
|
+
return useQuery3({
|
|
151
|
+
queryKey: SCHEDULING_QUERY_KEYS.availabilityRules.all(resourceId),
|
|
152
|
+
queryFn: () => api.listAvailabilityRules(resourceId),
|
|
153
|
+
enabled: resourceId.length > 0
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/hooks/useAvailabilityExceptions.query.ts
|
|
158
|
+
import { useQuery as useQuery4 } from "@tanstack/react-query";
|
|
159
|
+
function useAvailabilityExceptions(resourceId) {
|
|
160
|
+
const api = useScheduling();
|
|
161
|
+
return useQuery4({
|
|
162
|
+
queryKey: SCHEDULING_QUERY_KEYS.availabilityExceptions.all(resourceId),
|
|
163
|
+
queryFn: () => api.listAvailabilityExceptions(resourceId),
|
|
164
|
+
enabled: resourceId.length > 0
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/hooks/useAvailabilityMutations.mutation.ts
|
|
169
|
+
import { useMutation as useMutation3, useQueryClient as useQueryClient3 } from "@tanstack/react-query";
|
|
170
|
+
function useSetAvailabilityRules() {
|
|
171
|
+
const api = useScheduling();
|
|
172
|
+
const queryClient = useQueryClient3();
|
|
173
|
+
return useMutation3({
|
|
174
|
+
mutationFn: ({
|
|
175
|
+
resourceId,
|
|
176
|
+
rules
|
|
177
|
+
}) => api.setAvailabilityRules(resourceId, rules),
|
|
178
|
+
onSuccess(_data, { resourceId }) {
|
|
179
|
+
void queryClient.invalidateQueries({ queryKey: SCHEDULING_QUERY_KEYS.availabilityRules.all(resourceId) });
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
function useAddAvailabilityException() {
|
|
184
|
+
const api = useScheduling();
|
|
185
|
+
const queryClient = useQueryClient3();
|
|
186
|
+
return useMutation3({
|
|
187
|
+
mutationFn: (input) => api.addAvailabilityException(input),
|
|
188
|
+
onSuccess(data) {
|
|
189
|
+
void queryClient.invalidateQueries({
|
|
190
|
+
queryKey: SCHEDULING_QUERY_KEYS.availabilityExceptions.all(data.resourceId)
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function useRemoveAvailabilityException() {
|
|
196
|
+
const api = useScheduling();
|
|
197
|
+
const queryClient = useQueryClient3();
|
|
198
|
+
return useMutation3({
|
|
199
|
+
mutationFn: ({ id }) => api.removeAvailabilityException(id),
|
|
200
|
+
onSuccess(_data, { resourceId }) {
|
|
201
|
+
void queryClient.invalidateQueries({
|
|
202
|
+
queryKey: SCHEDULING_QUERY_KEYS.availabilityExceptions.all(resourceId)
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/hooks/useAvailableSlots.query.ts
|
|
209
|
+
import { useQuery as useQuery5 } from "@tanstack/react-query";
|
|
210
|
+
function useAvailableSlots(params) {
|
|
211
|
+
const api = useScheduling();
|
|
212
|
+
return useQuery5({
|
|
213
|
+
queryKey: SCHEDULING_QUERY_KEYS.availableSlots.list(params),
|
|
214
|
+
queryFn: () => api.getAvailableSlots(params),
|
|
215
|
+
enabled: Boolean(params.resourceId && params.from && params.until)
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/hooks/useBookings.query.ts
|
|
220
|
+
import { useQuery as useQuery6 } from "@tanstack/react-query";
|
|
221
|
+
function useBookings(params = {}) {
|
|
222
|
+
const api = useScheduling();
|
|
223
|
+
return useQuery6({
|
|
224
|
+
queryKey: SCHEDULING_QUERY_KEYS.bookings.list(params),
|
|
225
|
+
queryFn: () => api.listBookings(params)
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function useBooking(id) {
|
|
229
|
+
const api = useScheduling();
|
|
230
|
+
return useQuery6({
|
|
231
|
+
queryKey: SCHEDULING_QUERY_KEYS.bookings.detail(id),
|
|
232
|
+
queryFn: () => api.getBooking(id),
|
|
233
|
+
enabled: id.length > 0
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/hooks/useBookingMutations.mutation.ts
|
|
238
|
+
import { useMutation as useMutation4, useQueryClient as useQueryClient4 } from "@tanstack/react-query";
|
|
239
|
+
function invalidateBooking(queryClient, booking) {
|
|
240
|
+
void queryClient.invalidateQueries({ queryKey: SCHEDULING_QUERY_KEYS.bookings.all });
|
|
241
|
+
void queryClient.invalidateQueries({ queryKey: SCHEDULING_QUERY_KEYS.bookings.detail(booking.id) });
|
|
242
|
+
}
|
|
243
|
+
function useConfirmBooking() {
|
|
244
|
+
const api = useScheduling();
|
|
245
|
+
const queryClient = useQueryClient4();
|
|
246
|
+
return useMutation4({
|
|
247
|
+
mutationFn: (id) => api.confirmBooking(id),
|
|
248
|
+
onSuccess: (booking) => invalidateBooking(queryClient, booking)
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
function useCancelBooking() {
|
|
252
|
+
const api = useScheduling();
|
|
253
|
+
const queryClient = useQueryClient4();
|
|
254
|
+
return useMutation4({
|
|
255
|
+
mutationFn: ({ id, input }) => api.cancelBooking(id, input),
|
|
256
|
+
onSuccess: (booking) => invalidateBooking(queryClient, booking)
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
function useCompleteBooking() {
|
|
260
|
+
const api = useScheduling();
|
|
261
|
+
const queryClient = useQueryClient4();
|
|
262
|
+
return useMutation4({
|
|
263
|
+
mutationFn: (id) => api.completeBooking(id),
|
|
264
|
+
onSuccess: (booking) => invalidateBooking(queryClient, booking)
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
function useMarkNoShow() {
|
|
268
|
+
const api = useScheduling();
|
|
269
|
+
const queryClient = useQueryClient4();
|
|
270
|
+
return useMutation4({
|
|
271
|
+
mutationFn: (id) => api.markNoShow(id),
|
|
272
|
+
onSuccess: (booking) => invalidateBooking(queryClient, booking)
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/hooks/useRequestBooking.mutation.ts
|
|
277
|
+
import { useMutation as useMutation5, useQueryClient as useQueryClient5 } from "@tanstack/react-query";
|
|
278
|
+
function useRequestBooking() {
|
|
279
|
+
const api = useScheduling();
|
|
280
|
+
const queryClient = useQueryClient5();
|
|
281
|
+
return useMutation5({
|
|
282
|
+
mutationFn: ({ input, idempotencyKey }) => api.requestBooking(input, idempotencyKey),
|
|
283
|
+
onSuccess() {
|
|
284
|
+
void queryClient.invalidateQueries({ queryKey: SCHEDULING_QUERY_KEYS.bookings.all });
|
|
285
|
+
void queryClient.invalidateQueries({ queryKey: ["scheduling", "available-slots"] });
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/hooks/useRescheduleBooking.mutation.ts
|
|
291
|
+
import { useMutation as useMutation6, useQueryClient as useQueryClient6 } from "@tanstack/react-query";
|
|
292
|
+
function useRescheduleBooking() {
|
|
293
|
+
const api = useScheduling();
|
|
294
|
+
const queryClient = useQueryClient6();
|
|
295
|
+
return useMutation6({
|
|
296
|
+
mutationFn: ({ id, input }) => api.rescheduleBooking(id, input),
|
|
297
|
+
onSuccess(data) {
|
|
298
|
+
void queryClient.invalidateQueries({ queryKey: SCHEDULING_QUERY_KEYS.bookings.all });
|
|
299
|
+
void queryClient.invalidateQueries({ queryKey: SCHEDULING_QUERY_KEYS.bookings.detail(data.id) });
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/workspace/labels.ts
|
|
305
|
+
var DEFAULT_SCHEDULING_WORKSPACE_LABELS = {
|
|
306
|
+
title: "Agendamento",
|
|
307
|
+
areaNav: "\xC1reas do agendamento",
|
|
308
|
+
agendaTab: "Agenda",
|
|
309
|
+
bookingsTab: "Reservas",
|
|
310
|
+
resourcesTab: "Recursos",
|
|
311
|
+
servicesTab: "Servi\xE7os",
|
|
312
|
+
availabilityTab: "Disponibilidade"
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// src/workspace/SchedulingWorkspace.tsx
|
|
316
|
+
import { useState as useState13 } from "react";
|
|
317
|
+
|
|
318
|
+
// src/workspace/AgendaArea.tsx
|
|
319
|
+
import { useState } from "react";
|
|
320
|
+
import { MAX_PAGE_SIZE } from "@adatechnology/scheduling-contracts";
|
|
321
|
+
|
|
322
|
+
// src/locales/pt-BR.json
|
|
323
|
+
var pt_BR_default = {
|
|
324
|
+
"common.close": "Fechar",
|
|
325
|
+
"common.cancel": "Cancelar",
|
|
326
|
+
"common.save": "Salvar",
|
|
327
|
+
"common.remove": "Excluir",
|
|
328
|
+
"common.loading": "Carregando\u2026",
|
|
329
|
+
"common.loadFailure": "N\xE3o foi poss\xEDvel carregar",
|
|
330
|
+
"common.actionFailure": "A\xE7\xE3o falhou. Tente novamente.",
|
|
331
|
+
"common.empty": "Nada por aqui ainda",
|
|
332
|
+
"common.clearFilters": "Limpar filtros",
|
|
333
|
+
"common.selectAll": "Selecionar todos",
|
|
334
|
+
"common.previousPage": "P\xE1gina anterior",
|
|
335
|
+
"common.nextPage": "Pr\xF3xima p\xE1gina",
|
|
336
|
+
"resource.newResource": "Novo recurso",
|
|
337
|
+
"resource.editTitle": "Editar recurso",
|
|
338
|
+
"resource.createTitle": "Novo recurso",
|
|
339
|
+
"resource.name": "Nome",
|
|
340
|
+
"resource.kind": "Tipo",
|
|
341
|
+
"resource.timezone": "Fuso hor\xE1rio",
|
|
342
|
+
"resource.active": "Ativo",
|
|
343
|
+
"resource.inactive": "Inativo",
|
|
344
|
+
"resource.kind.person": "Pessoa",
|
|
345
|
+
"resource.kind.room": "Sala",
|
|
346
|
+
"resource.kind.equipment": "Equipamento",
|
|
347
|
+
"service.newService": "Novo servi\xE7o",
|
|
348
|
+
"service.editTitle": "Editar servi\xE7o",
|
|
349
|
+
"service.createTitle": "Novo servi\xE7o",
|
|
350
|
+
"service.name": "Nome",
|
|
351
|
+
"service.durationMinutes": "Dura\xE7\xE3o (minutos)",
|
|
352
|
+
"service.bufferBeforeMinutes": "Intervalo antes (minutos)",
|
|
353
|
+
"service.bufferAfterMinutes": "Intervalo depois (minutos)",
|
|
354
|
+
"service.active": "Ativo",
|
|
355
|
+
"service.inactive": "Inativo",
|
|
356
|
+
"agenda.resourceLabel": "Recurso",
|
|
357
|
+
"agenda.today": "Hoje",
|
|
358
|
+
"agenda.previous": "Anterior",
|
|
359
|
+
"agenda.next": "Pr\xF3ximo",
|
|
360
|
+
"agenda.viewDay": "Dia",
|
|
361
|
+
"agenda.viewWeek": "Semana",
|
|
362
|
+
"agenda.empty": "Sem reservas neste per\xEDodo",
|
|
363
|
+
"availability.weeklyRulesTitle": "Regras semanais",
|
|
364
|
+
"availability.exceptionsTitle": "Exce\xE7\xF5es",
|
|
365
|
+
"availability.resourceTimezone": "Fuso do recurso",
|
|
366
|
+
"availability.addRule": "Adicionar regra",
|
|
367
|
+
"availability.removeRule": "Remover",
|
|
368
|
+
"availability.addException": "Adicionar exce\xE7\xE3o",
|
|
369
|
+
"availability.removeException": "Remover",
|
|
370
|
+
"availability.exceptionKind.blocked": "Bloqueio",
|
|
371
|
+
"availability.exceptionKind.extra": "Extra",
|
|
372
|
+
"availability.exceptionReason": "Motivo",
|
|
373
|
+
"availability.weekday.0": "Domingo",
|
|
374
|
+
"availability.weekday.1": "Segunda",
|
|
375
|
+
"availability.weekday.2": "Ter\xE7a",
|
|
376
|
+
"availability.weekday.3": "Quarta",
|
|
377
|
+
"availability.weekday.4": "Quinta",
|
|
378
|
+
"availability.weekday.5": "Sexta",
|
|
379
|
+
"availability.weekday.6": "S\xE1bado",
|
|
380
|
+
"booking.status.requested": "Solicitada",
|
|
381
|
+
"booking.status.confirmed": "Confirmada",
|
|
382
|
+
"booking.status.cancelled": "Cancelada",
|
|
383
|
+
"booking.status.completed": "Conclu\xEDda",
|
|
384
|
+
"booking.status.noShow": "N\xE3o compareceu",
|
|
385
|
+
"booking.confirm": "Confirmar",
|
|
386
|
+
"booking.cancel": "Cancelar reserva",
|
|
387
|
+
"booking.complete": "Concluir",
|
|
388
|
+
"booking.markNoShow": "Marcar n\xE3o compareceu",
|
|
389
|
+
"booking.reschedule": "Reagendar",
|
|
390
|
+
"booking.detailTitle": "Detalhes da reserva",
|
|
391
|
+
"booking.cancelReason": "Motivo do cancelamento",
|
|
392
|
+
"booking.cancelledBy": "Cancelado por",
|
|
393
|
+
"booking.column.title": "T\xEDtulo",
|
|
394
|
+
"booking.column.resource": "Recurso",
|
|
395
|
+
"booking.column.status": "Status",
|
|
396
|
+
"booking.column.startsAt": "In\xEDcio",
|
|
397
|
+
"booking.column.endsAt": "Fim",
|
|
398
|
+
"booking.column.actions": "A\xE7\xF5es",
|
|
399
|
+
"booking.filterByStatus": "Filtrar por status",
|
|
400
|
+
"booking.bulkConfirm": "Confirmar selecionadas",
|
|
401
|
+
"booking.bulkCancel": "Cancelar selecionadas",
|
|
402
|
+
"availability.weekdayLabel": "Dia da semana",
|
|
403
|
+
"availability.startsAtLocal": "In\xEDcio",
|
|
404
|
+
"availability.endsAtLocal": "Fim",
|
|
405
|
+
"availability.exceptionFrom": "De",
|
|
406
|
+
"availability.exceptionUntil": "At\xE9"
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
// src/locales/en.json
|
|
410
|
+
var en_default = {
|
|
411
|
+
"common.close": "Close",
|
|
412
|
+
"common.cancel": "Cancel",
|
|
413
|
+
"common.save": "Save",
|
|
414
|
+
"common.remove": "Delete",
|
|
415
|
+
"common.loading": "Loading\u2026",
|
|
416
|
+
"common.loadFailure": "Could not load",
|
|
417
|
+
"common.actionFailure": "Action failed. Try again.",
|
|
418
|
+
"common.empty": "Nothing here yet",
|
|
419
|
+
"common.clearFilters": "Clear filters",
|
|
420
|
+
"common.selectAll": "Select all",
|
|
421
|
+
"common.previousPage": "Previous page",
|
|
422
|
+
"common.nextPage": "Next page",
|
|
423
|
+
"resource.newResource": "New resource",
|
|
424
|
+
"resource.editTitle": "Edit resource",
|
|
425
|
+
"resource.createTitle": "New resource",
|
|
426
|
+
"resource.name": "Name",
|
|
427
|
+
"resource.kind": "Kind",
|
|
428
|
+
"resource.timezone": "Timezone",
|
|
429
|
+
"resource.active": "Active",
|
|
430
|
+
"resource.inactive": "Inactive",
|
|
431
|
+
"resource.kind.person": "Person",
|
|
432
|
+
"resource.kind.room": "Room",
|
|
433
|
+
"resource.kind.equipment": "Equipment",
|
|
434
|
+
"service.newService": "New service",
|
|
435
|
+
"service.editTitle": "Edit service",
|
|
436
|
+
"service.createTitle": "New service",
|
|
437
|
+
"service.name": "Name",
|
|
438
|
+
"service.durationMinutes": "Duration (minutes)",
|
|
439
|
+
"service.bufferBeforeMinutes": "Buffer before (minutes)",
|
|
440
|
+
"service.bufferAfterMinutes": "Buffer after (minutes)",
|
|
441
|
+
"service.active": "Active",
|
|
442
|
+
"service.inactive": "Inactive",
|
|
443
|
+
"agenda.resourceLabel": "Resource",
|
|
444
|
+
"agenda.today": "Today",
|
|
445
|
+
"agenda.previous": "Previous",
|
|
446
|
+
"agenda.next": "Next",
|
|
447
|
+
"agenda.viewDay": "Day",
|
|
448
|
+
"agenda.viewWeek": "Week",
|
|
449
|
+
"agenda.empty": "No bookings in this period",
|
|
450
|
+
"availability.weeklyRulesTitle": "Weekly rules",
|
|
451
|
+
"availability.exceptionsTitle": "Exceptions",
|
|
452
|
+
"availability.resourceTimezone": "Resource timezone",
|
|
453
|
+
"availability.addRule": "Add rule",
|
|
454
|
+
"availability.removeRule": "Remove",
|
|
455
|
+
"availability.addException": "Add exception",
|
|
456
|
+
"availability.removeException": "Remove",
|
|
457
|
+
"availability.exceptionKind.blocked": "Blocked",
|
|
458
|
+
"availability.exceptionKind.extra": "Extra",
|
|
459
|
+
"availability.exceptionReason": "Reason",
|
|
460
|
+
"availability.weekday.0": "Sunday",
|
|
461
|
+
"availability.weekday.1": "Monday",
|
|
462
|
+
"availability.weekday.2": "Tuesday",
|
|
463
|
+
"availability.weekday.3": "Wednesday",
|
|
464
|
+
"availability.weekday.4": "Thursday",
|
|
465
|
+
"availability.weekday.5": "Friday",
|
|
466
|
+
"availability.weekday.6": "Saturday",
|
|
467
|
+
"booking.status.requested": "Requested",
|
|
468
|
+
"booking.status.confirmed": "Confirmed",
|
|
469
|
+
"booking.status.cancelled": "Cancelled",
|
|
470
|
+
"booking.status.completed": "Completed",
|
|
471
|
+
"booking.status.noShow": "No-show",
|
|
472
|
+
"booking.confirm": "Confirm",
|
|
473
|
+
"booking.cancel": "Cancel booking",
|
|
474
|
+
"booking.complete": "Complete",
|
|
475
|
+
"booking.markNoShow": "Mark no-show",
|
|
476
|
+
"booking.reschedule": "Reschedule",
|
|
477
|
+
"booking.detailTitle": "Booking details",
|
|
478
|
+
"booking.cancelReason": "Cancellation reason",
|
|
479
|
+
"booking.cancelledBy": "Cancelled by",
|
|
480
|
+
"booking.column.title": "Title",
|
|
481
|
+
"booking.column.resource": "Resource",
|
|
482
|
+
"booking.column.status": "Status",
|
|
483
|
+
"booking.column.startsAt": "Starts",
|
|
484
|
+
"booking.column.endsAt": "Ends",
|
|
485
|
+
"booking.column.actions": "Actions",
|
|
486
|
+
"booking.filterByStatus": "Filter by status",
|
|
487
|
+
"booking.bulkConfirm": "Confirm selected",
|
|
488
|
+
"booking.bulkCancel": "Cancel selected",
|
|
489
|
+
"availability.weekdayLabel": "Weekday",
|
|
490
|
+
"availability.startsAtLocal": "Starts",
|
|
491
|
+
"availability.endsAtLocal": "Ends",
|
|
492
|
+
"availability.exceptionFrom": "From",
|
|
493
|
+
"availability.exceptionUntil": "Until"
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
// src/locales/index.ts
|
|
497
|
+
var DEFAULT_SCHEDULING_LOCALE = "pt-BR";
|
|
498
|
+
var MESSAGES_BY_LOCALE = {
|
|
499
|
+
"pt-BR": pt_BR_default,
|
|
500
|
+
en: en_default
|
|
501
|
+
};
|
|
502
|
+
function resolveSchedulingMessages(locale) {
|
|
503
|
+
return MESSAGES_BY_LOCALE[locale] ?? MESSAGES_BY_LOCALE[DEFAULT_SCHEDULING_LOCALE];
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// src/components/agendaLayout.util.ts
|
|
507
|
+
function groupOverlappingBookings(sortedBookings) {
|
|
508
|
+
const clusters = [];
|
|
509
|
+
let current = [];
|
|
510
|
+
let currentEnd = -Infinity;
|
|
511
|
+
for (const booking of sortedBookings) {
|
|
512
|
+
if (current.length > 0 && booking.startsAt.getTime() >= currentEnd) {
|
|
513
|
+
clusters.push(current);
|
|
514
|
+
current = [];
|
|
515
|
+
currentEnd = -Infinity;
|
|
516
|
+
}
|
|
517
|
+
current.push(booking);
|
|
518
|
+
currentEnd = Math.max(currentEnd, booking.endsAt.getTime());
|
|
519
|
+
}
|
|
520
|
+
if (current.length > 0) clusters.push(current);
|
|
521
|
+
return clusters;
|
|
522
|
+
}
|
|
523
|
+
function assignColumns(cluster) {
|
|
524
|
+
const columnEnds = [];
|
|
525
|
+
const positioned = [];
|
|
526
|
+
for (const booking of cluster) {
|
|
527
|
+
const freeColumnIndex = columnEnds.findIndex((end) => end <= booking.startsAt.getTime());
|
|
528
|
+
const columnIndex = freeColumnIndex === -1 ? columnEnds.length : freeColumnIndex;
|
|
529
|
+
columnEnds[columnIndex] = booking.endsAt.getTime();
|
|
530
|
+
positioned.push({ booking, columnIndex });
|
|
531
|
+
}
|
|
532
|
+
const columnCount = columnEnds.length;
|
|
533
|
+
return positioned.map((item) => ({ ...item, columnCount }));
|
|
534
|
+
}
|
|
535
|
+
function layoutDayBookings(bookings) {
|
|
536
|
+
const sorted = [...bookings].sort((a, b) => a.startsAt.getTime() - b.startsAt.getTime());
|
|
537
|
+
return groupOverlappingBookings(sorted).flatMap(assignColumns);
|
|
538
|
+
}
|
|
539
|
+
function minutesFromDayStart(date, dayStart) {
|
|
540
|
+
return (date.getTime() - dayStart.getTime()) / 6e4;
|
|
541
|
+
}
|
|
542
|
+
function toVisiblePercent(params) {
|
|
543
|
+
const visibleMinutes = (params.endHour - params.startHour) * 60;
|
|
544
|
+
const minutesSinceVisibleStart = params.minutesSinceDayStart - params.startHour * 60;
|
|
545
|
+
return Math.max(0, Math.min(100, minutesSinceVisibleStart / visibleMinutes * 100));
|
|
546
|
+
}
|
|
547
|
+
function startOfDay(date) {
|
|
548
|
+
const start = new Date(date);
|
|
549
|
+
start.setHours(0, 0, 0, 0);
|
|
550
|
+
return start;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// src/components/AgendaGrid.tsx
|
|
554
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
555
|
+
function buildHours(startHour, endHour) {
|
|
556
|
+
return Array.from({ length: endHour - startHour }, (_hour, index) => startHour + index);
|
|
557
|
+
}
|
|
558
|
+
function bookingsOnDay(bookings, day, startHour, endHour) {
|
|
559
|
+
const dayStart = startOfDay(day);
|
|
560
|
+
const visibleStart = new Date(dayStart.getTime() + startHour * 60 * 6e4);
|
|
561
|
+
const visibleEnd = new Date(dayStart.getTime() + endHour * 60 * 6e4);
|
|
562
|
+
return bookings.filter((booking) => booking.startsAt < visibleEnd && booking.endsAt > visibleStart);
|
|
563
|
+
}
|
|
564
|
+
function AgendaGrid({ bookings, days }) {
|
|
565
|
+
const { locale, agendaStartHour, agendaEndHour } = useSchedulingConfig();
|
|
566
|
+
const messages = resolveSchedulingMessages(locale);
|
|
567
|
+
const hours = buildHours(agendaStartHour, agendaEndHour);
|
|
568
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700", children: [
|
|
569
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col shrink-0 border-r border-gray-200 dark:border-gray-700 text-xs text-gray-500 dark:text-gray-400", children: [
|
|
570
|
+
/* @__PURE__ */ jsx2("div", { className: "h-8" }),
|
|
571
|
+
hours.map((hour) => /* @__PURE__ */ jsxs("div", { className: "h-16 px-2 pt-1", children: [
|
|
572
|
+
String(hour).padStart(2, "0"),
|
|
573
|
+
":00"
|
|
574
|
+
] }, hour))
|
|
575
|
+
] }),
|
|
576
|
+
days.map((day) => {
|
|
577
|
+
const dayBookings = bookingsOnDay(bookings, day, agendaStartHour, agendaEndHour);
|
|
578
|
+
const positioned = layoutDayBookings(dayBookings);
|
|
579
|
+
const dayStart = startOfDay(day);
|
|
580
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-[160px] border-r border-gray-200 dark:border-gray-700 last:border-r-0", children: [
|
|
581
|
+
/* @__PURE__ */ jsx2("div", { className: "h-8 px-2 flex items-center text-xs font-medium text-gray-700 dark:text-gray-300 border-b border-gray-200 dark:border-gray-700", children: day.toLocaleDateString(locale, { weekday: "short", day: "2-digit", month: "2-digit" }) }),
|
|
582
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", style: { height: `${hours.length * 4}rem` }, children: [
|
|
583
|
+
positioned.length === 0 && /* @__PURE__ */ jsx2("p", { className: "absolute inset-x-2 top-2 text-xs text-gray-400", children: messages["agenda.empty"] }),
|
|
584
|
+
positioned.map(({ booking, columnIndex, columnCount }) => {
|
|
585
|
+
const top = toVisiblePercent({
|
|
586
|
+
minutesSinceDayStart: minutesFromDayStart(booking.startsAt, dayStart),
|
|
587
|
+
startHour: agendaStartHour,
|
|
588
|
+
endHour: agendaEndHour
|
|
589
|
+
});
|
|
590
|
+
const bottom = toVisiblePercent({
|
|
591
|
+
minutesSinceDayStart: minutesFromDayStart(booking.endsAt, dayStart),
|
|
592
|
+
startHour: agendaStartHour,
|
|
593
|
+
endHour: agendaEndHour
|
|
594
|
+
});
|
|
595
|
+
const width = 100 / columnCount;
|
|
596
|
+
return /* @__PURE__ */ jsx2(
|
|
597
|
+
"div",
|
|
598
|
+
{
|
|
599
|
+
title: booking.title,
|
|
600
|
+
className: "absolute rounded-md bg-brand-100 dark:bg-brand-900/40 border border-brand-300 dark:border-brand-700 px-1.5 py-0.5 text-xs text-brand-900 dark:text-brand-100 overflow-hidden",
|
|
601
|
+
style: {
|
|
602
|
+
top: `${top}%`,
|
|
603
|
+
height: `${Math.max(bottom - top, 2)}%`,
|
|
604
|
+
left: `${columnIndex * width}%`,
|
|
605
|
+
width: `${width}%`
|
|
606
|
+
},
|
|
607
|
+
children: booking.title
|
|
608
|
+
},
|
|
609
|
+
booking.id
|
|
610
|
+
);
|
|
611
|
+
})
|
|
612
|
+
] })
|
|
613
|
+
] }, day.toISOString());
|
|
614
|
+
})
|
|
615
|
+
] });
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// src/workspace/AgendaArea.tsx
|
|
619
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
620
|
+
var DAY_IN_MS = 24 * 60 * 6e4;
|
|
621
|
+
function startOfWeek(date, weekStartsOn) {
|
|
622
|
+
const start = startOfDay(date);
|
|
623
|
+
const offset = (start.getDay() - weekStartsOn + 7) % 7;
|
|
624
|
+
return new Date(start.getTime() - offset * DAY_IN_MS);
|
|
625
|
+
}
|
|
626
|
+
function buildVisibleDays(anchorDate, view, weekStartsOn) {
|
|
627
|
+
if (view === "day") return [startOfDay(anchorDate)];
|
|
628
|
+
const weekStart = startOfWeek(anchorDate, weekStartsOn);
|
|
629
|
+
return Array.from({ length: 7 }, (_day, index) => new Date(weekStart.getTime() + index * DAY_IN_MS));
|
|
630
|
+
}
|
|
631
|
+
var NAV_BUTTON_CLASS = "min-h-11 px-3 rounded-lg text-sm font-medium border border-gray-300 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800";
|
|
632
|
+
function AgendaArea() {
|
|
633
|
+
const { locale, weekStartsOn } = useSchedulingConfig();
|
|
634
|
+
const messages = resolveSchedulingMessages(locale);
|
|
635
|
+
const { data: resourcesPage } = useResources({ active: true, pageSize: MAX_PAGE_SIZE });
|
|
636
|
+
const [resourceId, setResourceId] = useState("");
|
|
637
|
+
const [view, setView] = useState("day");
|
|
638
|
+
const [anchorDate, setAnchorDate] = useState(() => /* @__PURE__ */ new Date());
|
|
639
|
+
const resources = resourcesPage?.data ?? [];
|
|
640
|
+
const days = buildVisibleDays(anchorDate, view, weekStartsOn);
|
|
641
|
+
const from = days[0];
|
|
642
|
+
const until = new Date(days[days.length - 1].getTime() + DAY_IN_MS);
|
|
643
|
+
const { data: bookingsPage, isLoading, isError } = useBookings(
|
|
644
|
+
resourceId ? { resourceId, from, until, pageSize: MAX_PAGE_SIZE } : { from, until, pageSize: MAX_PAGE_SIZE }
|
|
645
|
+
);
|
|
646
|
+
function shiftAnchor(days_) {
|
|
647
|
+
setAnchorDate((current) => new Date(current.getTime() + days_ * DAY_IN_MS));
|
|
648
|
+
}
|
|
649
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col p-4 space-y-4", children: [
|
|
650
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
651
|
+
/* @__PURE__ */ jsxs2("label", { className: "flex items-center gap-2 text-sm", children: [
|
|
652
|
+
/* @__PURE__ */ jsx3("span", { className: "font-medium text-gray-700 dark:text-gray-300", children: messages["agenda.resourceLabel"] }),
|
|
653
|
+
/* @__PURE__ */ jsxs2(
|
|
654
|
+
"select",
|
|
655
|
+
{
|
|
656
|
+
value: resourceId,
|
|
657
|
+
onChange: (event) => setResourceId(event.target.value),
|
|
658
|
+
className: "min-h-11 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 text-sm",
|
|
659
|
+
children: [
|
|
660
|
+
/* @__PURE__ */ jsx3("option", { value: "", children: "\u2014" }),
|
|
661
|
+
resources.map((resource) => /* @__PURE__ */ jsx3("option", { value: resource.id, children: resource.name }, resource.id))
|
|
662
|
+
]
|
|
663
|
+
}
|
|
664
|
+
)
|
|
665
|
+
] }),
|
|
666
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1 ml-auto", children: [
|
|
667
|
+
/* @__PURE__ */ jsx3("button", { type: "button", onClick: () => shiftAnchor(view === "day" ? -1 : -7), className: NAV_BUTTON_CLASS, children: messages["agenda.previous"] }),
|
|
668
|
+
/* @__PURE__ */ jsx3("button", { type: "button", onClick: () => setAnchorDate(/* @__PURE__ */ new Date()), className: NAV_BUTTON_CLASS, children: messages["agenda.today"] }),
|
|
669
|
+
/* @__PURE__ */ jsx3("button", { type: "button", onClick: () => shiftAnchor(view === "day" ? 1 : 7), className: NAV_BUTTON_CLASS, children: messages["agenda.next"] })
|
|
670
|
+
] }),
|
|
671
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1", children: [
|
|
672
|
+
/* @__PURE__ */ jsx3(
|
|
673
|
+
"button",
|
|
674
|
+
{
|
|
675
|
+
type: "button",
|
|
676
|
+
onClick: () => setView("day"),
|
|
677
|
+
"aria-current": view === "day" ? "true" : void 0,
|
|
678
|
+
className: `${NAV_BUTTON_CLASS} ${view === "day" ? "bg-brand-600 text-white border-brand-600" : ""}`,
|
|
679
|
+
children: messages["agenda.viewDay"]
|
|
680
|
+
}
|
|
681
|
+
),
|
|
682
|
+
/* @__PURE__ */ jsx3(
|
|
683
|
+
"button",
|
|
684
|
+
{
|
|
685
|
+
type: "button",
|
|
686
|
+
onClick: () => setView("week"),
|
|
687
|
+
"aria-current": view === "week" ? "true" : void 0,
|
|
688
|
+
className: `${NAV_BUTTON_CLASS} ${view === "week" ? "bg-brand-600 text-white border-brand-600" : ""}`,
|
|
689
|
+
children: messages["agenda.viewWeek"]
|
|
690
|
+
}
|
|
691
|
+
)
|
|
692
|
+
] })
|
|
693
|
+
] }),
|
|
694
|
+
isError && /* @__PURE__ */ jsx3("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.loadFailure"] }),
|
|
695
|
+
isLoading ? /* @__PURE__ */ jsx3("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.loading"] }) : /* @__PURE__ */ jsx3(AgendaGrid, { bookings: bookingsPage?.data ?? [], days })
|
|
696
|
+
] });
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// src/workspace/AvailabilityArea.tsx
|
|
700
|
+
import { useState as useState4 } from "react";
|
|
701
|
+
import { MAX_PAGE_SIZE as MAX_PAGE_SIZE2 } from "@adatechnology/scheduling-contracts";
|
|
702
|
+
|
|
703
|
+
// src/components/WeeklyRulesEditor.tsx
|
|
704
|
+
import { Plus, Trash2 } from "lucide-react";
|
|
705
|
+
import { useState as useState2 } from "react";
|
|
706
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
707
|
+
var WEEKDAYS = [0, 1, 2, 3, 4, 5, 6];
|
|
708
|
+
function toDraft(rule) {
|
|
709
|
+
return { weekday: rule.weekday, startsAtLocal: rule.startsAtLocal, endsAtLocal: rule.endsAtLocal };
|
|
710
|
+
}
|
|
711
|
+
function createEmptyDraft() {
|
|
712
|
+
return { weekday: 1, startsAtLocal: "09:00", endsAtLocal: "18:00" };
|
|
713
|
+
}
|
|
714
|
+
function WeeklyRulesEditor({ resourceId }) {
|
|
715
|
+
const { locale } = useSchedulingConfig();
|
|
716
|
+
const messages = resolveSchedulingMessages(locale);
|
|
717
|
+
const { data, isSuccess: isRulesLoaded, isError: isRulesLoadError } = useAvailabilityRules(resourceId);
|
|
718
|
+
const setAvailabilityRules = useSetAvailabilityRules();
|
|
719
|
+
const [draft, setDraft] = useState2(void 0);
|
|
720
|
+
const rules = draft ?? (data ? data.map(toDraft) : []);
|
|
721
|
+
function updateRule(index, patch) {
|
|
722
|
+
setDraft(rules.map((rule, ruleIndex) => ruleIndex === index ? { ...rule, ...patch } : rule));
|
|
723
|
+
}
|
|
724
|
+
function removeRule(index) {
|
|
725
|
+
setDraft(rules.filter((_rule, ruleIndex) => ruleIndex !== index));
|
|
726
|
+
}
|
|
727
|
+
function addRule() {
|
|
728
|
+
setDraft([...rules, createEmptyDraft()]);
|
|
729
|
+
}
|
|
730
|
+
async function handleSave() {
|
|
731
|
+
try {
|
|
732
|
+
await setAvailabilityRules.mutateAsync({
|
|
733
|
+
resourceId,
|
|
734
|
+
rules: rules.map((rule) => ({ ...rule, resourceId }))
|
|
735
|
+
});
|
|
736
|
+
setDraft(void 0);
|
|
737
|
+
} catch {
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
if (isRulesLoadError) {
|
|
741
|
+
return /* @__PURE__ */ jsxs3("section", { className: "space-y-3", children: [
|
|
742
|
+
/* @__PURE__ */ jsx4("h3", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: messages["availability.weeklyRulesTitle"] }),
|
|
743
|
+
/* @__PURE__ */ jsx4("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.loadFailure"] })
|
|
744
|
+
] });
|
|
745
|
+
}
|
|
746
|
+
if (!isRulesLoaded) {
|
|
747
|
+
return /* @__PURE__ */ jsxs3("section", { className: "space-y-3", children: [
|
|
748
|
+
/* @__PURE__ */ jsx4("h3", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: messages["availability.weeklyRulesTitle"] }),
|
|
749
|
+
/* @__PURE__ */ jsx4("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.loading"] })
|
|
750
|
+
] });
|
|
751
|
+
}
|
|
752
|
+
return /* @__PURE__ */ jsxs3("section", { className: "space-y-3", children: [
|
|
753
|
+
/* @__PURE__ */ jsx4("h3", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: messages["availability.weeklyRulesTitle"] }),
|
|
754
|
+
setAvailabilityRules.isError && /* @__PURE__ */ jsx4("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.actionFailure"] }),
|
|
755
|
+
/* @__PURE__ */ jsx4("ul", { className: "space-y-2", children: rules.map((rule, index) => /* @__PURE__ */ jsxs3("li", { className: "flex items-center gap-2", children: [
|
|
756
|
+
/* @__PURE__ */ jsx4(
|
|
757
|
+
"select",
|
|
758
|
+
{
|
|
759
|
+
"aria-label": messages["availability.weekdayLabel"],
|
|
760
|
+
value: rule.weekday,
|
|
761
|
+
onChange: (event) => updateRule(index, { weekday: Number(event.target.value) }),
|
|
762
|
+
className: "min-h-11 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 text-sm",
|
|
763
|
+
children: WEEKDAYS.map((weekday) => /* @__PURE__ */ jsx4("option", { value: weekday, children: messages[`availability.weekday.${weekday}`] }, weekday))
|
|
764
|
+
}
|
|
765
|
+
),
|
|
766
|
+
/* @__PURE__ */ jsx4(
|
|
767
|
+
"input",
|
|
768
|
+
{
|
|
769
|
+
"aria-label": messages["availability.startsAtLocal"],
|
|
770
|
+
type: "time",
|
|
771
|
+
value: rule.startsAtLocal,
|
|
772
|
+
onChange: (event) => updateRule(index, { startsAtLocal: event.target.value }),
|
|
773
|
+
className: "min-h-11 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 text-sm"
|
|
774
|
+
}
|
|
775
|
+
),
|
|
776
|
+
/* @__PURE__ */ jsx4(
|
|
777
|
+
"input",
|
|
778
|
+
{
|
|
779
|
+
"aria-label": messages["availability.endsAtLocal"],
|
|
780
|
+
type: "time",
|
|
781
|
+
value: rule.endsAtLocal,
|
|
782
|
+
onChange: (event) => updateRule(index, { endsAtLocal: event.target.value }),
|
|
783
|
+
className: "min-h-11 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 text-sm"
|
|
784
|
+
}
|
|
785
|
+
),
|
|
786
|
+
/* @__PURE__ */ jsx4(
|
|
787
|
+
"button",
|
|
788
|
+
{
|
|
789
|
+
type: "button",
|
|
790
|
+
onClick: () => removeRule(index),
|
|
791
|
+
"aria-label": messages["availability.removeRule"],
|
|
792
|
+
className: "min-h-11 min-w-11 inline-flex items-center justify-center rounded-lg text-red-700 hover:bg-red-50",
|
|
793
|
+
children: /* @__PURE__ */ jsx4(Trash2, { "aria-hidden": "true", className: "w-4 h-4" })
|
|
794
|
+
}
|
|
795
|
+
)
|
|
796
|
+
] }, index)) }),
|
|
797
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
|
|
798
|
+
/* @__PURE__ */ jsxs3(
|
|
799
|
+
"button",
|
|
800
|
+
{
|
|
801
|
+
type: "button",
|
|
802
|
+
onClick: addRule,
|
|
803
|
+
className: "inline-flex items-center gap-2 min-h-11 px-3 py-2 rounded-lg text-sm font-medium border border-gray-300 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800",
|
|
804
|
+
children: [
|
|
805
|
+
/* @__PURE__ */ jsx4(Plus, { "aria-hidden": "true", className: "w-4 h-4" }),
|
|
806
|
+
messages["availability.addRule"]
|
|
807
|
+
]
|
|
808
|
+
}
|
|
809
|
+
),
|
|
810
|
+
/* @__PURE__ */ jsx4(
|
|
811
|
+
"button",
|
|
812
|
+
{
|
|
813
|
+
type: "button",
|
|
814
|
+
onClick: handleSave,
|
|
815
|
+
disabled: setAvailabilityRules.isPending,
|
|
816
|
+
className: "min-h-11 px-3 py-2 rounded-lg text-sm font-medium bg-brand-600 text-white hover:bg-brand-700 disabled:opacity-50",
|
|
817
|
+
children: messages["common.save"]
|
|
818
|
+
}
|
|
819
|
+
)
|
|
820
|
+
] })
|
|
821
|
+
] });
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/components/AvailabilityExceptionsEditor.tsx
|
|
825
|
+
import { Plus as Plus2, Trash2 as Trash22 } from "lucide-react";
|
|
826
|
+
import { useState as useState3 } from "react";
|
|
827
|
+
import { AVAILABILITY_EXCEPTION_KIND } from "@adatechnology/scheduling-contracts";
|
|
828
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
829
|
+
var SELECT_CLASS = "min-h-11 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 text-sm";
|
|
830
|
+
function AvailabilityExceptionsEditor({ resourceId }) {
|
|
831
|
+
const { locale } = useSchedulingConfig();
|
|
832
|
+
const messages = resolveSchedulingMessages(locale);
|
|
833
|
+
const { data, isLoading, isError: isLoadError } = useAvailabilityExceptions(resourceId);
|
|
834
|
+
const addException = useAddAvailabilityException();
|
|
835
|
+
const removeException = useRemoveAvailabilityException();
|
|
836
|
+
const [from, setFrom] = useState3("");
|
|
837
|
+
const [until, setUntil] = useState3("");
|
|
838
|
+
const [kind, setKind] = useState3(AVAILABILITY_EXCEPTION_KIND.BLOCK);
|
|
839
|
+
const [reason, setReason] = useState3("");
|
|
840
|
+
async function handleAdd() {
|
|
841
|
+
if (!from || !until) return;
|
|
842
|
+
try {
|
|
843
|
+
await addException.mutateAsync({
|
|
844
|
+
resourceId,
|
|
845
|
+
during: { start: new Date(from), end: new Date(until) },
|
|
846
|
+
kind,
|
|
847
|
+
...reason ? { reason } : {}
|
|
848
|
+
});
|
|
849
|
+
setFrom("");
|
|
850
|
+
setUntil("");
|
|
851
|
+
setReason("");
|
|
852
|
+
} catch {
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return /* @__PURE__ */ jsxs4("section", { className: "space-y-3", children: [
|
|
856
|
+
/* @__PURE__ */ jsx5("h3", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: messages["availability.exceptionsTitle"] }),
|
|
857
|
+
(addException.isError || removeException.isError) && /* @__PURE__ */ jsx5("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.actionFailure"] }),
|
|
858
|
+
isLoadError && /* @__PURE__ */ jsx5("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.loadFailure"] }),
|
|
859
|
+
isLoading && /* @__PURE__ */ jsx5("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.loading"] }),
|
|
860
|
+
/* @__PURE__ */ jsx5("ul", { className: "space-y-2", children: (data ?? []).map((exception) => /* @__PURE__ */ jsxs4("li", { className: "flex items-center gap-2 text-sm", children: [
|
|
861
|
+
/* @__PURE__ */ jsxs4("span", { className: "flex-1", children: [
|
|
862
|
+
messages[`availability.exceptionKind.${exception.kind === "block" ? "blocked" : "extra"}`],
|
|
863
|
+
" \u2014",
|
|
864
|
+
" ",
|
|
865
|
+
exception.during.start.toLocaleString(locale),
|
|
866
|
+
" \u2192 ",
|
|
867
|
+
exception.during.end.toLocaleString(locale),
|
|
868
|
+
exception.reason ? ` (${exception.reason})` : ""
|
|
869
|
+
] }),
|
|
870
|
+
/* @__PURE__ */ jsx5(
|
|
871
|
+
"button",
|
|
872
|
+
{
|
|
873
|
+
type: "button",
|
|
874
|
+
onClick: () => removeException.mutate({ id: exception.id, resourceId }),
|
|
875
|
+
"aria-label": messages["availability.removeException"],
|
|
876
|
+
className: "min-h-11 min-w-11 inline-flex items-center justify-center rounded-lg text-red-700 hover:bg-red-50",
|
|
877
|
+
children: /* @__PURE__ */ jsx5(Trash22, { "aria-hidden": "true", className: "w-4 h-4" })
|
|
878
|
+
}
|
|
879
|
+
)
|
|
880
|
+
] }, exception.id)) }),
|
|
881
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
882
|
+
/* @__PURE__ */ jsx5(
|
|
883
|
+
"input",
|
|
884
|
+
{
|
|
885
|
+
"aria-label": messages["availability.exceptionFrom"],
|
|
886
|
+
type: "datetime-local",
|
|
887
|
+
value: from,
|
|
888
|
+
onChange: (event) => setFrom(event.target.value),
|
|
889
|
+
className: SELECT_CLASS
|
|
890
|
+
}
|
|
891
|
+
),
|
|
892
|
+
/* @__PURE__ */ jsx5(
|
|
893
|
+
"input",
|
|
894
|
+
{
|
|
895
|
+
"aria-label": messages["availability.exceptionUntil"],
|
|
896
|
+
type: "datetime-local",
|
|
897
|
+
value: until,
|
|
898
|
+
onChange: (event) => setUntil(event.target.value),
|
|
899
|
+
className: SELECT_CLASS
|
|
900
|
+
}
|
|
901
|
+
),
|
|
902
|
+
/* @__PURE__ */ jsxs4(
|
|
903
|
+
"select",
|
|
904
|
+
{
|
|
905
|
+
"aria-label": messages["availability.exceptionKind.blocked"],
|
|
906
|
+
value: kind,
|
|
907
|
+
onChange: (event) => setKind(event.target.value),
|
|
908
|
+
className: SELECT_CLASS,
|
|
909
|
+
children: [
|
|
910
|
+
/* @__PURE__ */ jsx5("option", { value: AVAILABILITY_EXCEPTION_KIND.BLOCK, children: messages["availability.exceptionKind.blocked"] }),
|
|
911
|
+
/* @__PURE__ */ jsx5("option", { value: AVAILABILITY_EXCEPTION_KIND.EXTRA, children: messages["availability.exceptionKind.extra"] })
|
|
912
|
+
]
|
|
913
|
+
}
|
|
914
|
+
),
|
|
915
|
+
/* @__PURE__ */ jsx5(
|
|
916
|
+
"input",
|
|
917
|
+
{
|
|
918
|
+
"aria-label": messages["availability.exceptionReason"],
|
|
919
|
+
type: "text",
|
|
920
|
+
placeholder: messages["availability.exceptionReason"],
|
|
921
|
+
value: reason,
|
|
922
|
+
onChange: (event) => setReason(event.target.value),
|
|
923
|
+
className: SELECT_CLASS
|
|
924
|
+
}
|
|
925
|
+
),
|
|
926
|
+
/* @__PURE__ */ jsxs4(
|
|
927
|
+
"button",
|
|
928
|
+
{
|
|
929
|
+
type: "button",
|
|
930
|
+
onClick: handleAdd,
|
|
931
|
+
disabled: addException.isPending,
|
|
932
|
+
className: "inline-flex items-center gap-2 min-h-11 px-3 py-2 rounded-lg text-sm font-medium bg-brand-600 text-white hover:bg-brand-700 disabled:opacity-50",
|
|
933
|
+
children: [
|
|
934
|
+
/* @__PURE__ */ jsx5(Plus2, { "aria-hidden": "true", className: "w-4 h-4" }),
|
|
935
|
+
messages["availability.addException"]
|
|
936
|
+
]
|
|
937
|
+
}
|
|
938
|
+
)
|
|
939
|
+
] })
|
|
940
|
+
] });
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// src/components/AvailabilityEditor.tsx
|
|
944
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
945
|
+
function AvailabilityEditor({ resourceId, timezone }) {
|
|
946
|
+
const { locale } = useSchedulingConfig();
|
|
947
|
+
const messages = resolveSchedulingMessages(locale);
|
|
948
|
+
return /* @__PURE__ */ jsxs5("div", { className: "space-y-6", children: [
|
|
949
|
+
/* @__PURE__ */ jsxs5("p", { className: "inline-flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400", children: [
|
|
950
|
+
/* @__PURE__ */ jsxs5("span", { className: "font-medium", children: [
|
|
951
|
+
messages["availability.resourceTimezone"],
|
|
952
|
+
":"
|
|
953
|
+
] }),
|
|
954
|
+
timezone
|
|
955
|
+
] }),
|
|
956
|
+
/* @__PURE__ */ jsx6(WeeklyRulesEditor, { resourceId }),
|
|
957
|
+
/* @__PURE__ */ jsx6(AvailabilityExceptionsEditor, { resourceId })
|
|
958
|
+
] });
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// src/workspace/AvailabilityArea.tsx
|
|
962
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
963
|
+
function AvailabilityArea() {
|
|
964
|
+
const { locale } = useSchedulingConfig();
|
|
965
|
+
const messages = resolveSchedulingMessages(locale);
|
|
966
|
+
const { data, isLoading, isError } = useResources({ active: true, pageSize: MAX_PAGE_SIZE2 });
|
|
967
|
+
const [resourceId, setResourceId] = useState4("");
|
|
968
|
+
const resources = data?.data ?? [];
|
|
969
|
+
const selectedResource = resources.find((resource) => resource.id === resourceId);
|
|
970
|
+
return /* @__PURE__ */ jsxs6("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col p-4 space-y-4", children: [
|
|
971
|
+
/* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-2 text-sm", children: [
|
|
972
|
+
/* @__PURE__ */ jsx7("span", { className: "font-medium text-gray-700 dark:text-gray-300", children: messages["agenda.resourceLabel"] }),
|
|
973
|
+
/* @__PURE__ */ jsxs6(
|
|
974
|
+
"select",
|
|
975
|
+
{
|
|
976
|
+
value: resourceId,
|
|
977
|
+
onChange: (event) => setResourceId(event.target.value),
|
|
978
|
+
className: "min-h-11 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 text-sm",
|
|
979
|
+
children: [
|
|
980
|
+
/* @__PURE__ */ jsx7("option", { value: "", children: "\u2014" }),
|
|
981
|
+
resources.map((resource) => /* @__PURE__ */ jsx7("option", { value: resource.id, children: resource.name }, resource.id))
|
|
982
|
+
]
|
|
983
|
+
}
|
|
984
|
+
)
|
|
985
|
+
] }),
|
|
986
|
+
isError && /* @__PURE__ */ jsx7("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.loadFailure"] }),
|
|
987
|
+
isLoading && /* @__PURE__ */ jsx7("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.loading"] }),
|
|
988
|
+
selectedResource && /* @__PURE__ */ jsx7(
|
|
989
|
+
AvailabilityEditor,
|
|
990
|
+
{
|
|
991
|
+
resourceId: selectedResource.id,
|
|
992
|
+
timezone: selectedResource.timezone
|
|
993
|
+
},
|
|
994
|
+
selectedResource.id
|
|
995
|
+
)
|
|
996
|
+
] });
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// src/workspace/BookingsArea.tsx
|
|
1000
|
+
import { useState as useState8 } from "react";
|
|
1001
|
+
|
|
1002
|
+
// src/components/BookingDrawer.tsx
|
|
1003
|
+
import { XCircle } from "lucide-react";
|
|
1004
|
+
import { useState as useState5 } from "react";
|
|
1005
|
+
import { BOOKING_STATUS } from "@adatechnology/scheduling-contracts";
|
|
1006
|
+
|
|
1007
|
+
// src/components/SidePanel.tsx
|
|
1008
|
+
import { X } from "lucide-react";
|
|
1009
|
+
import { useEffect } from "react";
|
|
1010
|
+
import { Fragment, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1011
|
+
var BUTTON_CLASS = "inline-flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-colors min-h-11";
|
|
1012
|
+
function SidePanel({ title, closeLabel, onClose, headerActions, children }) {
|
|
1013
|
+
useEffect(() => {
|
|
1014
|
+
function handleKeyDown(event) {
|
|
1015
|
+
if (event.key === "Escape") onClose();
|
|
1016
|
+
}
|
|
1017
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
1018
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1019
|
+
}, [onClose]);
|
|
1020
|
+
return /* @__PURE__ */ jsxs7(Fragment, { children: [
|
|
1021
|
+
/* @__PURE__ */ jsx8(
|
|
1022
|
+
"button",
|
|
1023
|
+
{
|
|
1024
|
+
type: "button",
|
|
1025
|
+
"aria-hidden": "true",
|
|
1026
|
+
tabIndex: -1,
|
|
1027
|
+
onClick: onClose,
|
|
1028
|
+
className: "absolute inset-0 z-10 bg-gray-900/20 wide:hidden"
|
|
1029
|
+
}
|
|
1030
|
+
),
|
|
1031
|
+
/* @__PURE__ */ jsxs7(
|
|
1032
|
+
"section",
|
|
1033
|
+
{
|
|
1034
|
+
"aria-label": title,
|
|
1035
|
+
className: "absolute inset-y-0 right-0 z-20 flex w-full max-w-full flex-col bg-white shadow-2xl desktop:w-[28rem] dark:bg-gray-900 wide:static wide:z-auto wide:shrink-0 wide:border-l wide:border-gray-200 wide:shadow-none wide:dark:border-gray-700",
|
|
1036
|
+
children: [
|
|
1037
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2 border-b border-gray-200 px-4 py-3 dark:border-gray-700", children: [
|
|
1038
|
+
/* @__PURE__ */ jsx8("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100 mr-auto", children: title }),
|
|
1039
|
+
headerActions,
|
|
1040
|
+
/* @__PURE__ */ jsxs7(
|
|
1041
|
+
"button",
|
|
1042
|
+
{
|
|
1043
|
+
type: "button",
|
|
1044
|
+
onClick: onClose,
|
|
1045
|
+
className: `${BUTTON_CLASS} text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800`,
|
|
1046
|
+
children: [
|
|
1047
|
+
/* @__PURE__ */ jsx8(X, { "aria-hidden": "true", className: "w-4 h-4" }),
|
|
1048
|
+
closeLabel
|
|
1049
|
+
]
|
|
1050
|
+
}
|
|
1051
|
+
)
|
|
1052
|
+
] }),
|
|
1053
|
+
/* @__PURE__ */ jsx8("div", { className: "min-h-0 flex-1 overflow-y-auto p-4", children })
|
|
1054
|
+
]
|
|
1055
|
+
}
|
|
1056
|
+
)
|
|
1057
|
+
] });
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// src/components/BookingDrawer.tsx
|
|
1061
|
+
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1062
|
+
var ACTION_BUTTON_CLASS = "min-h-11 px-3 rounded-lg text-sm font-medium border border-gray-300 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800";
|
|
1063
|
+
var PRIMARY_BUTTON_CLASS = "min-h-11 px-3 rounded-lg text-sm font-medium bg-brand-600 text-white hover:bg-brand-700";
|
|
1064
|
+
var DANGER_BUTTON_CLASS = "inline-flex items-center gap-2 min-h-11 px-3 rounded-lg text-sm font-medium text-red-700 hover:bg-red-50";
|
|
1065
|
+
var INPUT_CLASS = "min-h-11 w-full px-3 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-sm";
|
|
1066
|
+
function toLocalInputValue(date) {
|
|
1067
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
1068
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
1069
|
+
}
|
|
1070
|
+
function BookingDrawer({ booking, onClose }) {
|
|
1071
|
+
const { locale } = useSchedulingConfig();
|
|
1072
|
+
const messages = resolveSchedulingMessages(locale);
|
|
1073
|
+
const confirmBooking = useConfirmBooking();
|
|
1074
|
+
const completeBooking = useCompleteBooking();
|
|
1075
|
+
const markNoShow = useMarkNoShow();
|
|
1076
|
+
const cancelBooking = useCancelBooking();
|
|
1077
|
+
const rescheduleBooking = useRescheduleBooking();
|
|
1078
|
+
const [isCancelling, setIsCancelling] = useState5(false);
|
|
1079
|
+
const [cancelledBy, setCancelledBy] = useState5("");
|
|
1080
|
+
const [cancellationReason, setCancellationReason] = useState5("");
|
|
1081
|
+
const [isRescheduling, setIsRescheduling] = useState5(false);
|
|
1082
|
+
const [start, setStart] = useState5(() => toLocalInputValue(booking.startsAt));
|
|
1083
|
+
const [end, setEnd] = useState5(() => toLocalInputValue(booking.endsAt));
|
|
1084
|
+
async function handleCancel() {
|
|
1085
|
+
if (!cancelledBy) return;
|
|
1086
|
+
try {
|
|
1087
|
+
await cancelBooking.mutateAsync({
|
|
1088
|
+
id: booking.id,
|
|
1089
|
+
input: { cancelledBy, ...cancellationReason ? { cancellationReason } : {} }
|
|
1090
|
+
});
|
|
1091
|
+
onClose();
|
|
1092
|
+
} catch {
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
async function handleReschedule() {
|
|
1096
|
+
try {
|
|
1097
|
+
await rescheduleBooking.mutateAsync({ id: booking.id, input: { during: { start: new Date(start), end: new Date(end) } } });
|
|
1098
|
+
setIsRescheduling(false);
|
|
1099
|
+
} catch {
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
const isTerminal = booking.status === BOOKING_STATUS.CANCELLED || booking.status === BOOKING_STATUS.COMPLETED || booking.status === BOOKING_STATUS.NO_SHOW;
|
|
1103
|
+
return /* @__PURE__ */ jsx9(SidePanel, { title: messages["booking.detailTitle"], closeLabel: messages["common.close"], onClose, children: /* @__PURE__ */ jsxs8("div", { className: "space-y-4", children: [
|
|
1104
|
+
/* @__PURE__ */ jsxs8("div", { children: [
|
|
1105
|
+
/* @__PURE__ */ jsx9("p", { className: "text-base font-semibold text-gray-900 dark:text-gray-100", children: booking.title }),
|
|
1106
|
+
/* @__PURE__ */ jsx9("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages[`booking.status.${booking.status === "no_show" ? "noShow" : booking.status}`] }),
|
|
1107
|
+
/* @__PURE__ */ jsxs8("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: [
|
|
1108
|
+
booking.startsAt.toLocaleString(locale),
|
|
1109
|
+
" \u2192 ",
|
|
1110
|
+
booking.endsAt.toLocaleString(locale)
|
|
1111
|
+
] })
|
|
1112
|
+
] }),
|
|
1113
|
+
(confirmBooking.isError || completeBooking.isError || markNoShow.isError || cancelBooking.isError || rescheduleBooking.isError) && /* @__PURE__ */ jsx9("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.actionFailure"] }),
|
|
1114
|
+
!isTerminal && /* @__PURE__ */ jsxs8("div", { className: "flex flex-wrap gap-2", children: [
|
|
1115
|
+
booking.status === BOOKING_STATUS.REQUESTED && /* @__PURE__ */ jsx9("button", { type: "button", onClick: () => confirmBooking.mutate(booking.id), className: PRIMARY_BUTTON_CLASS, children: messages["booking.confirm"] }),
|
|
1116
|
+
booking.status === BOOKING_STATUS.CONFIRMED && /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
1117
|
+
/* @__PURE__ */ jsx9("button", { type: "button", onClick: () => completeBooking.mutate(booking.id), className: ACTION_BUTTON_CLASS, children: messages["booking.complete"] }),
|
|
1118
|
+
/* @__PURE__ */ jsx9("button", { type: "button", onClick: () => markNoShow.mutate(booking.id), className: ACTION_BUTTON_CLASS, children: messages["booking.markNoShow"] }),
|
|
1119
|
+
/* @__PURE__ */ jsx9("button", { type: "button", onClick: () => setIsRescheduling(true), className: ACTION_BUTTON_CLASS, children: messages["booking.reschedule"] })
|
|
1120
|
+
] }),
|
|
1121
|
+
/* @__PURE__ */ jsxs8("button", { type: "button", onClick: () => setIsCancelling(true), className: DANGER_BUTTON_CLASS, children: [
|
|
1122
|
+
/* @__PURE__ */ jsx9(XCircle, { "aria-hidden": "true", className: "w-4 h-4" }),
|
|
1123
|
+
messages["booking.cancel"]
|
|
1124
|
+
] })
|
|
1125
|
+
] }),
|
|
1126
|
+
isRescheduling && /* @__PURE__ */ jsxs8("div", { className: "space-y-2 rounded-lg border border-gray-200 dark:border-gray-700 p-3", children: [
|
|
1127
|
+
/* @__PURE__ */ jsx9("input", { type: "datetime-local", value: start, onChange: (event) => setStart(event.target.value), className: INPUT_CLASS }),
|
|
1128
|
+
/* @__PURE__ */ jsx9("input", { type: "datetime-local", value: end, onChange: (event) => setEnd(event.target.value), className: INPUT_CLASS }),
|
|
1129
|
+
/* @__PURE__ */ jsx9("button", { type: "button", onClick: handleReschedule, disabled: rescheduleBooking.isPending, className: PRIMARY_BUTTON_CLASS, children: messages["common.save"] })
|
|
1130
|
+
] }),
|
|
1131
|
+
isCancelling && /* @__PURE__ */ jsxs8("div", { className: "space-y-2 rounded-lg border border-gray-200 dark:border-gray-700 p-3", children: [
|
|
1132
|
+
/* @__PURE__ */ jsx9(
|
|
1133
|
+
"input",
|
|
1134
|
+
{
|
|
1135
|
+
type: "text",
|
|
1136
|
+
placeholder: messages["booking.cancelledBy"],
|
|
1137
|
+
value: cancelledBy,
|
|
1138
|
+
onChange: (event) => setCancelledBy(event.target.value),
|
|
1139
|
+
className: INPUT_CLASS
|
|
1140
|
+
}
|
|
1141
|
+
),
|
|
1142
|
+
/* @__PURE__ */ jsx9(
|
|
1143
|
+
"input",
|
|
1144
|
+
{
|
|
1145
|
+
type: "text",
|
|
1146
|
+
placeholder: messages["booking.cancelReason"],
|
|
1147
|
+
value: cancellationReason,
|
|
1148
|
+
onChange: (event) => setCancellationReason(event.target.value),
|
|
1149
|
+
className: INPUT_CLASS
|
|
1150
|
+
}
|
|
1151
|
+
),
|
|
1152
|
+
/* @__PURE__ */ jsxs8("button", { type: "button", onClick: handleCancel, disabled: cancelBooking.isPending, className: DANGER_BUTTON_CLASS, children: [
|
|
1153
|
+
/* @__PURE__ */ jsx9(XCircle, { "aria-hidden": "true", className: "w-4 h-4" }),
|
|
1154
|
+
messages["booking.cancel"]
|
|
1155
|
+
] })
|
|
1156
|
+
] })
|
|
1157
|
+
] }) });
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// src/components/BookingsTable.tsx
|
|
1161
|
+
import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, ArrowUpDown } from "lucide-react";
|
|
1162
|
+
import { useState as useState6 } from "react";
|
|
1163
|
+
import { BOOKING_STATUS as BOOKING_STATUS2 } from "@adatechnology/scheduling-contracts";
|
|
1164
|
+
|
|
1165
|
+
// src/components/bookingsTableState.util.ts
|
|
1166
|
+
var DEFAULT_BOOKINGS_TABLE_STATE = {
|
|
1167
|
+
sortDirection: "asc",
|
|
1168
|
+
statusFilters: [],
|
|
1169
|
+
page: 1
|
|
1170
|
+
};
|
|
1171
|
+
var SORT_COLUMNS = ["title", "status", "startsAt", "endsAt"];
|
|
1172
|
+
function isBookingSortColumn(value) {
|
|
1173
|
+
return SORT_COLUMNS.includes(value);
|
|
1174
|
+
}
|
|
1175
|
+
function parsePage(value) {
|
|
1176
|
+
const parsed = Number(value);
|
|
1177
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : 1;
|
|
1178
|
+
}
|
|
1179
|
+
function parseBookingsTableState(search) {
|
|
1180
|
+
const params = new URLSearchParams(search);
|
|
1181
|
+
const sortColumnParam = params.get("sortBy");
|
|
1182
|
+
const sortColumn = isBookingSortColumn(sortColumnParam) ? sortColumnParam : void 0;
|
|
1183
|
+
const sortDirection = params.get("sortDirection") === "desc" ? "desc" : "asc";
|
|
1184
|
+
const statusFilters = params.getAll("status");
|
|
1185
|
+
const page = parsePage(params.get("page"));
|
|
1186
|
+
return { ...sortColumn ? { sortColumn } : {}, sortDirection, statusFilters, page };
|
|
1187
|
+
}
|
|
1188
|
+
function serializeBookingsTableState(state) {
|
|
1189
|
+
const params = new URLSearchParams();
|
|
1190
|
+
if (state.sortColumn) {
|
|
1191
|
+
params.set("sortBy", state.sortColumn);
|
|
1192
|
+
params.set("sortDirection", state.sortDirection);
|
|
1193
|
+
}
|
|
1194
|
+
for (const status of state.statusFilters) params.append("status", status);
|
|
1195
|
+
if (state.page > 1) params.set("page", String(state.page));
|
|
1196
|
+
return params.toString();
|
|
1197
|
+
}
|
|
1198
|
+
function isBookingsTableStateDefault(state) {
|
|
1199
|
+
return state.sortColumn === void 0 && state.statusFilters.length === 0 && state.page === 1;
|
|
1200
|
+
}
|
|
1201
|
+
function filterBookingsByStatus(bookings, statusFilters) {
|
|
1202
|
+
if (statusFilters.length === 0) return bookings;
|
|
1203
|
+
return bookings.filter((booking) => statusFilters.includes(booking.status));
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// src/components/BookingsTable.tsx
|
|
1207
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1208
|
+
var ALL_STATUSES = Object.values(BOOKING_STATUS2);
|
|
1209
|
+
var HEADER_BUTTON_CLASS = "inline-flex items-center gap-1 text-left text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400";
|
|
1210
|
+
var CHECKBOX_CELL_CLASS = "px-3 py-2";
|
|
1211
|
+
function nextSortDirection(column, currentColumn, currentDirection) {
|
|
1212
|
+
if (currentColumn !== column) return "asc";
|
|
1213
|
+
return currentDirection === "asc" ? "desc" : "asc";
|
|
1214
|
+
}
|
|
1215
|
+
function BookingsTable({
|
|
1216
|
+
bookings,
|
|
1217
|
+
state = DEFAULT_BOOKINGS_TABLE_STATE,
|
|
1218
|
+
onStateChange,
|
|
1219
|
+
pagination,
|
|
1220
|
+
onRowClick,
|
|
1221
|
+
bulkActions = []
|
|
1222
|
+
}) {
|
|
1223
|
+
const { locale } = useSchedulingConfig();
|
|
1224
|
+
const messages = resolveSchedulingMessages(locale);
|
|
1225
|
+
const [selected, setSelected] = useState6(/* @__PURE__ */ new Set());
|
|
1226
|
+
const visibleBookings = filterBookingsByStatus(bookings, state.statusFilters);
|
|
1227
|
+
const allSelected = visibleBookings.length > 0 && visibleBookings.every((booking) => selected.has(booking.id));
|
|
1228
|
+
function toggleSort(column) {
|
|
1229
|
+
onStateChange?.({
|
|
1230
|
+
...state,
|
|
1231
|
+
sortColumn: column,
|
|
1232
|
+
sortDirection: nextSortDirection(column, state.sortColumn, state.sortDirection)
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
function toggleStatusFilter(status) {
|
|
1236
|
+
const isActive = state.statusFilters.includes(status);
|
|
1237
|
+
onStateChange?.({
|
|
1238
|
+
...state,
|
|
1239
|
+
statusFilters: isActive ? state.statusFilters.filter((filterStatus) => filterStatus !== status) : [...state.statusFilters, status],
|
|
1240
|
+
page: 1
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
function goToPage(page) {
|
|
1244
|
+
onStateChange?.({ ...state, page });
|
|
1245
|
+
}
|
|
1246
|
+
function toggleSelectAll() {
|
|
1247
|
+
setSelected(allSelected ? /* @__PURE__ */ new Set() : new Set(visibleBookings.map((booking) => booking.id)));
|
|
1248
|
+
}
|
|
1249
|
+
function toggleRowSelected(id) {
|
|
1250
|
+
const next = new Set(selected);
|
|
1251
|
+
if (next.has(id)) next.delete(id);
|
|
1252
|
+
else next.add(id);
|
|
1253
|
+
setSelected(next);
|
|
1254
|
+
}
|
|
1255
|
+
function renderSortIcon(column) {
|
|
1256
|
+
if (state.sortColumn !== column) return /* @__PURE__ */ jsx10(ArrowUpDown, { "aria-hidden": "true", className: "w-3 h-3" });
|
|
1257
|
+
return state.sortDirection === "asc" ? /* @__PURE__ */ jsx10(ArrowUp, { "aria-hidden": "true", className: "w-3 h-3" }) : /* @__PURE__ */ jsx10(ArrowDown, { "aria-hidden": "true", className: "w-3 h-3" });
|
|
1258
|
+
}
|
|
1259
|
+
function renderHeader(column, labelKey) {
|
|
1260
|
+
return /* @__PURE__ */ jsx10("th", { scope: "col", className: "px-3 py-2", children: /* @__PURE__ */ jsxs9("button", { type: "button", onClick: () => toggleSort(column), className: HEADER_BUTTON_CLASS, children: [
|
|
1261
|
+
messages[labelKey],
|
|
1262
|
+
renderSortIcon(column)
|
|
1263
|
+
] }) });
|
|
1264
|
+
}
|
|
1265
|
+
return /* @__PURE__ */ jsxs9("div", { className: "space-y-3", children: [
|
|
1266
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex flex-wrap items-center gap-3", children: [
|
|
1267
|
+
ALL_STATUSES.map((status) => /* @__PURE__ */ jsxs9("label", { className: "flex items-center gap-1.5 text-sm min-h-11", children: [
|
|
1268
|
+
/* @__PURE__ */ jsx10(
|
|
1269
|
+
"input",
|
|
1270
|
+
{
|
|
1271
|
+
type: "checkbox",
|
|
1272
|
+
checked: state.statusFilters.includes(status),
|
|
1273
|
+
onChange: () => toggleStatusFilter(status)
|
|
1274
|
+
}
|
|
1275
|
+
),
|
|
1276
|
+
messages[`booking.status.${status === "no_show" ? "noShow" : status}`]
|
|
1277
|
+
] }, status)),
|
|
1278
|
+
!isBookingsTableStateDefault(state) && /* @__PURE__ */ jsx10(
|
|
1279
|
+
"button",
|
|
1280
|
+
{
|
|
1281
|
+
type: "button",
|
|
1282
|
+
onClick: () => onStateChange?.(DEFAULT_BOOKINGS_TABLE_STATE),
|
|
1283
|
+
className: "min-h-11 px-3 text-sm font-medium text-brand-700 hover:underline",
|
|
1284
|
+
children: messages["common.clearFilters"]
|
|
1285
|
+
}
|
|
1286
|
+
)
|
|
1287
|
+
] }),
|
|
1288
|
+
selected.size > 0 && bulkActions.length > 0 && /* @__PURE__ */ jsx10("div", { className: "flex items-center gap-2 rounded-lg bg-brand-50 dark:bg-brand-900/30 px-3 py-2", children: bulkActions.map((action) => /* @__PURE__ */ jsx10(
|
|
1289
|
+
"button",
|
|
1290
|
+
{
|
|
1291
|
+
type: "button",
|
|
1292
|
+
onClick: () => action.onRun(Array.from(selected)),
|
|
1293
|
+
className: "min-h-11 px-3 rounded-lg text-sm font-medium bg-brand-600 text-white hover:bg-brand-700",
|
|
1294
|
+
children: action.label
|
|
1295
|
+
},
|
|
1296
|
+
action.key
|
|
1297
|
+
)) }),
|
|
1298
|
+
/* @__PURE__ */ jsxs9("div", { className: "overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700", children: [
|
|
1299
|
+
/* @__PURE__ */ jsxs9("table", { className: "min-w-full text-sm", children: [
|
|
1300
|
+
/* @__PURE__ */ jsx10("thead", { className: "border-b border-gray-200 dark:border-gray-700", children: /* @__PURE__ */ jsxs9("tr", { children: [
|
|
1301
|
+
/* @__PURE__ */ jsx10("th", { scope: "col", className: CHECKBOX_CELL_CLASS, children: /* @__PURE__ */ jsx10(
|
|
1302
|
+
"input",
|
|
1303
|
+
{
|
|
1304
|
+
type: "checkbox",
|
|
1305
|
+
"aria-label": messages["common.selectAll"],
|
|
1306
|
+
checked: allSelected,
|
|
1307
|
+
onChange: toggleSelectAll
|
|
1308
|
+
}
|
|
1309
|
+
) }),
|
|
1310
|
+
renderHeader("title", "booking.column.title"),
|
|
1311
|
+
renderHeader("status", "booking.column.status"),
|
|
1312
|
+
renderHeader("startsAt", "booking.column.startsAt"),
|
|
1313
|
+
renderHeader("endsAt", "booking.column.endsAt")
|
|
1314
|
+
] }) }),
|
|
1315
|
+
/* @__PURE__ */ jsx10("tbody", { children: visibleBookings.map((booking, index) => /* @__PURE__ */ jsxs9(
|
|
1316
|
+
"tr",
|
|
1317
|
+
{
|
|
1318
|
+
className: index % 2 === 1 ? "bg-gray-50 dark:bg-gray-800/50" : void 0,
|
|
1319
|
+
children: [
|
|
1320
|
+
/* @__PURE__ */ jsx10("td", { className: CHECKBOX_CELL_CLASS, children: /* @__PURE__ */ jsx10(
|
|
1321
|
+
"input",
|
|
1322
|
+
{
|
|
1323
|
+
type: "checkbox",
|
|
1324
|
+
"aria-label": booking.title,
|
|
1325
|
+
checked: selected.has(booking.id),
|
|
1326
|
+
onChange: () => toggleRowSelected(booking.id)
|
|
1327
|
+
}
|
|
1328
|
+
) }),
|
|
1329
|
+
/* @__PURE__ */ jsx10("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsx10("button", { type: "button", onClick: () => onRowClick?.(booking), className: "min-h-11 text-left hover:underline", children: booking.title }) }),
|
|
1330
|
+
/* @__PURE__ */ jsx10("td", { className: "px-3 py-2", children: messages[`booking.status.${booking.status === "no_show" ? "noShow" : booking.status}`] }),
|
|
1331
|
+
/* @__PURE__ */ jsx10("td", { className: "px-3 py-2", children: booking.startsAt.toLocaleString(locale) }),
|
|
1332
|
+
/* @__PURE__ */ jsx10("td", { className: "px-3 py-2", children: booking.endsAt.toLocaleString(locale) })
|
|
1333
|
+
]
|
|
1334
|
+
},
|
|
1335
|
+
booking.id
|
|
1336
|
+
)) })
|
|
1337
|
+
] }),
|
|
1338
|
+
visibleBookings.length === 0 && /* @__PURE__ */ jsx10("p", { className: "px-3 py-4 text-sm text-gray-500 dark:text-gray-400", children: messages["common.empty"] })
|
|
1339
|
+
] }),
|
|
1340
|
+
pagination && pagination.totalPages > 1 && /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-end gap-3", children: [
|
|
1341
|
+
/* @__PURE__ */ jsx10(
|
|
1342
|
+
"button",
|
|
1343
|
+
{
|
|
1344
|
+
type: "button",
|
|
1345
|
+
"aria-label": messages["common.previousPage"],
|
|
1346
|
+
disabled: state.page <= 1,
|
|
1347
|
+
onClick: () => goToPage(state.page - 1),
|
|
1348
|
+
className: "min-h-11 min-w-11 inline-flex items-center justify-center rounded-lg border border-gray-300 dark:border-gray-700 disabled:opacity-50",
|
|
1349
|
+
children: /* @__PURE__ */ jsx10(ArrowLeft, { "aria-hidden": "true", className: "w-4 h-4" })
|
|
1350
|
+
}
|
|
1351
|
+
),
|
|
1352
|
+
/* @__PURE__ */ jsxs9("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: [
|
|
1353
|
+
state.page,
|
|
1354
|
+
" / ",
|
|
1355
|
+
pagination.totalPages
|
|
1356
|
+
] }),
|
|
1357
|
+
/* @__PURE__ */ jsx10(
|
|
1358
|
+
"button",
|
|
1359
|
+
{
|
|
1360
|
+
type: "button",
|
|
1361
|
+
"aria-label": messages["common.nextPage"],
|
|
1362
|
+
disabled: state.page >= pagination.totalPages,
|
|
1363
|
+
onClick: () => goToPage(state.page + 1),
|
|
1364
|
+
className: "min-h-11 min-w-11 inline-flex items-center justify-center rounded-lg border border-gray-300 dark:border-gray-700 disabled:opacity-50",
|
|
1365
|
+
children: /* @__PURE__ */ jsx10(ArrowRight, { "aria-hidden": "true", className: "w-4 h-4" })
|
|
1366
|
+
}
|
|
1367
|
+
)
|
|
1368
|
+
] })
|
|
1369
|
+
] });
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
// src/hooks/useBookingsTableState.hook.ts
|
|
1373
|
+
import { useEffect as useEffect2, useState as useState7 } from "react";
|
|
1374
|
+
var OWNED_PARAM_KEYS = ["sortBy", "sortDirection", "status", "page"];
|
|
1375
|
+
function readInitialState() {
|
|
1376
|
+
if (typeof window === "undefined") return DEFAULT_BOOKINGS_TABLE_STATE;
|
|
1377
|
+
return parseBookingsTableState(window.location.search);
|
|
1378
|
+
}
|
|
1379
|
+
function useBookingsTableState() {
|
|
1380
|
+
const [state, setState] = useState7(readInitialState);
|
|
1381
|
+
useEffect2(() => {
|
|
1382
|
+
if (typeof window === "undefined") return;
|
|
1383
|
+
const params = new URLSearchParams(window.location.search);
|
|
1384
|
+
for (const key of OWNED_PARAM_KEYS) params.delete(key);
|
|
1385
|
+
for (const [key, value] of new URLSearchParams(serializeBookingsTableState(state))) params.append(key, value);
|
|
1386
|
+
const query = params.toString();
|
|
1387
|
+
const url = `${window.location.pathname}${query ? `?${query}` : ""}${window.location.hash}`;
|
|
1388
|
+
window.history.replaceState(window.history.state, "", url);
|
|
1389
|
+
}, [state]);
|
|
1390
|
+
return [state, setState];
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// src/workspace/BookingsArea.tsx
|
|
1394
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1395
|
+
var PAGE_SIZE = 20;
|
|
1396
|
+
function BookingsArea() {
|
|
1397
|
+
const { locale } = useSchedulingConfig();
|
|
1398
|
+
const messages = resolveSchedulingMessages(locale);
|
|
1399
|
+
const [tableState, setTableState] = useBookingsTableState();
|
|
1400
|
+
const status = tableState.statusFilters.length > 0 ? tableState.statusFilters : void 0;
|
|
1401
|
+
const { data, isLoading, isError } = useBookings({
|
|
1402
|
+
page: tableState.page,
|
|
1403
|
+
pageSize: PAGE_SIZE,
|
|
1404
|
+
status,
|
|
1405
|
+
sortBy: tableState.sortColumn,
|
|
1406
|
+
sortDirection: tableState.sortColumn ? tableState.sortDirection : void 0
|
|
1407
|
+
});
|
|
1408
|
+
const confirmBooking = useConfirmBooking();
|
|
1409
|
+
const [selectedBookingId, setSelectedBookingId] = useState8(void 0);
|
|
1410
|
+
const selectedBooking = data?.data.find((booking) => booking.id === selectedBookingId);
|
|
1411
|
+
function bulkConfirm(ids) {
|
|
1412
|
+
for (const id of ids) confirmBooking.mutate(id);
|
|
1413
|
+
}
|
|
1414
|
+
return /* @__PURE__ */ jsxs10("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col p-4 space-y-4", children: [
|
|
1415
|
+
isError && /* @__PURE__ */ jsx11("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.loadFailure"] }),
|
|
1416
|
+
isLoading ? /* @__PURE__ */ jsx11("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.loading"] }) : /* @__PURE__ */ jsx11(
|
|
1417
|
+
BookingsTable,
|
|
1418
|
+
{
|
|
1419
|
+
bookings: data?.data ?? [],
|
|
1420
|
+
state: tableState,
|
|
1421
|
+
onStateChange: setTableState,
|
|
1422
|
+
pagination: data ? { totalPages: data.totalPages } : void 0,
|
|
1423
|
+
onRowClick: (booking) => setSelectedBookingId(booking.id),
|
|
1424
|
+
bulkActions: [{ key: "confirm", label: messages["booking.confirm"], onRun: bulkConfirm }]
|
|
1425
|
+
}
|
|
1426
|
+
),
|
|
1427
|
+
selectedBooking && /* @__PURE__ */ jsx11(BookingDrawer, { booking: selectedBooking, onClose: () => setSelectedBookingId(void 0) })
|
|
1428
|
+
] });
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
// src/workspace/ResourcesArea.tsx
|
|
1432
|
+
import { Plus as Plus3, Trash2 as Trash23 } from "lucide-react";
|
|
1433
|
+
import { useState as useState10 } from "react";
|
|
1434
|
+
|
|
1435
|
+
// src/components/ResourceForm.tsx
|
|
1436
|
+
import { useState as useState9 } from "react";
|
|
1437
|
+
import { RESOURCE_KIND } from "@adatechnology/scheduling-contracts";
|
|
1438
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1439
|
+
var INPUT_CLASS2 = "w-full min-h-11 px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 bg-white dark:bg-gray-900";
|
|
1440
|
+
var LABEL_CLASS = "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1";
|
|
1441
|
+
var BUTTON_PRIMARY = "inline-flex items-center justify-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-colors min-h-11 bg-brand-600 text-white hover:bg-brand-700 disabled:opacity-50";
|
|
1442
|
+
function ResourceForm({ initialValues, onSubmit }) {
|
|
1443
|
+
const { locale } = useSchedulingConfig();
|
|
1444
|
+
const messages = resolveSchedulingMessages(locale);
|
|
1445
|
+
const [name, setName] = useState9(initialValues?.name ?? "");
|
|
1446
|
+
const [kind, setKind] = useState9(initialValues?.kind ?? RESOURCE_KIND.PERSON);
|
|
1447
|
+
const [timezone, setTimezone] = useState9(initialValues?.timezone ?? "America/Sao_Paulo");
|
|
1448
|
+
const [active, setActive] = useState9(initialValues?.active ?? true);
|
|
1449
|
+
const [submitting, setSubmitting] = useState9(false);
|
|
1450
|
+
async function handleSubmit(event) {
|
|
1451
|
+
event.preventDefault();
|
|
1452
|
+
setSubmitting(true);
|
|
1453
|
+
try {
|
|
1454
|
+
await onSubmit({ name, kind, timezone, active });
|
|
1455
|
+
} finally {
|
|
1456
|
+
setSubmitting(false);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
return /* @__PURE__ */ jsxs11("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
|
|
1460
|
+
/* @__PURE__ */ jsxs11("div", { children: [
|
|
1461
|
+
/* @__PURE__ */ jsx12("label", { htmlFor: "scheduling-resource-name", className: LABEL_CLASS, children: messages["resource.name"] }),
|
|
1462
|
+
/* @__PURE__ */ jsx12(
|
|
1463
|
+
"input",
|
|
1464
|
+
{
|
|
1465
|
+
id: "scheduling-resource-name",
|
|
1466
|
+
type: "text",
|
|
1467
|
+
required: true,
|
|
1468
|
+
value: name,
|
|
1469
|
+
onChange: (event) => setName(event.target.value),
|
|
1470
|
+
className: INPUT_CLASS2
|
|
1471
|
+
}
|
|
1472
|
+
)
|
|
1473
|
+
] }),
|
|
1474
|
+
/* @__PURE__ */ jsxs11("div", { children: [
|
|
1475
|
+
/* @__PURE__ */ jsx12("label", { htmlFor: "scheduling-resource-kind", className: LABEL_CLASS, children: messages["resource.kind"] }),
|
|
1476
|
+
/* @__PURE__ */ jsxs11(
|
|
1477
|
+
"select",
|
|
1478
|
+
{
|
|
1479
|
+
id: "scheduling-resource-kind",
|
|
1480
|
+
value: kind,
|
|
1481
|
+
onChange: (event) => setKind(event.target.value),
|
|
1482
|
+
className: INPUT_CLASS2,
|
|
1483
|
+
children: [
|
|
1484
|
+
/* @__PURE__ */ jsx12("option", { value: RESOURCE_KIND.PERSON, children: messages["resource.kind.person"] }),
|
|
1485
|
+
/* @__PURE__ */ jsx12("option", { value: RESOURCE_KIND.ROOM, children: messages["resource.kind.room"] }),
|
|
1486
|
+
/* @__PURE__ */ jsx12("option", { value: RESOURCE_KIND.EQUIPMENT, children: messages["resource.kind.equipment"] })
|
|
1487
|
+
]
|
|
1488
|
+
}
|
|
1489
|
+
)
|
|
1490
|
+
] }),
|
|
1491
|
+
/* @__PURE__ */ jsxs11("div", { children: [
|
|
1492
|
+
/* @__PURE__ */ jsx12("label", { htmlFor: "scheduling-resource-timezone", className: LABEL_CLASS, children: messages["resource.timezone"] }),
|
|
1493
|
+
/* @__PURE__ */ jsx12(
|
|
1494
|
+
"input",
|
|
1495
|
+
{
|
|
1496
|
+
id: "scheduling-resource-timezone",
|
|
1497
|
+
type: "text",
|
|
1498
|
+
required: true,
|
|
1499
|
+
value: timezone,
|
|
1500
|
+
onChange: (event) => setTimezone(event.target.value),
|
|
1501
|
+
placeholder: "America/Sao_Paulo",
|
|
1502
|
+
className: INPUT_CLASS2
|
|
1503
|
+
}
|
|
1504
|
+
)
|
|
1505
|
+
] }),
|
|
1506
|
+
initialValues && /* @__PURE__ */ jsxs11("label", { className: "flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300 min-h-11", children: [
|
|
1507
|
+
/* @__PURE__ */ jsx12("input", { type: "checkbox", checked: active, onChange: (event) => setActive(event.target.checked) }),
|
|
1508
|
+
messages["resource.active"]
|
|
1509
|
+
] }),
|
|
1510
|
+
/* @__PURE__ */ jsx12("button", { type: "submit", disabled: submitting, className: BUTTON_PRIMARY, children: messages["common.save"] })
|
|
1511
|
+
] });
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
// src/components/ResourceList.tsx
|
|
1515
|
+
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1516
|
+
function ResourceList({ resources, onSelect }) {
|
|
1517
|
+
const { locale } = useSchedulingConfig();
|
|
1518
|
+
const messages = resolveSchedulingMessages(locale);
|
|
1519
|
+
if (resources.length === 0) {
|
|
1520
|
+
return /* @__PURE__ */ jsx13("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.empty"] });
|
|
1521
|
+
}
|
|
1522
|
+
return /* @__PURE__ */ jsx13("ul", { className: "divide-y divide-gray-200 dark:divide-gray-700 rounded-lg border border-gray-200 dark:border-gray-700", children: resources.map((resource, index) => /* @__PURE__ */ jsx13("li", { className: index % 2 === 1 ? "bg-gray-50 dark:bg-gray-800/50" : void 0, children: /* @__PURE__ */ jsxs12(
|
|
1523
|
+
"button",
|
|
1524
|
+
{
|
|
1525
|
+
type: "button",
|
|
1526
|
+
onClick: () => onSelect(resource),
|
|
1527
|
+
className: "flex w-full items-center gap-3 px-4 py-3 min-h-11 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-800",
|
|
1528
|
+
children: [
|
|
1529
|
+
/* @__PURE__ */ jsx13("span", { className: "flex-1 font-medium text-gray-900 dark:text-gray-100", children: resource.name }),
|
|
1530
|
+
/* @__PURE__ */ jsx13("span", { className: "text-gray-500 dark:text-gray-400", children: messages[`resource.kind.${resource.kind}`] }),
|
|
1531
|
+
/* @__PURE__ */ jsx13("span", { className: "text-gray-500 dark:text-gray-400", children: resource.timezone }),
|
|
1532
|
+
!resource.active && /* @__PURE__ */ jsx13("span", { className: "rounded-full bg-gray-200 px-2 py-0.5 text-xs text-gray-700 dark:bg-gray-700 dark:text-gray-300", children: messages["resource.inactive"] })
|
|
1533
|
+
]
|
|
1534
|
+
}
|
|
1535
|
+
) }, resource.id)) });
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
// src/workspace/ResourcesArea.tsx
|
|
1539
|
+
import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1540
|
+
var BUTTON_CLASS2 = "inline-flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-colors min-h-11";
|
|
1541
|
+
var BUTTON_PRIMARY2 = `${BUTTON_CLASS2} bg-brand-600 text-white hover:bg-brand-700`;
|
|
1542
|
+
var BUTTON_DANGER = `${BUTTON_CLASS2} text-red-700 hover:bg-red-50`;
|
|
1543
|
+
function ResourcesArea() {
|
|
1544
|
+
const { locale } = useSchedulingConfig();
|
|
1545
|
+
const messages = resolveSchedulingMessages(locale);
|
|
1546
|
+
const { data, isLoading, isError } = useResources();
|
|
1547
|
+
const createResource = useCreateResource();
|
|
1548
|
+
const updateResource = useUpdateResource();
|
|
1549
|
+
const deleteResource = useDeleteResource();
|
|
1550
|
+
const [draft, setDraft] = useState10(void 0);
|
|
1551
|
+
const isDraftOpen = draft !== void 0;
|
|
1552
|
+
const isEditing = Boolean(draft);
|
|
1553
|
+
async function handleSubmit(input) {
|
|
1554
|
+
try {
|
|
1555
|
+
if (draft) {
|
|
1556
|
+
await updateResource.mutateAsync({ id: draft.id, input });
|
|
1557
|
+
} else {
|
|
1558
|
+
await createResource.mutateAsync(input);
|
|
1559
|
+
}
|
|
1560
|
+
setDraft(void 0);
|
|
1561
|
+
} catch {
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
return /* @__PURE__ */ jsxs13("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col p-4 space-y-4", children: [
|
|
1565
|
+
/* @__PURE__ */ jsx14("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxs13("button", { type: "button", onClick: () => setDraft(null), className: `${BUTTON_PRIMARY2} ml-auto`, children: [
|
|
1566
|
+
/* @__PURE__ */ jsx14(Plus3, { "aria-hidden": "true", className: "w-4 h-4" }),
|
|
1567
|
+
messages["resource.newResource"]
|
|
1568
|
+
] }) }),
|
|
1569
|
+
isError && /* @__PURE__ */ jsx14("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.loadFailure"] }),
|
|
1570
|
+
(createResource.isError || updateResource.isError || deleteResource.isError) && /* @__PURE__ */ jsx14("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.actionFailure"] }),
|
|
1571
|
+
isLoading ? /* @__PURE__ */ jsx14("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.loading"] }) : /* @__PURE__ */ jsx14(ResourceList, { resources: data?.data ?? [], onSelect: setDraft }),
|
|
1572
|
+
isDraftOpen && /* @__PURE__ */ jsx14(
|
|
1573
|
+
SidePanel,
|
|
1574
|
+
{
|
|
1575
|
+
title: isEditing ? messages["resource.editTitle"] : messages["resource.createTitle"],
|
|
1576
|
+
closeLabel: messages["common.close"],
|
|
1577
|
+
onClose: () => setDraft(void 0),
|
|
1578
|
+
headerActions: isEditing ? /* @__PURE__ */ jsxs13(
|
|
1579
|
+
"button",
|
|
1580
|
+
{
|
|
1581
|
+
type: "button",
|
|
1582
|
+
onClick: () => {
|
|
1583
|
+
if (draft) void deleteResource.mutateAsync(draft.id).then(() => setDraft(void 0)).catch(() => {
|
|
1584
|
+
});
|
|
1585
|
+
},
|
|
1586
|
+
className: BUTTON_DANGER,
|
|
1587
|
+
children: [
|
|
1588
|
+
/* @__PURE__ */ jsx14(Trash23, { "aria-hidden": "true", className: "w-4 h-4" }),
|
|
1589
|
+
messages["common.remove"]
|
|
1590
|
+
]
|
|
1591
|
+
}
|
|
1592
|
+
) : void 0,
|
|
1593
|
+
children: /* @__PURE__ */ jsx14(
|
|
1594
|
+
ResourceForm,
|
|
1595
|
+
{
|
|
1596
|
+
...draft ? { initialValues: draft } : {},
|
|
1597
|
+
onSubmit: handleSubmit
|
|
1598
|
+
},
|
|
1599
|
+
draft ? draft.id : "new"
|
|
1600
|
+
)
|
|
1601
|
+
}
|
|
1602
|
+
)
|
|
1603
|
+
] });
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
// src/workspace/ServicesArea.tsx
|
|
1607
|
+
import { Plus as Plus4, Trash2 as Trash24 } from "lucide-react";
|
|
1608
|
+
import { useState as useState12 } from "react";
|
|
1609
|
+
|
|
1610
|
+
// src/components/ServiceForm.tsx
|
|
1611
|
+
import { useState as useState11 } from "react";
|
|
1612
|
+
import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1613
|
+
var INPUT_CLASS3 = "w-full min-h-11 px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 bg-white dark:bg-gray-900";
|
|
1614
|
+
var LABEL_CLASS2 = "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1";
|
|
1615
|
+
var BUTTON_PRIMARY3 = "inline-flex items-center justify-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-colors min-h-11 bg-brand-600 text-white hover:bg-brand-700 disabled:opacity-50";
|
|
1616
|
+
function ServiceForm({ initialValues, onSubmit }) {
|
|
1617
|
+
const { locale } = useSchedulingConfig();
|
|
1618
|
+
const messages = resolveSchedulingMessages(locale);
|
|
1619
|
+
const [name, setName] = useState11(initialValues?.name ?? "");
|
|
1620
|
+
const [durationMinutes, setDurationMinutes] = useState11(initialValues?.durationMinutes ?? 30);
|
|
1621
|
+
const [bufferBeforeMinutes, setBufferBeforeMinutes] = useState11(initialValues?.bufferBeforeMinutes ?? 0);
|
|
1622
|
+
const [bufferAfterMinutes, setBufferAfterMinutes] = useState11(initialValues?.bufferAfterMinutes ?? 0);
|
|
1623
|
+
const [active, setActive] = useState11(initialValues?.active ?? true);
|
|
1624
|
+
const [submitting, setSubmitting] = useState11(false);
|
|
1625
|
+
async function handleSubmit(event) {
|
|
1626
|
+
event.preventDefault();
|
|
1627
|
+
setSubmitting(true);
|
|
1628
|
+
try {
|
|
1629
|
+
await onSubmit({ name, durationMinutes, bufferBeforeMinutes, bufferAfterMinutes, active });
|
|
1630
|
+
} finally {
|
|
1631
|
+
setSubmitting(false);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
return /* @__PURE__ */ jsxs14("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
|
|
1635
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1636
|
+
/* @__PURE__ */ jsx15("label", { htmlFor: "scheduling-service-name", className: LABEL_CLASS2, children: messages["service.name"] }),
|
|
1637
|
+
/* @__PURE__ */ jsx15(
|
|
1638
|
+
"input",
|
|
1639
|
+
{
|
|
1640
|
+
id: "scheduling-service-name",
|
|
1641
|
+
type: "text",
|
|
1642
|
+
required: true,
|
|
1643
|
+
value: name,
|
|
1644
|
+
onChange: (event) => setName(event.target.value),
|
|
1645
|
+
className: INPUT_CLASS3
|
|
1646
|
+
}
|
|
1647
|
+
)
|
|
1648
|
+
] }),
|
|
1649
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1650
|
+
/* @__PURE__ */ jsx15("label", { htmlFor: "scheduling-service-duration", className: LABEL_CLASS2, children: messages["service.durationMinutes"] }),
|
|
1651
|
+
/* @__PURE__ */ jsx15(
|
|
1652
|
+
"input",
|
|
1653
|
+
{
|
|
1654
|
+
id: "scheduling-service-duration",
|
|
1655
|
+
type: "number",
|
|
1656
|
+
min: 1,
|
|
1657
|
+
required: true,
|
|
1658
|
+
value: durationMinutes,
|
|
1659
|
+
onChange: (event) => setDurationMinutes(Number(event.target.value)),
|
|
1660
|
+
className: INPUT_CLASS3
|
|
1661
|
+
}
|
|
1662
|
+
)
|
|
1663
|
+
] }),
|
|
1664
|
+
/* @__PURE__ */ jsxs14("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
1665
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1666
|
+
/* @__PURE__ */ jsx15("label", { htmlFor: "scheduling-service-buffer-before", className: LABEL_CLASS2, children: messages["service.bufferBeforeMinutes"] }),
|
|
1667
|
+
/* @__PURE__ */ jsx15(
|
|
1668
|
+
"input",
|
|
1669
|
+
{
|
|
1670
|
+
id: "scheduling-service-buffer-before",
|
|
1671
|
+
type: "number",
|
|
1672
|
+
min: 0,
|
|
1673
|
+
value: bufferBeforeMinutes,
|
|
1674
|
+
onChange: (event) => setBufferBeforeMinutes(Number(event.target.value)),
|
|
1675
|
+
className: INPUT_CLASS3
|
|
1676
|
+
}
|
|
1677
|
+
)
|
|
1678
|
+
] }),
|
|
1679
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
1680
|
+
/* @__PURE__ */ jsx15("label", { htmlFor: "scheduling-service-buffer-after", className: LABEL_CLASS2, children: messages["service.bufferAfterMinutes"] }),
|
|
1681
|
+
/* @__PURE__ */ jsx15(
|
|
1682
|
+
"input",
|
|
1683
|
+
{
|
|
1684
|
+
id: "scheduling-service-buffer-after",
|
|
1685
|
+
type: "number",
|
|
1686
|
+
min: 0,
|
|
1687
|
+
value: bufferAfterMinutes,
|
|
1688
|
+
onChange: (event) => setBufferAfterMinutes(Number(event.target.value)),
|
|
1689
|
+
className: INPUT_CLASS3
|
|
1690
|
+
}
|
|
1691
|
+
)
|
|
1692
|
+
] })
|
|
1693
|
+
] }),
|
|
1694
|
+
initialValues && /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300 min-h-11", children: [
|
|
1695
|
+
/* @__PURE__ */ jsx15("input", { type: "checkbox", checked: active, onChange: (event) => setActive(event.target.checked) }),
|
|
1696
|
+
messages["service.active"]
|
|
1697
|
+
] }),
|
|
1698
|
+
/* @__PURE__ */ jsx15("button", { type: "submit", disabled: submitting, className: BUTTON_PRIMARY3, children: messages["common.save"] })
|
|
1699
|
+
] });
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
// src/components/ServiceList.tsx
|
|
1703
|
+
import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1704
|
+
function ServiceList({ services, onSelect }) {
|
|
1705
|
+
const { locale } = useSchedulingConfig();
|
|
1706
|
+
const messages = resolveSchedulingMessages(locale);
|
|
1707
|
+
if (services.length === 0) {
|
|
1708
|
+
return /* @__PURE__ */ jsx16("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.empty"] });
|
|
1709
|
+
}
|
|
1710
|
+
return /* @__PURE__ */ jsx16("ul", { className: "divide-y divide-gray-200 dark:divide-gray-700 rounded-lg border border-gray-200 dark:border-gray-700", children: services.map((service, index) => /* @__PURE__ */ jsx16("li", { className: index % 2 === 1 ? "bg-gray-50 dark:bg-gray-800/50" : void 0, children: /* @__PURE__ */ jsxs15(
|
|
1711
|
+
"button",
|
|
1712
|
+
{
|
|
1713
|
+
type: "button",
|
|
1714
|
+
onClick: () => onSelect(service),
|
|
1715
|
+
className: "flex w-full items-center gap-3 px-4 py-3 min-h-11 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-800",
|
|
1716
|
+
children: [
|
|
1717
|
+
/* @__PURE__ */ jsx16("span", { className: "flex-1 font-medium text-gray-900 dark:text-gray-100", children: service.name }),
|
|
1718
|
+
/* @__PURE__ */ jsxs15("span", { className: "text-gray-500 dark:text-gray-400", children: [
|
|
1719
|
+
service.durationMinutes,
|
|
1720
|
+
" min"
|
|
1721
|
+
] }),
|
|
1722
|
+
!service.active && /* @__PURE__ */ jsx16("span", { className: "rounded-full bg-gray-200 px-2 py-0.5 text-xs text-gray-700 dark:bg-gray-700 dark:text-gray-300", children: messages["service.inactive"] })
|
|
1723
|
+
]
|
|
1724
|
+
}
|
|
1725
|
+
) }, service.id)) });
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
// src/workspace/ServicesArea.tsx
|
|
1729
|
+
import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1730
|
+
var BUTTON_CLASS3 = "inline-flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-colors min-h-11";
|
|
1731
|
+
var BUTTON_PRIMARY4 = `${BUTTON_CLASS3} bg-brand-600 text-white hover:bg-brand-700`;
|
|
1732
|
+
var BUTTON_DANGER2 = `${BUTTON_CLASS3} text-red-700 hover:bg-red-50`;
|
|
1733
|
+
function ServicesArea() {
|
|
1734
|
+
const { locale } = useSchedulingConfig();
|
|
1735
|
+
const messages = resolveSchedulingMessages(locale);
|
|
1736
|
+
const { data, isLoading, isError } = useServices();
|
|
1737
|
+
const createService = useCreateService();
|
|
1738
|
+
const updateService = useUpdateService();
|
|
1739
|
+
const deleteService = useDeleteService();
|
|
1740
|
+
const [draft, setDraft] = useState12(void 0);
|
|
1741
|
+
const isDraftOpen = draft !== void 0;
|
|
1742
|
+
const isEditing = Boolean(draft);
|
|
1743
|
+
async function handleSubmit(input) {
|
|
1744
|
+
try {
|
|
1745
|
+
if (draft) {
|
|
1746
|
+
await updateService.mutateAsync({ id: draft.id, input });
|
|
1747
|
+
} else {
|
|
1748
|
+
await createService.mutateAsync(input);
|
|
1749
|
+
}
|
|
1750
|
+
setDraft(void 0);
|
|
1751
|
+
} catch {
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
return /* @__PURE__ */ jsxs16("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col p-4 space-y-4", children: [
|
|
1755
|
+
/* @__PURE__ */ jsx17("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxs16("button", { type: "button", onClick: () => setDraft(null), className: `${BUTTON_PRIMARY4} ml-auto`, children: [
|
|
1756
|
+
/* @__PURE__ */ jsx17(Plus4, { "aria-hidden": "true", className: "w-4 h-4" }),
|
|
1757
|
+
messages["service.newService"]
|
|
1758
|
+
] }) }),
|
|
1759
|
+
isError && /* @__PURE__ */ jsx17("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.loadFailure"] }),
|
|
1760
|
+
(createService.isError || updateService.isError || deleteService.isError) && /* @__PURE__ */ jsx17("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.actionFailure"] }),
|
|
1761
|
+
isLoading ? /* @__PURE__ */ jsx17("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.loading"] }) : /* @__PURE__ */ jsx17(ServiceList, { services: data?.data ?? [], onSelect: setDraft }),
|
|
1762
|
+
isDraftOpen && /* @__PURE__ */ jsx17(
|
|
1763
|
+
SidePanel,
|
|
1764
|
+
{
|
|
1765
|
+
title: isEditing ? messages["service.editTitle"] : messages["service.createTitle"],
|
|
1766
|
+
closeLabel: messages["common.close"],
|
|
1767
|
+
onClose: () => setDraft(void 0),
|
|
1768
|
+
headerActions: isEditing ? /* @__PURE__ */ jsxs16(
|
|
1769
|
+
"button",
|
|
1770
|
+
{
|
|
1771
|
+
type: "button",
|
|
1772
|
+
onClick: () => {
|
|
1773
|
+
if (draft) void deleteService.mutateAsync(draft.id).then(() => setDraft(void 0)).catch(() => {
|
|
1774
|
+
});
|
|
1775
|
+
},
|
|
1776
|
+
className: BUTTON_DANGER2,
|
|
1777
|
+
children: [
|
|
1778
|
+
/* @__PURE__ */ jsx17(Trash24, { "aria-hidden": "true", className: "w-4 h-4" }),
|
|
1779
|
+
messages["common.remove"]
|
|
1780
|
+
]
|
|
1781
|
+
}
|
|
1782
|
+
) : void 0,
|
|
1783
|
+
children: /* @__PURE__ */ jsx17(
|
|
1784
|
+
ServiceForm,
|
|
1785
|
+
{
|
|
1786
|
+
...draft ? { initialValues: draft } : {},
|
|
1787
|
+
onSubmit: handleSubmit
|
|
1788
|
+
},
|
|
1789
|
+
draft ? draft.id : "new"
|
|
1790
|
+
)
|
|
1791
|
+
}
|
|
1792
|
+
)
|
|
1793
|
+
] });
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
// src/workspace/WorkspaceAreaNav.tsx
|
|
1797
|
+
import { CalendarDays, Clock, ClipboardList, Users, Wrench } from "lucide-react";
|
|
1798
|
+
|
|
1799
|
+
// src/workspace/workspace.constant.ts
|
|
1800
|
+
var SCHEDULING_WORKSPACE_AREA = {
|
|
1801
|
+
AGENDA: "agenda",
|
|
1802
|
+
BOOKINGS: "bookings",
|
|
1803
|
+
RESOURCES: "resources",
|
|
1804
|
+
SERVICES: "services",
|
|
1805
|
+
AVAILABILITY: "availability"
|
|
1806
|
+
};
|
|
1807
|
+
var AREAS = Object.values(SCHEDULING_WORKSPACE_AREA);
|
|
1808
|
+
function isSchedulingWorkspaceArea(value) {
|
|
1809
|
+
return AREAS.includes(value);
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
// src/workspace/WorkspaceAreaNav.tsx
|
|
1813
|
+
import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1814
|
+
var ITEM_BASE = "flex items-center gap-2 px-3 py-2 -mb-px border-b-2 text-sm font-medium transition-colors min-h-11";
|
|
1815
|
+
var ITEM_ACTIVE = "border-brand-600 text-brand-700 dark:text-brand-400";
|
|
1816
|
+
var ITEM_IDLE = "border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100";
|
|
1817
|
+
var AREA_ICON = {
|
|
1818
|
+
[SCHEDULING_WORKSPACE_AREA.AGENDA]: CalendarDays,
|
|
1819
|
+
[SCHEDULING_WORKSPACE_AREA.BOOKINGS]: ClipboardList,
|
|
1820
|
+
[SCHEDULING_WORKSPACE_AREA.RESOURCES]: Users,
|
|
1821
|
+
[SCHEDULING_WORKSPACE_AREA.SERVICES]: Wrench,
|
|
1822
|
+
[SCHEDULING_WORKSPACE_AREA.AVAILABILITY]: Clock
|
|
1823
|
+
};
|
|
1824
|
+
function WorkspaceAreaNav({ area, labels, onSelect }) {
|
|
1825
|
+
const items = [
|
|
1826
|
+
[SCHEDULING_WORKSPACE_AREA.AGENDA, labels.agendaTab],
|
|
1827
|
+
[SCHEDULING_WORKSPACE_AREA.BOOKINGS, labels.bookingsTab],
|
|
1828
|
+
[SCHEDULING_WORKSPACE_AREA.RESOURCES, labels.resourcesTab],
|
|
1829
|
+
[SCHEDULING_WORKSPACE_AREA.SERVICES, labels.servicesTab],
|
|
1830
|
+
[SCHEDULING_WORKSPACE_AREA.AVAILABILITY, labels.availabilityTab]
|
|
1831
|
+
];
|
|
1832
|
+
return /* @__PURE__ */ jsx18("nav", { "aria-label": labels.areaNav, className: "flex gap-1 px-4 border-b border-gray-200 dark:border-gray-700 overflow-x-auto", children: items.map(([value, label]) => {
|
|
1833
|
+
const Icon = AREA_ICON[value];
|
|
1834
|
+
return /* @__PURE__ */ jsxs17(
|
|
1835
|
+
"button",
|
|
1836
|
+
{
|
|
1837
|
+
type: "button",
|
|
1838
|
+
onClick: () => onSelect(value),
|
|
1839
|
+
"aria-current": value === area ? "page" : void 0,
|
|
1840
|
+
className: `${ITEM_BASE} ${value === area ? ITEM_ACTIVE : ITEM_IDLE} whitespace-nowrap`,
|
|
1841
|
+
children: [
|
|
1842
|
+
/* @__PURE__ */ jsx18(Icon, { "aria-hidden": "true", className: "w-4 h-4 shrink-0" }),
|
|
1843
|
+
label
|
|
1844
|
+
]
|
|
1845
|
+
},
|
|
1846
|
+
value
|
|
1847
|
+
);
|
|
1848
|
+
}) });
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
// src/workspace/SchedulingWorkspace.tsx
|
|
1852
|
+
import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1853
|
+
var AREA_COMPONENT = {
|
|
1854
|
+
[SCHEDULING_WORKSPACE_AREA.AGENDA]: AgendaArea,
|
|
1855
|
+
[SCHEDULING_WORKSPACE_AREA.BOOKINGS]: BookingsArea,
|
|
1856
|
+
[SCHEDULING_WORKSPACE_AREA.RESOURCES]: ResourcesArea,
|
|
1857
|
+
[SCHEDULING_WORKSPACE_AREA.SERVICES]: ServicesArea,
|
|
1858
|
+
[SCHEDULING_WORKSPACE_AREA.AVAILABILITY]: AvailabilityArea
|
|
1859
|
+
};
|
|
1860
|
+
function SchedulingWorkspace({
|
|
1861
|
+
labels: labelsOverride,
|
|
1862
|
+
renderHeaderActions,
|
|
1863
|
+
area: areaProp,
|
|
1864
|
+
onAreaChange
|
|
1865
|
+
}) {
|
|
1866
|
+
const labels = { ...DEFAULT_SCHEDULING_WORKSPACE_LABELS, ...labelsOverride };
|
|
1867
|
+
const [internalArea, setInternalArea] = useState13(SCHEDULING_WORKSPACE_AREA.AGENDA);
|
|
1868
|
+
const area = areaProp ?? internalArea;
|
|
1869
|
+
const AreaComponent = AREA_COMPONENT[area];
|
|
1870
|
+
function handleSelectArea(next) {
|
|
1871
|
+
setInternalArea(next);
|
|
1872
|
+
onAreaChange?.(next);
|
|
1873
|
+
}
|
|
1874
|
+
return /* @__PURE__ */ jsxs18("div", { className: "flex flex-col h-full", children: [
|
|
1875
|
+
/* @__PURE__ */ jsxs18("header", { className: "flex flex-wrap items-center gap-3 px-4 py-3", children: [
|
|
1876
|
+
/* @__PURE__ */ jsx19("h1", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100 mr-auto", children: labels.title }),
|
|
1877
|
+
renderHeaderActions?.()
|
|
1878
|
+
] }),
|
|
1879
|
+
/* @__PURE__ */ jsx19(WorkspaceAreaNav, { area, labels, onSelect: handleSelectArea }),
|
|
1880
|
+
/* @__PURE__ */ jsx19("div", { className: "relative flex flex-1 min-h-0 overflow-hidden", children: /* @__PURE__ */ jsx19(AreaComponent, {}) })
|
|
1881
|
+
] });
|
|
1882
|
+
}
|
|
1883
|
+
export {
|
|
1884
|
+
DEFAULT_SCHEDULING_LOCALE,
|
|
1885
|
+
DEFAULT_SCHEDULING_UI_CONFIG,
|
|
1886
|
+
DEFAULT_SCHEDULING_WORKSPACE_LABELS,
|
|
1887
|
+
SCHEDULING_QUERY_KEYS,
|
|
1888
|
+
SCHEDULING_WORKSPACE_AREA,
|
|
1889
|
+
SchedulingProvider,
|
|
1890
|
+
SchedulingWorkspace,
|
|
1891
|
+
isSchedulingWorkspaceArea,
|
|
1892
|
+
resolveSchedulingMessages,
|
|
1893
|
+
useAddAvailabilityException,
|
|
1894
|
+
useAvailabilityExceptions,
|
|
1895
|
+
useAvailabilityRules,
|
|
1896
|
+
useAvailableSlots,
|
|
1897
|
+
useBooking,
|
|
1898
|
+
useBookings,
|
|
1899
|
+
useCancelBooking,
|
|
1900
|
+
useCompleteBooking,
|
|
1901
|
+
useConfirmBooking,
|
|
1902
|
+
useCreateResource,
|
|
1903
|
+
useCreateService,
|
|
1904
|
+
useDeleteResource,
|
|
1905
|
+
useDeleteService,
|
|
1906
|
+
useMarkNoShow,
|
|
1907
|
+
useRemoveAvailabilityException,
|
|
1908
|
+
useRequestBooking,
|
|
1909
|
+
useRescheduleBooking,
|
|
1910
|
+
useResources,
|
|
1911
|
+
useScheduling,
|
|
1912
|
+
useSchedulingConfig,
|
|
1913
|
+
useServices,
|
|
1914
|
+
useSetAvailabilityRules,
|
|
1915
|
+
useUpdateResource,
|
|
1916
|
+
useUpdateService
|
|
1917
|
+
};
|