@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/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
+ };