@adatechnology/scheduling-ui 0.1.0-rc.4 → 0.1.0-rc.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +16 -1
  2. package/dist/index.js +1303 -724
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -313,10 +313,11 @@ var DEFAULT_SCHEDULING_WORKSPACE_LABELS = {
313
313
  };
314
314
 
315
315
  // src/workspace/SchedulingWorkspace.tsx
316
- import { useState as useState13 } from "react";
316
+ import { useState as useState14 } from "react";
317
317
 
318
318
  // src/workspace/AgendaArea.tsx
319
- import { useState } from "react";
319
+ import { CalendarDays, ChevronLeft, ChevronRight } from "lucide-react";
320
+ import { useEffect as useEffect3, useState as useState3 } from "react";
320
321
  import { MAX_PAGE_SIZE } from "@adatechnology/scheduling-contracts";
321
322
 
322
323
  // src/locales/pt-BR.json
@@ -410,7 +411,22 @@ var pt_BR_default = {
410
411
  "datetime.year": "Ano",
411
412
  "datetime.month": "M\xEAs",
412
413
  "datetime.day": "Dia",
413
- "datetime.time": "Hora"
414
+ "datetime.time": "Hora",
415
+ "common.emptyHint": "Quando houver algo aqui, ele aparece nesta lista.",
416
+ "agenda.allResources": "Todos",
417
+ "agenda.viewLabel": "Visualiza\xE7\xE3o",
418
+ "agenda.now": "Agora",
419
+ "agenda.emptyTitle": "Nada agendado",
420
+ "resource.emptyTitle": "Nenhum recurso cadastrado",
421
+ "resource.emptyHint": "Recursos s\xE3o quem ou o que atende \u2014 pessoas, salas, equipamentos.",
422
+ "service.emptyTitle": "Nenhum servi\xE7o cadastrado",
423
+ "service.emptyHint": "Servi\xE7os definem o que \xE9 agendado e quanto tempo dura.",
424
+ "booking.emptyTitle": "Nenhuma reserva",
425
+ "booking.emptyHint": "Ajuste os filtros de status ou aguarde novas solicita\xE7\xF5es.",
426
+ "availability.selectResourceTitle": "Escolha um recurso",
427
+ "availability.selectResourceHint": "As regras de disponibilidade s\xE3o definidas por recurso.",
428
+ "select.search": "Buscar\u2026",
429
+ "select.noResults": "Nenhum resultado"
414
430
  };
415
431
 
416
432
  // src/locales/en.json
@@ -504,7 +520,22 @@ var en_default = {
504
520
  "datetime.year": "Year",
505
521
  "datetime.month": "Month",
506
522
  "datetime.day": "Day",
507
- "datetime.time": "Time"
523
+ "datetime.time": "Time",
524
+ "common.emptyHint": "When there is something here, it shows up in this list.",
525
+ "agenda.allResources": "All",
526
+ "agenda.viewLabel": "View",
527
+ "agenda.now": "Now",
528
+ "agenda.emptyTitle": "Nothing scheduled",
529
+ "resource.emptyTitle": "No resources yet",
530
+ "resource.emptyHint": "Resources are who or what serves \u2014 people, rooms, equipment.",
531
+ "service.emptyTitle": "No services yet",
532
+ "service.emptyHint": "Services define what gets booked and how long it takes.",
533
+ "booking.emptyTitle": "No bookings",
534
+ "booking.emptyHint": "Adjust the status filters or wait for new requests.",
535
+ "availability.selectResourceTitle": "Pick a resource",
536
+ "availability.selectResourceHint": "Availability rules are defined per resource.",
537
+ "select.search": "Search\u2026",
538
+ "select.noResults": "No results"
508
539
  };
509
540
 
510
541
  // src/locales/index.ts
@@ -517,6 +548,47 @@ function resolveSchedulingMessages(locale) {
517
548
  return MESSAGES_BY_LOCALE[locale] ?? MESSAGES_BY_LOCALE[DEFAULT_SCHEDULING_LOCALE];
518
549
  }
519
550
 
551
+ // src/components/BookingStatusBadge.tsx
552
+ import { BOOKING_STATUS } from "@adatechnology/scheduling-contracts";
553
+ import { jsx as jsx2 } from "react/jsx-runtime";
554
+ var BOOKING_STATUS_TONE = {
555
+ [BOOKING_STATUS.REQUESTED]: {
556
+ badge: "bg-amber-100 text-amber-800 dark:bg-amber-900/50 dark:text-amber-200",
557
+ block: "border-amber-400 bg-amber-50 text-amber-900 dark:border-amber-600 dark:bg-amber-900/40 dark:text-amber-100"
558
+ },
559
+ [BOOKING_STATUS.CONFIRMED]: {
560
+ badge: "bg-brand-100 text-brand-800 dark:bg-brand-900/50 dark:text-brand-200",
561
+ block: "border-brand-400 bg-brand-50 text-brand-900 dark:border-brand-600 dark:bg-brand-900/40 dark:text-brand-100"
562
+ },
563
+ [BOOKING_STATUS.CANCELLED]: {
564
+ badge: "bg-gray-200 text-gray-600 line-through dark:bg-gray-700 dark:text-gray-400",
565
+ block: "border-gray-300 bg-gray-100 text-gray-500 line-through dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400"
566
+ },
567
+ [BOOKING_STATUS.COMPLETED]: {
568
+ badge: "bg-emerald-100 text-emerald-800 dark:bg-emerald-900/50 dark:text-emerald-200",
569
+ block: "border-emerald-400 bg-emerald-50 text-emerald-900 dark:border-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-100"
570
+ },
571
+ [BOOKING_STATUS.NO_SHOW]: {
572
+ badge: "bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-200",
573
+ block: "border-red-400 bg-red-50 text-red-900 dark:border-red-600 dark:bg-red-900/40 dark:text-red-100"
574
+ }
575
+ };
576
+ function bookingStatusLabel(messages, status) {
577
+ const key = status === BOOKING_STATUS.NO_SHOW ? "booking.status.noShow" : `booking.status.${status}`;
578
+ return messages[key];
579
+ }
580
+ function BookingStatusBadge({ status }) {
581
+ const { locale } = useSchedulingConfig();
582
+ const messages = resolveSchedulingMessages(locale);
583
+ return /* @__PURE__ */ jsx2(
584
+ "span",
585
+ {
586
+ className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${BOOKING_STATUS_TONE[status].badge}`,
587
+ children: bookingStatusLabel(messages, status)
588
+ }
589
+ );
590
+ }
591
+
520
592
  // src/components/agendaLayout.util.ts
521
593
  function groupOverlappingBookings(sortedBookings) {
522
594
  const clusters = [];
@@ -565,7 +637,9 @@ function startOfDay(date) {
565
637
  }
566
638
 
567
639
  // src/components/AgendaGrid.tsx
568
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
640
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
641
+ var HOUR_HEIGHT_REM = 4;
642
+ var HEADER_CLASS = "sticky top-0 z-10 h-10 bg-white px-2 text-xs font-medium text-gray-700 dark:bg-gray-900 dark:text-gray-300";
569
643
  function buildHours(startHour, endHour) {
570
644
  return Array.from({ length: endHour - startHour }, (_hour, index) => startHour + index);
571
645
  }
@@ -575,14 +649,18 @@ function bookingsOnDay(bookings, day, startHour, endHour) {
575
649
  const visibleEnd = new Date(dayStart.getTime() + endHour * 60 * 6e4);
576
650
  return bookings.filter((booking) => booking.startsAt < visibleEnd && booking.endsAt > visibleStart);
577
651
  }
578
- function AgendaGrid({ bookings, days }) {
652
+ function isSameDay(left, right) {
653
+ return startOfDay(left).getTime() === startOfDay(right).getTime();
654
+ }
655
+ function AgendaGrid({ bookings, days, now, onSelect }) {
579
656
  const { locale, agendaStartHour, agendaEndHour } = useSchedulingConfig();
580
657
  const messages = resolveSchedulingMessages(locale);
581
658
  const hours = buildHours(agendaStartHour, agendaEndHour);
582
- return /* @__PURE__ */ jsxs("div", { className: "flex overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700", children: [
583
- /* @__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: [
584
- /* @__PURE__ */ jsx2("div", { className: "h-8" }),
585
- hours.map((hour) => /* @__PURE__ */ jsxs("div", { className: "h-16 px-2 pt-1", children: [
659
+ const columnHeight = `${hours.length * HOUR_HEIGHT_REM}rem`;
660
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-1 min-h-0 overflow-auto rounded-xl border border-gray-200 dark:border-gray-800", children: [
661
+ /* @__PURE__ */ jsxs("div", { className: "sticky left-0 z-20 flex shrink-0 flex-col border-r border-gray-200 bg-white text-xs text-gray-500 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-400", children: [
662
+ /* @__PURE__ */ jsx3("div", { className: `${HEADER_CLASS} border-b border-gray-200 dark:border-gray-800` }),
663
+ hours.map((hour) => /* @__PURE__ */ jsxs("div", { className: "h-16 border-b border-gray-100 px-2 pt-1 tabular-nums dark:border-gray-800/60", children: [
586
664
  String(hour).padStart(2, "0"),
587
665
  ":00"
588
666
  ] }, hour))
@@ -591,47 +669,748 @@ function AgendaGrid({ bookings, days }) {
591
669
  const dayBookings = bookingsOnDay(bookings, day, agendaStartHour, agendaEndHour);
592
670
  const positioned = layoutDayBookings(dayBookings);
593
671
  const dayStart = startOfDay(day);
594
- return /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-40 border-r border-gray-200 dark:border-gray-700 last:border-r-0", children: [
595
- /* @__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" }) }),
596
- /* @__PURE__ */ jsxs("div", { className: "relative", style: { height: `${hours.length * 4}rem` }, children: [
597
- positioned.length === 0 && /* @__PURE__ */ jsx2("p", { className: "absolute inset-x-2 top-2 text-xs text-gray-400", children: messages["agenda.empty"] }),
598
- positioned.map(({ booking, columnIndex, columnCount }) => {
599
- const top = toVisiblePercent({
600
- minutesSinceDayStart: minutesFromDayStart(booking.startsAt, dayStart),
601
- startHour: agendaStartHour,
602
- endHour: agendaEndHour
603
- });
604
- const bottom = toVisiblePercent({
605
- minutesSinceDayStart: minutesFromDayStart(booking.endsAt, dayStart),
606
- startHour: agendaStartHour,
607
- endHour: agendaEndHour
608
- });
609
- const width = 100 / columnCount;
610
- return /* @__PURE__ */ jsx2(
672
+ const isToday = isSameDay(day, now);
673
+ const nowPercent = toVisiblePercent({
674
+ minutesSinceDayStart: minutesFromDayStart(now, dayStart),
675
+ startHour: agendaStartHour,
676
+ endHour: agendaEndHour
677
+ });
678
+ const showNowLine = isToday && nowPercent >= 0 && nowPercent <= 100;
679
+ return /* @__PURE__ */ jsxs(
680
+ "div",
681
+ {
682
+ className: `flex-1 min-w-40 border-r border-gray-200 last:border-r-0 dark:border-gray-800 ${isToday ? "bg-brand-50/40 dark:bg-brand-900/10" : ""}`,
683
+ children: [
684
+ /* @__PURE__ */ jsxs(
611
685
  "div",
612
686
  {
613
- title: booking.title,
614
- 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",
615
- style: {
616
- top: `${top}%`,
617
- height: `${Math.max(bottom - top, 2)}%`,
618
- left: `${columnIndex * width}%`,
619
- width: `${width}%`
687
+ className: `${HEADER_CLASS} flex items-center gap-1 border-b border-gray-200 dark:border-gray-800 ${isToday ? "text-brand-700 dark:text-brand-300" : ""}`,
688
+ children: [
689
+ day.toLocaleDateString(locale, { weekday: "short", day: "2-digit", month: "2-digit" }),
690
+ isToday && /* @__PURE__ */ jsx3("span", { className: "h-1.5 w-1.5 rounded-full bg-brand-600", "aria-hidden": "true" })
691
+ ]
692
+ }
693
+ ),
694
+ /* @__PURE__ */ jsxs("div", { className: "relative", style: { height: columnHeight }, children: [
695
+ hours.map((hour, index) => /* @__PURE__ */ jsx3(
696
+ "div",
697
+ {
698
+ "aria-hidden": "true",
699
+ className: "absolute inset-x-0 border-b border-gray-100 dark:border-gray-800/60",
700
+ style: { top: `${index / hours.length * 100}%`, height: `${100 / hours.length}%` }
620
701
  },
621
- children: booking.title
622
- },
623
- booking.id
624
- );
625
- })
626
- ] })
627
- ] }, day.toISOString());
702
+ hour
703
+ )),
704
+ positioned.length === 0 && /* @__PURE__ */ jsx3("p", { className: "absolute inset-x-2 top-2 text-xs text-gray-400 dark:text-gray-500", children: messages["agenda.empty"] }),
705
+ positioned.map(({ booking, columnIndex, columnCount }) => {
706
+ const top = toVisiblePercent({
707
+ minutesSinceDayStart: minutesFromDayStart(booking.startsAt, dayStart),
708
+ startHour: agendaStartHour,
709
+ endHour: agendaEndHour
710
+ });
711
+ const bottom = toVisiblePercent({
712
+ minutesSinceDayStart: minutesFromDayStart(booking.endsAt, dayStart),
713
+ startHour: agendaStartHour,
714
+ endHour: agendaEndHour
715
+ });
716
+ const width = 100 / columnCount;
717
+ const startLabel = booking.startsAt.toLocaleTimeString(locale, { hour: "2-digit", minute: "2-digit" });
718
+ return /* @__PURE__ */ jsxs(
719
+ "button",
720
+ {
721
+ type: "button",
722
+ onClick: () => onSelect?.(booking),
723
+ title: `${startLabel} \xB7 ${booking.title}`,
724
+ className: `absolute overflow-hidden rounded-md border-l-4 px-1.5 py-0.5 text-left text-xs shadow-sm transition-shadow hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 ${BOOKING_STATUS_TONE[booking.status].block}`,
725
+ style: {
726
+ top: `${top}%`,
727
+ height: `${Math.max(bottom - top, 2)}%`,
728
+ left: `${columnIndex * width}%`,
729
+ width: `${width}%`
730
+ },
731
+ children: [
732
+ /* @__PURE__ */ jsx3("span", { className: "block font-medium tabular-nums opacity-80", children: startLabel }),
733
+ /* @__PURE__ */ jsx3("span", { className: "block truncate", children: booking.title })
734
+ ]
735
+ },
736
+ booking.id
737
+ );
738
+ }),
739
+ showNowLine && /* @__PURE__ */ jsxs(
740
+ "div",
741
+ {
742
+ "aria-label": messages["agenda.now"],
743
+ className: "pointer-events-none absolute inset-x-0 z-10 flex items-center",
744
+ style: { top: `${nowPercent}%` },
745
+ children: [
746
+ /* @__PURE__ */ jsx3("span", { className: "h-2 w-2 shrink-0 rounded-full bg-red-500" }),
747
+ /* @__PURE__ */ jsx3("span", { className: "h-px flex-1 bg-red-500" })
748
+ ]
749
+ }
750
+ )
751
+ ] })
752
+ ]
753
+ },
754
+ day.toISOString()
755
+ );
628
756
  })
629
757
  ] });
630
758
  }
631
759
 
760
+ // src/components/BookingDrawer.tsx
761
+ import { XCircle } from "lucide-react";
762
+ import { useState as useState2 } from "react";
763
+ import { BOOKING_STATUS as BOOKING_STATUS2 } from "@adatechnology/scheduling-contracts";
764
+
765
+ // src/components/DateTimeField.tsx
766
+ import { useId as useId2, useMemo as useMemo3 } from "react";
767
+
768
+ // src/components/SelectField.tsx
769
+ import { Check, ChevronDown, Search } from "lucide-react";
770
+ import { useEffect, useId, useMemo as useMemo2, useRef, useState } from "react";
771
+
772
+ // src/components/selectFilter.util.ts
773
+ var COMBINING_MARKS = /[̀-ͯ]/g;
774
+ function normalizeForSearch(text) {
775
+ return text.normalize("NFD").replace(COMBINING_MARKS, "").toLowerCase().trim();
776
+ }
777
+ function filterSelectOptions(options, query) {
778
+ const needle = normalizeForSearch(query);
779
+ if (needle === "") return options;
780
+ return options.filter((option) => normalizeForSearch(option.label).includes(needle));
781
+ }
782
+ function findByPrefix(options, prefix) {
783
+ const needle = normalizeForSearch(prefix);
784
+ if (needle === "") return -1;
785
+ return options.findIndex((option) => normalizeForSearch(option.label).startsWith(needle));
786
+ }
787
+
788
+ // src/components/ui.constant.ts
789
+ var BUTTON_BASE = "inline-flex items-center justify-center gap-2 rounded-lg px-3 text-sm font-medium min-h-11 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-900 disabled:opacity-50 disabled:pointer-events-none";
790
+ var BUTTON_PRIMARY = `${BUTTON_BASE} bg-brand-600 text-white hover:bg-brand-700`;
791
+ var BUTTON_SECONDARY = `${BUTTON_BASE} border border-gray-300 bg-white text-gray-700 hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-200 dark:hover:bg-gray-800`;
792
+ var BUTTON_GHOST = `${BUTTON_BASE} text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800`;
793
+ var BUTTON_DANGER = `${BUTTON_BASE} text-red-700 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950/40`;
794
+ var ICON_BUTTON = `${BUTTON_SECONDARY} min-w-11 px-0`;
795
+ var FIELD_CONTROL = "min-h-11 rounded-lg border border-gray-300 bg-white px-3 text-sm text-gray-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100";
796
+ var FIELD_LABEL = "text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400";
797
+ var SURFACE_BORDER = "rounded-xl border border-gray-200 dark:border-gray-800";
798
+ var ROW_STRIPE = "bg-gray-50 dark:bg-gray-800/40";
799
+
800
+ // src/components/SelectField.tsx
801
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
802
+ var SEARCHABLE_THRESHOLD = 8;
803
+ var POPUP_MAX_HEIGHT = 288;
804
+ var TYPEAHEAD_RESET_MS = 700;
805
+ var OPTION_BASE = "flex w-full cursor-pointer items-center gap-2 px-3 py-2 text-left text-sm";
806
+ function SelectField({
807
+ label,
808
+ value,
809
+ options,
810
+ onChange,
811
+ emptyOptionLabel,
812
+ searchable,
813
+ hideLabel,
814
+ className
815
+ }) {
816
+ const { locale } = useSchedulingConfig();
817
+ const messages = resolveSchedulingMessages(locale);
818
+ const fieldId = useId();
819
+ const listboxId = `${fieldId}-listbox`;
820
+ const containerRef = useRef(null);
821
+ const triggerRef = useRef(null);
822
+ const searchRef = useRef(null);
823
+ const listRef = useRef(null);
824
+ const typeahead = useRef({ text: "", at: 0 });
825
+ const [isOpen, setIsOpen] = useState(false);
826
+ const [openUpward, setOpenUpward] = useState(false);
827
+ const [query, setQuery] = useState("");
828
+ const [highlighted, setHighlighted] = useState(0);
829
+ const allOptions = useMemo2(
830
+ () => emptyOptionLabel === void 0 ? options : [{ value: "", label: emptyOptionLabel }, ...options],
831
+ [emptyOptionLabel, options]
832
+ );
833
+ const withSearch = searchable ?? allOptions.length >= SEARCHABLE_THRESHOLD;
834
+ const visibleOptions = useMemo2(
835
+ () => withSearch ? filterSelectOptions(allOptions, query) : allOptions,
836
+ [allOptions, query, withSearch]
837
+ );
838
+ const selected = allOptions.find((option) => option.value === value);
839
+ function open() {
840
+ const rect = triggerRef.current?.getBoundingClientRect();
841
+ if (rect) {
842
+ const spaceBelow = window.innerHeight - rect.bottom;
843
+ setOpenUpward(spaceBelow < POPUP_MAX_HEIGHT && rect.top > spaceBelow);
844
+ }
845
+ setQuery("");
846
+ setHighlighted(Math.max(allOptions.findIndex((option) => option.value === value), 0));
847
+ setIsOpen(true);
848
+ }
849
+ function close(returnFocus = true) {
850
+ setIsOpen(false);
851
+ if (returnFocus) triggerRef.current?.focus();
852
+ }
853
+ function commit(option) {
854
+ onChange(option.value);
855
+ close();
856
+ }
857
+ useEffect(() => {
858
+ if (!isOpen) return;
859
+ function handlePointerDown(event) {
860
+ if (!containerRef.current?.contains(event.target)) setIsOpen(false);
861
+ }
862
+ document.addEventListener("pointerdown", handlePointerDown);
863
+ return () => document.removeEventListener("pointerdown", handlePointerDown);
864
+ }, [isOpen]);
865
+ useEffect(() => {
866
+ if (isOpen && withSearch) searchRef.current?.focus();
867
+ }, [isOpen, withSearch]);
868
+ useEffect(() => {
869
+ if (!isOpen) return;
870
+ listRef.current?.children[highlighted]?.scrollIntoView({ block: "nearest" });
871
+ }, [highlighted, isOpen]);
872
+ function jumpByTypeahead(key) {
873
+ if (withSearch || key.length !== 1 || !/\S/.test(key)) return false;
874
+ const now = Date.now();
875
+ const text = now - typeahead.current.at > TYPEAHEAD_RESET_MS ? key : typeahead.current.text + key;
876
+ typeahead.current = { text, at: now };
877
+ const index = findByPrefix(visibleOptions, text);
878
+ if (index >= 0) setHighlighted(index);
879
+ return index >= 0;
880
+ }
881
+ function handleKeyDown(event) {
882
+ if (!isOpen) {
883
+ if (["ArrowDown", "ArrowUp", "Enter", " "].includes(event.key)) {
884
+ event.preventDefault();
885
+ open();
886
+ }
887
+ return;
888
+ }
889
+ switch (event.key) {
890
+ case "Escape":
891
+ event.preventDefault();
892
+ close();
893
+ return;
894
+ case "Tab":
895
+ close(false);
896
+ return;
897
+ case "ArrowDown":
898
+ event.preventDefault();
899
+ setHighlighted((current) => Math.min(current + 1, visibleOptions.length - 1));
900
+ return;
901
+ case "ArrowUp":
902
+ event.preventDefault();
903
+ setHighlighted((current) => Math.max(current - 1, 0));
904
+ return;
905
+ case "Home":
906
+ event.preventDefault();
907
+ setHighlighted(0);
908
+ return;
909
+ case "End":
910
+ event.preventDefault();
911
+ setHighlighted(visibleOptions.length - 1);
912
+ return;
913
+ case "Enter": {
914
+ event.preventDefault();
915
+ const option = visibleOptions[highlighted];
916
+ if (option) commit(option);
917
+ return;
918
+ }
919
+ default:
920
+ if (jumpByTypeahead(event.key)) event.preventDefault();
921
+ }
922
+ }
923
+ return /* @__PURE__ */ jsxs2("div", { className: `flex flex-col gap-1 ${className ?? ""}`, children: [
924
+ /* @__PURE__ */ jsx4("span", { id: fieldId, className: hideLabel ? "sr-only" : FIELD_LABEL, children: label }),
925
+ /* @__PURE__ */ jsxs2("div", { ref: containerRef, className: "relative", children: [
926
+ /* @__PURE__ */ jsxs2(
927
+ "button",
928
+ {
929
+ ref: triggerRef,
930
+ type: "button",
931
+ role: "combobox",
932
+ "aria-controls": listboxId,
933
+ "aria-expanded": isOpen,
934
+ "aria-haspopup": "listbox",
935
+ "aria-labelledby": fieldId,
936
+ onClick: () => isOpen ? close(false) : open(),
937
+ onKeyDown: handleKeyDown,
938
+ className: `${FIELD_CONTROL} flex w-full items-center gap-2 text-left`,
939
+ children: [
940
+ /* @__PURE__ */ jsx4("span", { className: `flex-1 truncate ${selected ? "" : "text-gray-500 dark:text-gray-400"}`, children: selected?.label ?? emptyOptionLabel ?? "" }),
941
+ /* @__PURE__ */ jsx4(
942
+ ChevronDown,
943
+ {
944
+ "aria-hidden": "true",
945
+ className: `h-4 w-4 shrink-0 text-gray-400 transition-transform ${isOpen ? "rotate-180" : ""}`
946
+ }
947
+ )
948
+ ]
949
+ }
950
+ ),
951
+ isOpen && /* @__PURE__ */ jsxs2(
952
+ "div",
953
+ {
954
+ className: `absolute z-30 w-full min-w-max overflow-hidden rounded-lg border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-900 ${openUpward ? "bottom-full mb-1" : "top-full mt-1"}`,
955
+ children: [
956
+ withSearch && /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 border-b border-gray-200 px-3 dark:border-gray-700", children: [
957
+ /* @__PURE__ */ jsx4(Search, { "aria-hidden": "true", className: "h-4 w-4 shrink-0 text-gray-400" }),
958
+ /* @__PURE__ */ jsx4(
959
+ "input",
960
+ {
961
+ ref: searchRef,
962
+ type: "text",
963
+ value: query,
964
+ onChange: (event) => {
965
+ setQuery(event.target.value);
966
+ setHighlighted(0);
967
+ },
968
+ onKeyDown: handleKeyDown,
969
+ "aria-label": messages["select.search"],
970
+ placeholder: messages["select.search"],
971
+ className: "min-h-11 w-full bg-transparent text-sm text-gray-900 outline-none placeholder:text-gray-400 dark:text-gray-100"
972
+ }
973
+ )
974
+ ] }),
975
+ /* @__PURE__ */ jsxs2(
976
+ "ul",
977
+ {
978
+ ref: listRef,
979
+ id: listboxId,
980
+ role: "listbox",
981
+ "aria-labelledby": fieldId,
982
+ className: "max-h-72 overflow-y-auto py-1",
983
+ children: [
984
+ visibleOptions.map((option, index) => {
985
+ const isSelected = option.value === value;
986
+ return /* @__PURE__ */ jsxs2(
987
+ "li",
988
+ {
989
+ role: "option",
990
+ "aria-selected": isSelected,
991
+ onPointerEnter: () => setHighlighted(index),
992
+ onClick: () => commit(option),
993
+ className: `${OPTION_BASE} ${index === highlighted ? "bg-brand-50 dark:bg-brand-900/30" : ""} ${isSelected ? "font-medium text-brand-800 dark:text-brand-200" : "text-gray-800 dark:text-gray-200"}`,
994
+ children: [
995
+ /* @__PURE__ */ jsx4("span", { className: "flex-1 truncate", children: option.label }),
996
+ isSelected && /* @__PURE__ */ jsx4(Check, { "aria-hidden": "true", className: "h-4 w-4 shrink-0" })
997
+ ]
998
+ },
999
+ option.value
1000
+ );
1001
+ }),
1002
+ visibleOptions.length === 0 && /* @__PURE__ */ jsx4("li", { className: "px-3 py-3 text-sm text-gray-500 dark:text-gray-400", children: messages["select.noResults"] })
1003
+ ]
1004
+ }
1005
+ )
1006
+ ]
1007
+ }
1008
+ )
1009
+ ] })
1010
+ ] });
1011
+ }
1012
+
1013
+ // src/components/dateTimeParts.util.ts
1014
+ var DATE_TIME_LOCAL_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/;
1015
+ var MONTHS_IN_YEAR = 12;
1016
+ function parseDateTimeParts(value) {
1017
+ const match = DATE_TIME_LOCAL_PATTERN.exec(value);
1018
+ if (!match) return void 0;
1019
+ const [, year, month, day, hour, minute] = match;
1020
+ return { year: Number(year), month: Number(month), day: Number(day), time: `${hour}:${minute}` };
1021
+ }
1022
+ function daysInMonth(params) {
1023
+ return new Date(Date.UTC(params.year, params.month, 0)).getUTCDate();
1024
+ }
1025
+ function pad(value) {
1026
+ return String(value).padStart(2, "0");
1027
+ }
1028
+ function formatDateTimeParts(parts) {
1029
+ const lastDay = daysInMonth({ year: parts.year, month: parts.month });
1030
+ const day = Math.min(Math.max(parts.day, 1), lastDay);
1031
+ return `${String(parts.year).padStart(4, "0")}-${pad(parts.month)}-${pad(day)}T${parts.time}`;
1032
+ }
1033
+ function buildYearOptions(params) {
1034
+ const current = params.today.getFullYear();
1035
+ const first = Math.min(current - params.past, params.year);
1036
+ const last = Math.max(current + params.future, params.year);
1037
+ return Array.from({ length: last - first + 1 }, (_, index) => first + index);
1038
+ }
1039
+
1040
+ // src/components/DateTimeField.tsx
1041
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1042
+ var FIELD_CLASS = "min-h-11 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 text-sm";
1043
+ var YEAR_WINDOW = { PAST: 1, FUTURE: 2 };
1044
+ function partsOf(value, fallback) {
1045
+ return parseDateTimeParts(value) ?? {
1046
+ year: fallback.getFullYear(),
1047
+ month: fallback.getMonth() + 1,
1048
+ day: fallback.getDate(),
1049
+ time: "09:00"
1050
+ };
1051
+ }
1052
+ function monthLabels(locale) {
1053
+ const format = new Intl.DateTimeFormat(locale, { month: "long", timeZone: "UTC" });
1054
+ return Array.from(
1055
+ { length: MONTHS_IN_YEAR },
1056
+ (_, index) => format.format(new Date(Date.UTC(2026, index, 1)))
1057
+ );
1058
+ }
1059
+ function DateTimeField({ label, value, onChange, emptyDefault }) {
1060
+ const { locale } = useSchedulingConfig();
1061
+ const messages = resolveSchedulingMessages(locale);
1062
+ const groupId = useId2();
1063
+ const today = emptyDefault ?? /* @__PURE__ */ new Date();
1064
+ const parts = partsOf(value, today);
1065
+ const years = buildYearOptions({
1066
+ year: parts.year,
1067
+ today,
1068
+ past: YEAR_WINDOW.PAST,
1069
+ future: YEAR_WINDOW.FUTURE
1070
+ });
1071
+ const days = daysInMonth({ year: parts.year, month: parts.month });
1072
+ const months = monthLabels(locale);
1073
+ const yearOptions = useMemo3(
1074
+ () => years.map((year) => ({ value: String(year), label: String(year) })),
1075
+ [years]
1076
+ );
1077
+ const monthOptions = useMemo3(
1078
+ () => months.map((name, index) => ({ value: String(index + 1), label: name })),
1079
+ [months]
1080
+ );
1081
+ const dayOptions = useMemo3(
1082
+ () => Array.from({ length: days }, (_, index) => ({ value: String(index + 1), label: String(index + 1) })),
1083
+ [days]
1084
+ );
1085
+ function change(patch) {
1086
+ onChange(formatDateTimeParts({ ...parts, ...patch }));
1087
+ }
1088
+ return /* @__PURE__ */ jsxs3("div", { role: "group", "aria-labelledby": groupId, className: "flex flex-wrap items-center gap-2", children: [
1089
+ /* @__PURE__ */ jsx5("span", { id: groupId, className: "sr-only", children: label }),
1090
+ /* @__PURE__ */ jsx5(
1091
+ SelectField,
1092
+ {
1093
+ hideLabel: true,
1094
+ label: `${label} \u2014 ${messages["datetime.year"]}`,
1095
+ value: String(parts.year),
1096
+ options: yearOptions,
1097
+ onChange: (next) => change({ year: Number(next) }),
1098
+ className: "min-w-24"
1099
+ }
1100
+ ),
1101
+ /* @__PURE__ */ jsx5(
1102
+ SelectField,
1103
+ {
1104
+ hideLabel: true,
1105
+ label: `${label} \u2014 ${messages["datetime.month"]}`,
1106
+ value: String(parts.month),
1107
+ options: monthOptions,
1108
+ onChange: (next) => change({ month: Number(next) }),
1109
+ searchable: false,
1110
+ className: "min-w-36"
1111
+ }
1112
+ ),
1113
+ /* @__PURE__ */ jsx5(
1114
+ SelectField,
1115
+ {
1116
+ hideLabel: true,
1117
+ label: `${label} \u2014 ${messages["datetime.day"]}`,
1118
+ value: String(Math.min(parts.day, days)),
1119
+ options: dayOptions,
1120
+ onChange: (next) => change({ day: Number(next) }),
1121
+ searchable: false,
1122
+ className: "min-w-20"
1123
+ }
1124
+ ),
1125
+ /* @__PURE__ */ jsx5(
1126
+ "input",
1127
+ {
1128
+ type: "time",
1129
+ "aria-label": `${label} \u2014 ${messages["datetime.time"]}`,
1130
+ value: parts.time,
1131
+ onChange: (event) => change({ time: event.target.value }),
1132
+ className: FIELD_CLASS
1133
+ }
1134
+ )
1135
+ ] });
1136
+ }
1137
+
1138
+ // src/components/datetimeLocal.util.ts
1139
+ function getTimeZoneOffsetMs(instant, timeZone) {
1140
+ const parts = new Intl.DateTimeFormat("en-US", {
1141
+ timeZone,
1142
+ year: "numeric",
1143
+ month: "2-digit",
1144
+ day: "2-digit",
1145
+ hour: "2-digit",
1146
+ minute: "2-digit",
1147
+ second: "2-digit",
1148
+ hourCycle: "h23"
1149
+ }).formatToParts(instant);
1150
+ const get = (type) => Number(parts.find((part) => part.type === type)?.value ?? 0);
1151
+ const asUtcOfWallClock = Date.UTC(
1152
+ get("year"),
1153
+ get("month") - 1,
1154
+ get("day"),
1155
+ get("hour") % 24,
1156
+ get("minute"),
1157
+ get("second")
1158
+ );
1159
+ return asUtcOfWallClock - instant.getTime();
1160
+ }
1161
+ function parseDateTimeLocalInTimeZone(value, timeZone) {
1162
+ const naiveUtc = /* @__PURE__ */ new Date(`${value}:00.000Z`);
1163
+ const firstOffset = getTimeZoneOffsetMs(naiveUtc, timeZone);
1164
+ const firstPass = new Date(naiveUtc.getTime() - firstOffset);
1165
+ const secondOffset = getTimeZoneOffsetMs(firstPass, timeZone);
1166
+ return new Date(naiveUtc.getTime() - secondOffset);
1167
+ }
1168
+ function formatDateTimeLocalInTimeZone(instant, timeZone) {
1169
+ const parts = new Intl.DateTimeFormat("en-US", {
1170
+ timeZone,
1171
+ year: "numeric",
1172
+ month: "2-digit",
1173
+ day: "2-digit",
1174
+ hour: "2-digit",
1175
+ minute: "2-digit",
1176
+ hourCycle: "h23"
1177
+ }).formatToParts(instant);
1178
+ const get = (type) => parts.find((part) => part.type === type)?.value ?? "00";
1179
+ const hour = get("hour") === "24" ? "00" : get("hour");
1180
+ return `${get("year")}-${get("month")}-${get("day")}T${hour}:${get("minute")}`;
1181
+ }
1182
+
1183
+ // src/components/SidePanel.tsx
1184
+ import { X } from "lucide-react";
1185
+ import { useEffect as useEffect2 } from "react";
1186
+ import { Fragment, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1187
+ function SidePanel({ title, closeLabel, onClose, headerActions, children }) {
1188
+ useEffect2(() => {
1189
+ function handleKeyDown(event) {
1190
+ if (event.key === "Escape") onClose();
1191
+ }
1192
+ window.addEventListener("keydown", handleKeyDown);
1193
+ return () => window.removeEventListener("keydown", handleKeyDown);
1194
+ }, [onClose]);
1195
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
1196
+ /* @__PURE__ */ jsx6(
1197
+ "button",
1198
+ {
1199
+ type: "button",
1200
+ "aria-hidden": "true",
1201
+ tabIndex: -1,
1202
+ onClick: onClose,
1203
+ className: "absolute inset-0 z-10 bg-gray-900/30 backdrop-blur-[1px] wide:hidden"
1204
+ }
1205
+ ),
1206
+ /* @__PURE__ */ jsxs4(
1207
+ "section",
1208
+ {
1209
+ "aria-label": title,
1210
+ 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",
1211
+ children: [
1212
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 border-b border-gray-200 px-4 py-3 dark:border-gray-700", children: [
1213
+ /* @__PURE__ */ jsx6("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100 mr-auto", children: title }),
1214
+ headerActions,
1215
+ /* @__PURE__ */ jsxs4(
1216
+ "button",
1217
+ {
1218
+ type: "button",
1219
+ onClick: onClose,
1220
+ className: BUTTON_GHOST,
1221
+ children: [
1222
+ /* @__PURE__ */ jsx6(X, { "aria-hidden": "true", className: "w-4 h-4" }),
1223
+ closeLabel
1224
+ ]
1225
+ }
1226
+ )
1227
+ ] }),
1228
+ /* @__PURE__ */ jsx6("div", { className: "min-h-0 flex-1 overflow-y-auto p-4", children })
1229
+ ]
1230
+ }
1231
+ )
1232
+ ] });
1233
+ }
1234
+
1235
+ // src/components/BookingDrawer.tsx
1236
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1237
+ var BROWSER_TIME_ZONE = Intl.DateTimeFormat().resolvedOptions().timeZone;
1238
+ 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";
1239
+ var PRIMARY_BUTTON_CLASS = "min-h-11 px-3 rounded-lg text-sm font-medium bg-brand-600 text-white hover:bg-brand-700";
1240
+ 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";
1241
+ 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";
1242
+ function BookingDrawer({ booking, resourceTimezone, onClose }) {
1243
+ const { locale } = useSchedulingConfig();
1244
+ const messages = resolveSchedulingMessages(locale);
1245
+ const confirmBooking = useConfirmBooking();
1246
+ const completeBooking = useCompleteBooking();
1247
+ const markNoShow = useMarkNoShow();
1248
+ const cancelBooking = useCancelBooking();
1249
+ const rescheduleBooking = useRescheduleBooking();
1250
+ const timezone = resourceTimezone ?? BROWSER_TIME_ZONE;
1251
+ const [isCancelling, setIsCancelling] = useState2(false);
1252
+ const [cancelledBy, setCancelledBy] = useState2("");
1253
+ const [cancellationReason, setCancellationReason] = useState2("");
1254
+ const [isRescheduling, setIsRescheduling] = useState2(false);
1255
+ const [start, setStart] = useState2(() => formatDateTimeLocalInTimeZone(booking.startsAt, timezone));
1256
+ const [end, setEnd] = useState2(() => formatDateTimeLocalInTimeZone(booking.endsAt, timezone));
1257
+ async function handleCancel() {
1258
+ if (!cancelledBy) return;
1259
+ try {
1260
+ await cancelBooking.mutateAsync({
1261
+ id: booking.id,
1262
+ input: { cancelledBy, ...cancellationReason ? { cancellationReason } : {} }
1263
+ });
1264
+ onClose();
1265
+ } catch {
1266
+ }
1267
+ }
1268
+ async function handleReschedule() {
1269
+ try {
1270
+ await rescheduleBooking.mutateAsync({
1271
+ id: booking.id,
1272
+ input: {
1273
+ during: {
1274
+ start: parseDateTimeLocalInTimeZone(start, timezone),
1275
+ end: parseDateTimeLocalInTimeZone(end, timezone)
1276
+ }
1277
+ }
1278
+ });
1279
+ setIsRescheduling(false);
1280
+ } catch {
1281
+ }
1282
+ }
1283
+ const isTerminal = booking.status === BOOKING_STATUS2.CANCELLED || booking.status === BOOKING_STATUS2.COMPLETED || booking.status === BOOKING_STATUS2.NO_SHOW;
1284
+ return /* @__PURE__ */ jsx7(SidePanel, { title: messages["booking.detailTitle"], closeLabel: messages["common.close"], onClose, children: /* @__PURE__ */ jsxs5("div", { className: "space-y-4", children: [
1285
+ /* @__PURE__ */ jsxs5("div", { children: [
1286
+ /* @__PURE__ */ jsx7("p", { className: "text-base font-semibold text-gray-900 dark:text-gray-100", children: booking.title }),
1287
+ /* @__PURE__ */ jsx7("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages[`booking.status.${booking.status === "no_show" ? "noShow" : booking.status}`] }),
1288
+ /* @__PURE__ */ jsxs5("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: [
1289
+ booking.startsAt.toLocaleString(locale),
1290
+ " \u2192 ",
1291
+ booking.endsAt.toLocaleString(locale)
1292
+ ] })
1293
+ ] }),
1294
+ (confirmBooking.isError || completeBooking.isError || markNoShow.isError || cancelBooking.isError || rescheduleBooking.isError) && /* @__PURE__ */ jsx7("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.actionFailure"] }),
1295
+ !isTerminal && /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap gap-2", children: [
1296
+ booking.status === BOOKING_STATUS2.REQUESTED && /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => confirmBooking.mutate(booking.id), className: PRIMARY_BUTTON_CLASS, children: messages["booking.confirm"] }),
1297
+ booking.status === BOOKING_STATUS2.CONFIRMED && /* @__PURE__ */ jsxs5(Fragment2, { children: [
1298
+ /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => completeBooking.mutate(booking.id), className: ACTION_BUTTON_CLASS, children: messages["booking.complete"] }),
1299
+ /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => markNoShow.mutate(booking.id), className: ACTION_BUTTON_CLASS, children: messages["booking.markNoShow"] }),
1300
+ /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => setIsRescheduling(true), className: ACTION_BUTTON_CLASS, children: messages["booking.reschedule"] })
1301
+ ] }),
1302
+ /* @__PURE__ */ jsxs5("button", { type: "button", onClick: () => setIsCancelling(true), className: DANGER_BUTTON_CLASS, children: [
1303
+ /* @__PURE__ */ jsx7(XCircle, { "aria-hidden": "true", className: "w-4 h-4" }),
1304
+ messages["booking.cancel"]
1305
+ ] })
1306
+ ] }),
1307
+ isRescheduling && /* @__PURE__ */ jsxs5("div", { className: "space-y-2 rounded-lg border border-gray-200 dark:border-gray-700 p-3", children: [
1308
+ /* @__PURE__ */ jsx7(
1309
+ DateTimeField,
1310
+ {
1311
+ label: messages["booking.rescheduleStart"],
1312
+ value: start,
1313
+ onChange: setStart
1314
+ }
1315
+ ),
1316
+ /* @__PURE__ */ jsx7(DateTimeField, { label: messages["booking.rescheduleEnd"], value: end, onChange: setEnd }),
1317
+ /* @__PURE__ */ jsx7("button", { type: "button", onClick: handleReschedule, disabled: rescheduleBooking.isPending, className: PRIMARY_BUTTON_CLASS, children: messages["common.save"] })
1318
+ ] }),
1319
+ isCancelling && /* @__PURE__ */ jsxs5("div", { className: "space-y-2 rounded-lg border border-gray-200 dark:border-gray-700 p-3", children: [
1320
+ /* @__PURE__ */ jsx7(
1321
+ "input",
1322
+ {
1323
+ type: "text",
1324
+ placeholder: messages["booking.cancelledBy"],
1325
+ value: cancelledBy,
1326
+ onChange: (event) => setCancelledBy(event.target.value),
1327
+ className: INPUT_CLASS
1328
+ }
1329
+ ),
1330
+ /* @__PURE__ */ jsx7(
1331
+ "input",
1332
+ {
1333
+ type: "text",
1334
+ placeholder: messages["booking.cancelReason"],
1335
+ value: cancellationReason,
1336
+ onChange: (event) => setCancellationReason(event.target.value),
1337
+ className: INPUT_CLASS
1338
+ }
1339
+ ),
1340
+ /* @__PURE__ */ jsxs5("button", { type: "button", onClick: handleCancel, disabled: cancelBooking.isPending, className: DANGER_BUTTON_CLASS, children: [
1341
+ /* @__PURE__ */ jsx7(XCircle, { "aria-hidden": "true", className: "w-4 h-4" }),
1342
+ messages["booking.cancel"]
1343
+ ] })
1344
+ ] })
1345
+ ] }) });
1346
+ }
1347
+
1348
+ // src/components/ResourceSelect.tsx
1349
+ import { useMemo as useMemo4 } from "react";
1350
+ import { jsx as jsx8 } from "react/jsx-runtime";
1351
+ function ResourceSelect({ label, emptyOptionLabel, resources, value, onChange }) {
1352
+ const options = useMemo4(
1353
+ () => resources.map((resource) => ({ value: resource.id, label: resource.name })),
1354
+ [resources]
1355
+ );
1356
+ return /* @__PURE__ */ jsx8(
1357
+ SelectField,
1358
+ {
1359
+ label,
1360
+ emptyOptionLabel,
1361
+ options,
1362
+ value,
1363
+ onChange,
1364
+ searchable: true,
1365
+ className: "min-w-52"
1366
+ }
1367
+ );
1368
+ }
1369
+
1370
+ // src/components/StateFeedback.tsx
1371
+ import { AlertTriangle } from "lucide-react";
1372
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1373
+ function ErrorBanner({ message }) {
1374
+ return /* @__PURE__ */ jsxs6(
1375
+ "p",
1376
+ {
1377
+ role: "alert",
1378
+ className: "flex items-start gap-2 rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800 dark:border-red-900 dark:bg-red-950/50 dark:text-red-200",
1379
+ children: [
1380
+ /* @__PURE__ */ jsx9(AlertTriangle, { "aria-hidden": "true", className: "mt-0.5 h-4 w-4 shrink-0" }),
1381
+ message
1382
+ ]
1383
+ }
1384
+ );
1385
+ }
1386
+ function EmptyState({ icon: Icon, title, hint, action }) {
1387
+ return /* @__PURE__ */ jsxs6("div", { className: `${SURFACE_BORDER} flex flex-col items-center gap-2 px-6 py-12 text-center`, children: [
1388
+ /* @__PURE__ */ jsx9("span", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-gray-100 text-gray-400 dark:bg-gray-800 dark:text-gray-500", children: /* @__PURE__ */ jsx9(Icon, { "aria-hidden": "true", className: "h-6 w-6" }) }),
1389
+ /* @__PURE__ */ jsx9("p", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: title }),
1390
+ hint && /* @__PURE__ */ jsx9("p", { className: "max-w-sm text-sm text-gray-500 dark:text-gray-400", children: hint }),
1391
+ action && /* @__PURE__ */ jsx9("div", { className: "mt-2", children: action })
1392
+ ] });
1393
+ }
1394
+ var SKELETON_ROW = "h-11 animate-pulse rounded-lg bg-gray-100 dark:bg-gray-800";
1395
+ function ListSkeleton({ label, rows = 5 }) {
1396
+ return /* @__PURE__ */ jsx9("div", { "aria-busy": "true", "aria-label": label, role: "status", className: "space-y-2", children: Array.from({ length: rows }, (_row, index) => /* @__PURE__ */ jsx9("div", { className: SKELETON_ROW }, index)) });
1397
+ }
1398
+ function BlockSkeleton({ label }) {
1399
+ return /* @__PURE__ */ jsx9(
1400
+ "div",
1401
+ {
1402
+ "aria-busy": "true",
1403
+ "aria-label": label,
1404
+ role: "status",
1405
+ className: "min-h-64 flex-1 animate-pulse rounded-xl bg-gray-100 dark:bg-gray-800"
1406
+ }
1407
+ );
1408
+ }
1409
+
632
1410
  // src/workspace/AgendaArea.tsx
633
- import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1411
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
634
1412
  var DAY_IN_MS = 24 * 60 * 6e4;
1413
+ var NOW_TICK_MS = 6e4;
635
1414
  function startOfWeek(date, weekStartsOn) {
636
1415
  const start = startOfDay(date);
637
1416
  const offset = (start.getDay() - weekStartsOn + 7) % 7;
@@ -642,14 +1421,32 @@ function buildVisibleDays(anchorDate, view, weekStartsOn) {
642
1421
  const weekStart = startOfWeek(anchorDate, weekStartsOn);
643
1422
  return Array.from({ length: 7 }, (_day, index) => new Date(weekStart.getTime() + index * DAY_IN_MS));
644
1423
  }
645
- 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";
1424
+ function formatPeriod(days, locale) {
1425
+ const first = days[0];
1426
+ if (days.length === 1) {
1427
+ return first.toLocaleDateString(locale, { weekday: "long", day: "2-digit", month: "long", year: "numeric" });
1428
+ }
1429
+ const last = days[days.length - 1];
1430
+ const short = { day: "2-digit", month: "short" };
1431
+ return `${first.toLocaleDateString(locale, short)} \u2013 ${last.toLocaleDateString(locale, { ...short, year: "numeric" })}`;
1432
+ }
1433
+ function useNow() {
1434
+ const [now, setNow] = useState3(() => /* @__PURE__ */ new Date());
1435
+ useEffect3(() => {
1436
+ const timer = window.setInterval(() => setNow(/* @__PURE__ */ new Date()), NOW_TICK_MS);
1437
+ return () => window.clearInterval(timer);
1438
+ }, []);
1439
+ return now;
1440
+ }
646
1441
  function AgendaArea() {
647
1442
  const { locale, weekStartsOn } = useSchedulingConfig();
648
1443
  const messages = resolveSchedulingMessages(locale);
1444
+ const now = useNow();
649
1445
  const { data: resourcesPage } = useResources({ active: true, pageSize: MAX_PAGE_SIZE });
650
- const [resourceId, setResourceId] = useState("");
651
- const [view, setView] = useState("day");
652
- const [anchorDate, setAnchorDate] = useState(() => /* @__PURE__ */ new Date());
1446
+ const [resourceId, setResourceId] = useState3("");
1447
+ const [view, setView] = useState3("day");
1448
+ const [anchorDate, setAnchorDate] = useState3(() => /* @__PURE__ */ new Date());
1449
+ const [selectedBookingId, setSelectedBookingId] = useState3(void 0);
653
1450
  const resources = resourcesPage?.data ?? [];
654
1451
  const days = buildVisibleDays(anchorDate, view, weekStartsOn);
655
1452
  const from = days[0];
@@ -657,70 +1454,115 @@ function AgendaArea() {
657
1454
  const { data: bookingsPage, isLoading, isError } = useBookings(
658
1455
  resourceId ? { resourceId, from, until, pageSize: MAX_PAGE_SIZE } : { from, until, pageSize: MAX_PAGE_SIZE }
659
1456
  );
660
- function shiftAnchor(days_) {
661
- setAnchorDate((current) => new Date(current.getTime() + days_ * DAY_IN_MS));
1457
+ const visibleBookings = bookingsPage?.data ?? [];
1458
+ const selectedBooking = visibleBookings.find((booking) => booking.id === selectedBookingId);
1459
+ const selectedResourceTimezone = resources.find(
1460
+ (resource) => resource.id === selectedBooking?.resourceIds[0]
1461
+ )?.timezone;
1462
+ function shiftAnchor(direction) {
1463
+ const step = view === "day" ? 1 : 7;
1464
+ setAnchorDate((current) => new Date(current.getTime() + direction * step * DAY_IN_MS));
1465
+ }
1466
+ function renderViewOption(value, label) {
1467
+ const isActive = view === value;
1468
+ return /* @__PURE__ */ jsx10(
1469
+ "button",
1470
+ {
1471
+ type: "button",
1472
+ onClick: () => setView(value),
1473
+ "aria-pressed": isActive,
1474
+ className: `${BUTTON_BASE} flex-1 ${isActive ? "bg-white text-brand-700 shadow-sm dark:bg-gray-900 dark:text-brand-300" : "text-gray-600 dark:text-gray-400"}`,
1475
+ children: label
1476
+ }
1477
+ );
662
1478
  }
663
- return /* @__PURE__ */ jsxs2("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col p-4 space-y-4", children: [
664
- /* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-center gap-2", children: [
665
- /* @__PURE__ */ jsxs2("label", { className: "flex items-center gap-2 text-sm", children: [
666
- /* @__PURE__ */ jsx3("span", { className: "font-medium text-gray-700 dark:text-gray-300", children: messages["agenda.resourceLabel"] }),
667
- /* @__PURE__ */ jsxs2(
668
- "select",
1479
+ return /* @__PURE__ */ jsxs7("div", { className: "flex flex-1 min-h-0 min-w-0", children: [
1480
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col gap-4 overflow-y-auto p-4", children: [
1481
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-end gap-3", children: [
1482
+ /* @__PURE__ */ jsx10(
1483
+ ResourceSelect,
669
1484
  {
1485
+ label: messages["agenda.resourceLabel"],
1486
+ emptyOptionLabel: messages["agenda.allResources"],
1487
+ resources,
670
1488
  value: resourceId,
671
- onChange: (event) => setResourceId(event.target.value),
672
- className: "min-h-11 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 text-sm",
673
- children: [
674
- /* @__PURE__ */ jsx3("option", { value: "", children: "\u2014" }),
675
- resources.map((resource) => /* @__PURE__ */ jsx3("option", { value: resource.id, children: resource.name }, resource.id))
676
- ]
677
- }
678
- )
679
- ] }),
680
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1 ml-auto", children: [
681
- /* @__PURE__ */ jsx3("button", { type: "button", onClick: () => shiftAnchor(view === "day" ? -1 : -7), className: NAV_BUTTON_CLASS, children: messages["agenda.previous"] }),
682
- /* @__PURE__ */ jsx3("button", { type: "button", onClick: () => setAnchorDate(/* @__PURE__ */ new Date()), className: NAV_BUTTON_CLASS, children: messages["agenda.today"] }),
683
- /* @__PURE__ */ jsx3("button", { type: "button", onClick: () => shiftAnchor(view === "day" ? 1 : 7), className: NAV_BUTTON_CLASS, children: messages["agenda.next"] })
684
- ] }),
685
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1", children: [
686
- /* @__PURE__ */ jsx3(
687
- "button",
688
- {
689
- type: "button",
690
- onClick: () => setView("day"),
691
- "aria-current": view === "day" ? "true" : void 0,
692
- className: `${NAV_BUTTON_CLASS} ${view === "day" ? "bg-brand-600 text-white border-brand-600" : ""}`,
693
- children: messages["agenda.viewDay"]
1489
+ onChange: setResourceId
694
1490
  }
695
1491
  ),
696
- /* @__PURE__ */ jsx3(
697
- "button",
1492
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1", children: [
1493
+ /* @__PURE__ */ jsx10(
1494
+ "button",
1495
+ {
1496
+ type: "button",
1497
+ "aria-label": messages["agenda.previous"],
1498
+ onClick: () => shiftAnchor(-1),
1499
+ className: ICON_BUTTON,
1500
+ children: /* @__PURE__ */ jsx10(ChevronLeft, { "aria-hidden": "true", className: "h-4 w-4" })
1501
+ }
1502
+ ),
1503
+ /* @__PURE__ */ jsx10("button", { type: "button", onClick: () => setAnchorDate(/* @__PURE__ */ new Date()), className: BUTTON_SECONDARY, children: messages["agenda.today"] }),
1504
+ /* @__PURE__ */ jsx10(
1505
+ "button",
1506
+ {
1507
+ type: "button",
1508
+ "aria-label": messages["agenda.next"],
1509
+ onClick: () => shiftAnchor(1),
1510
+ className: ICON_BUTTON,
1511
+ children: /* @__PURE__ */ jsx10(ChevronRight, { "aria-hidden": "true", className: "h-4 w-4" })
1512
+ }
1513
+ )
1514
+ ] }),
1515
+ /* @__PURE__ */ jsx10(
1516
+ "p",
698
1517
  {
699
- type: "button",
700
- onClick: () => setView("week"),
701
- "aria-current": view === "week" ? "true" : void 0,
702
- className: `${NAV_BUTTON_CLASS} ${view === "week" ? "bg-brand-600 text-white border-brand-600" : ""}`,
703
- children: messages["agenda.viewWeek"]
1518
+ "aria-live": "polite",
1519
+ className: "min-w-0 flex-1 truncate text-sm font-semibold text-gray-900 first-letter:uppercase dark:text-gray-100",
1520
+ children: formatPeriod(days, locale)
704
1521
  }
705
- )
706
- ] })
1522
+ ),
1523
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1", children: [
1524
+ /* @__PURE__ */ jsx10("span", { className: FIELD_LABEL, children: messages["agenda.viewLabel"] }),
1525
+ /* @__PURE__ */ jsxs7("div", { className: "flex gap-1 rounded-lg bg-gray-100 p-1 dark:bg-gray-800", children: [
1526
+ renderViewOption("day", messages["agenda.viewDay"]),
1527
+ renderViewOption("week", messages["agenda.viewWeek"])
1528
+ ] })
1529
+ ] })
1530
+ ] }),
1531
+ isError && /* @__PURE__ */ jsx10(ErrorBanner, { message: messages["common.loadFailure"] }),
1532
+ bookingsPage && bookingsPage.total > bookingsPage.data.length && /* @__PURE__ */ jsx10("p", { className: "rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-800 dark:border-amber-900 dark:bg-amber-950/50 dark:text-amber-200", children: messages["agenda.moreResults"] }),
1533
+ isLoading && /* @__PURE__ */ jsx10(BlockSkeleton, { label: messages["common.loading"] }),
1534
+ !isLoading && !isError && visibleBookings.length === 0 && /* @__PURE__ */ jsx10(EmptyState, { icon: CalendarDays, title: messages["agenda.emptyTitle"], hint: messages["agenda.empty"] }),
1535
+ !isLoading && visibleBookings.length > 0 && /* @__PURE__ */ jsx10(
1536
+ AgendaGrid,
1537
+ {
1538
+ bookings: visibleBookings,
1539
+ days,
1540
+ now,
1541
+ onSelect: (booking) => setSelectedBookingId(booking.id)
1542
+ }
1543
+ )
707
1544
  ] }),
708
- 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"] }),
709
- isLoading ? /* @__PURE__ */ jsx3("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.loading"] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
710
- bookingsPage && bookingsPage.total > bookingsPage.data.length && /* @__PURE__ */ jsx3("p", { className: "text-sm text-amber-700 bg-amber-50 rounded-lg px-3 py-2", children: messages["agenda.moreResults"] }),
711
- /* @__PURE__ */ jsx3(AgendaGrid, { bookings: bookingsPage?.data ?? [], days })
712
- ] })
1545
+ selectedBooking && /* @__PURE__ */ jsx10(
1546
+ BookingDrawer,
1547
+ {
1548
+ booking: selectedBooking,
1549
+ resourceTimezone: selectedResourceTimezone,
1550
+ onClose: () => setSelectedBookingId(void 0)
1551
+ },
1552
+ selectedBooking.id
1553
+ )
713
1554
  ] });
714
1555
  }
715
1556
 
716
1557
  // src/workspace/AvailabilityArea.tsx
717
- import { useState as useState4 } from "react";
1558
+ import { Clock } from "lucide-react";
1559
+ import { useState as useState6 } from "react";
718
1560
  import { MAX_PAGE_SIZE as MAX_PAGE_SIZE2 } from "@adatechnology/scheduling-contracts";
719
1561
 
720
1562
  // src/components/WeeklyRulesEditor.tsx
721
1563
  import { Plus, Trash2 } from "lucide-react";
722
- import { useState as useState2 } from "react";
723
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1564
+ import { useState as useState4, useMemo as useMemo5 } from "react";
1565
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
724
1566
  var WEEKDAYS = [0, 1, 2, 3, 4, 5, 6];
725
1567
  function toDraft(rule) {
726
1568
  return { weekday: rule.weekday, startsAtLocal: rule.startsAtLocal, endsAtLocal: rule.endsAtLocal };
@@ -731,9 +1573,16 @@ function createEmptyDraft() {
731
1573
  function WeeklyRulesEditor({ resourceId }) {
732
1574
  const { locale } = useSchedulingConfig();
733
1575
  const messages = resolveSchedulingMessages(locale);
1576
+ const weekdayOptions = useMemo5(
1577
+ () => WEEKDAYS.map((weekday) => ({
1578
+ value: String(weekday),
1579
+ label: messages[`availability.weekday.${weekday}`]
1580
+ })),
1581
+ [messages]
1582
+ );
734
1583
  const { data, isSuccess: isRulesLoaded, isError: isRulesLoadError } = useAvailabilityRules(resourceId);
735
1584
  const setAvailabilityRules = useSetAvailabilityRules();
736
- const [draft, setDraft] = useState2(void 0);
1585
+ const [draft, setDraft] = useState4(void 0);
737
1586
  const rules = draft ?? (data ? data.map(toDraft) : []);
738
1587
  function updateRule(index, patch) {
739
1588
  setDraft(rules.map((rule, ruleIndex) => ruleIndex === index ? { ...rule, ...patch } : rule));
@@ -755,32 +1604,32 @@ function WeeklyRulesEditor({ resourceId }) {
755
1604
  }
756
1605
  }
757
1606
  if (isRulesLoadError) {
758
- return /* @__PURE__ */ jsxs3("section", { className: "space-y-3", children: [
759
- /* @__PURE__ */ jsx4("h3", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: messages["availability.weeklyRulesTitle"] }),
760
- /* @__PURE__ */ jsx4("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.loadFailure"] })
1607
+ return /* @__PURE__ */ jsxs8("section", { className: "space-y-3", children: [
1608
+ /* @__PURE__ */ jsx11("h3", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: messages["availability.weeklyRulesTitle"] }),
1609
+ /* @__PURE__ */ jsx11("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.loadFailure"] })
761
1610
  ] });
762
1611
  }
763
1612
  if (!isRulesLoaded) {
764
- return /* @__PURE__ */ jsxs3("section", { className: "space-y-3", children: [
765
- /* @__PURE__ */ jsx4("h3", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: messages["availability.weeklyRulesTitle"] }),
766
- /* @__PURE__ */ jsx4("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.loading"] })
1613
+ return /* @__PURE__ */ jsxs8("section", { className: "space-y-3", children: [
1614
+ /* @__PURE__ */ jsx11("h3", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: messages["availability.weeklyRulesTitle"] }),
1615
+ /* @__PURE__ */ jsx11("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.loading"] })
767
1616
  ] });
768
1617
  }
769
- return /* @__PURE__ */ jsxs3("section", { className: "space-y-3", children: [
770
- /* @__PURE__ */ jsx4("h3", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: messages["availability.weeklyRulesTitle"] }),
771
- 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"] }),
772
- /* @__PURE__ */ jsx4("ul", { className: "space-y-2", children: rules.map((rule, index) => /* @__PURE__ */ jsxs3("li", { className: "flex items-center gap-2", children: [
773
- /* @__PURE__ */ jsx4(
774
- "select",
1618
+ return /* @__PURE__ */ jsxs8("section", { className: "space-y-3", children: [
1619
+ /* @__PURE__ */ jsx11("h3", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: messages["availability.weeklyRulesTitle"] }),
1620
+ setAvailabilityRules.isError && /* @__PURE__ */ jsx11("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.actionFailure"] }),
1621
+ /* @__PURE__ */ jsx11("ul", { className: "space-y-2", children: rules.map((rule, index) => /* @__PURE__ */ jsxs8("li", { className: "flex items-center gap-2", children: [
1622
+ /* @__PURE__ */ jsx11(
1623
+ SelectField,
775
1624
  {
776
- "aria-label": messages["availability.weekdayLabel"],
777
- value: rule.weekday,
778
- onChange: (event) => updateRule(index, { weekday: Number(event.target.value) }),
779
- className: "min-h-11 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 text-sm",
780
- children: WEEKDAYS.map((weekday) => /* @__PURE__ */ jsx4("option", { value: weekday, children: messages[`availability.weekday.${weekday}`] }, weekday))
1625
+ hideLabel: true,
1626
+ label: messages["availability.weekdayLabel"],
1627
+ value: String(rule.weekday),
1628
+ options: weekdayOptions,
1629
+ onChange: (next) => updateRule(index, { weekday: Number(next) })
781
1630
  }
782
1631
  ),
783
- /* @__PURE__ */ jsx4(
1632
+ /* @__PURE__ */ jsx11(
784
1633
  "input",
785
1634
  {
786
1635
  "aria-label": messages["availability.startsAtLocal"],
@@ -790,7 +1639,7 @@ function WeeklyRulesEditor({ resourceId }) {
790
1639
  className: "min-h-11 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 text-sm"
791
1640
  }
792
1641
  ),
793
- /* @__PURE__ */ jsx4(
1642
+ /* @__PURE__ */ jsx11(
794
1643
  "input",
795
1644
  {
796
1645
  "aria-label": messages["availability.endsAtLocal"],
@@ -800,31 +1649,31 @@ function WeeklyRulesEditor({ resourceId }) {
800
1649
  className: "min-h-11 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 text-sm"
801
1650
  }
802
1651
  ),
803
- /* @__PURE__ */ jsx4(
1652
+ /* @__PURE__ */ jsx11(
804
1653
  "button",
805
1654
  {
806
1655
  type: "button",
807
1656
  onClick: () => removeRule(index),
808
1657
  "aria-label": messages["availability.removeRule"],
809
1658
  className: "min-h-11 min-w-11 inline-flex items-center justify-center rounded-lg text-red-700 hover:bg-red-50",
810
- children: /* @__PURE__ */ jsx4(Trash2, { "aria-hidden": "true", className: "w-4 h-4" })
1659
+ children: /* @__PURE__ */ jsx11(Trash2, { "aria-hidden": "true", className: "w-4 h-4" })
811
1660
  }
812
1661
  )
813
1662
  ] }, index)) }),
814
- /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
815
- /* @__PURE__ */ jsxs3(
1663
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
1664
+ /* @__PURE__ */ jsxs8(
816
1665
  "button",
817
1666
  {
818
1667
  type: "button",
819
1668
  onClick: addRule,
820
1669
  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",
821
1670
  children: [
822
- /* @__PURE__ */ jsx4(Plus, { "aria-hidden": "true", className: "w-4 h-4" }),
1671
+ /* @__PURE__ */ jsx11(Plus, { "aria-hidden": "true", className: "w-4 h-4" }),
823
1672
  messages["availability.addRule"]
824
1673
  ]
825
1674
  }
826
1675
  ),
827
- /* @__PURE__ */ jsx4(
1676
+ /* @__PURE__ */ jsx11(
828
1677
  "button",
829
1678
  {
830
1679
  type: "button",
@@ -833,186 +1682,35 @@ function WeeklyRulesEditor({ resourceId }) {
833
1682
  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",
834
1683
  children: messages["common.save"]
835
1684
  }
836
- )
837
- ] })
838
- ] });
839
- }
840
-
841
- // src/components/AvailabilityExceptionsEditor.tsx
842
- import { Plus as Plus2, Trash2 as Trash22 } from "lucide-react";
843
- import { useState as useState3 } from "react";
844
- import { AVAILABILITY_EXCEPTION_KIND } from "@adatechnology/scheduling-contracts";
845
-
846
- // src/components/DateTimeField.tsx
847
- import { useId } from "react";
848
-
849
- // src/components/dateTimeParts.util.ts
850
- var DATE_TIME_LOCAL_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/;
851
- var MONTHS_IN_YEAR = 12;
852
- function parseDateTimeParts(value) {
853
- const match = DATE_TIME_LOCAL_PATTERN.exec(value);
854
- if (!match) return void 0;
855
- const [, year, month, day, hour, minute] = match;
856
- return { year: Number(year), month: Number(month), day: Number(day), time: `${hour}:${minute}` };
857
- }
858
- function daysInMonth(params) {
859
- return new Date(Date.UTC(params.year, params.month, 0)).getUTCDate();
860
- }
861
- function pad(value) {
862
- return String(value).padStart(2, "0");
863
- }
864
- function formatDateTimeParts(parts) {
865
- const lastDay = daysInMonth({ year: parts.year, month: parts.month });
866
- const day = Math.min(Math.max(parts.day, 1), lastDay);
867
- return `${String(parts.year).padStart(4, "0")}-${pad(parts.month)}-${pad(day)}T${parts.time}`;
868
- }
869
- function buildYearOptions(params) {
870
- const current = params.today.getFullYear();
871
- const first = Math.min(current - params.past, params.year);
872
- const last = Math.max(current + params.future, params.year);
873
- return Array.from({ length: last - first + 1 }, (_, index) => first + index);
874
- }
875
-
876
- // src/components/DateTimeField.tsx
877
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
878
- var FIELD_CLASS = "min-h-11 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 text-sm";
879
- var YEAR_WINDOW = { PAST: 1, FUTURE: 2 };
880
- function partsOf(value, fallback) {
881
- return parseDateTimeParts(value) ?? {
882
- year: fallback.getFullYear(),
883
- month: fallback.getMonth() + 1,
884
- day: fallback.getDate(),
885
- time: "09:00"
886
- };
887
- }
888
- function monthLabels(locale) {
889
- const format = new Intl.DateTimeFormat(locale, { month: "long", timeZone: "UTC" });
890
- return Array.from(
891
- { length: MONTHS_IN_YEAR },
892
- (_, index) => format.format(new Date(Date.UTC(2026, index, 1)))
893
- );
894
- }
895
- function DateTimeField({ label, value, onChange, emptyDefault }) {
896
- const { locale } = useSchedulingConfig();
897
- const messages = resolveSchedulingMessages(locale);
898
- const groupId = useId();
899
- const today = emptyDefault ?? /* @__PURE__ */ new Date();
900
- const parts = partsOf(value, today);
901
- const years = buildYearOptions({
902
- year: parts.year,
903
- today,
904
- past: YEAR_WINDOW.PAST,
905
- future: YEAR_WINDOW.FUTURE
906
- });
907
- const days = daysInMonth({ year: parts.year, month: parts.month });
908
- const months = monthLabels(locale);
909
- function change(patch) {
910
- onChange(formatDateTimeParts({ ...parts, ...patch }));
911
- }
912
- return /* @__PURE__ */ jsxs4("div", { role: "group", "aria-labelledby": groupId, className: "flex flex-wrap items-center gap-2", children: [
913
- /* @__PURE__ */ jsx5("span", { id: groupId, className: "sr-only", children: label }),
914
- /* @__PURE__ */ jsx5(
915
- "select",
916
- {
917
- "aria-label": `${label} \u2014 ${messages["datetime.year"]}`,
918
- value: parts.year,
919
- onChange: (event) => change({ year: Number(event.target.value) }),
920
- className: FIELD_CLASS,
921
- children: years.map((year) => /* @__PURE__ */ jsx5("option", { value: year, children: year }, year))
922
- }
923
- ),
924
- /* @__PURE__ */ jsx5(
925
- "select",
926
- {
927
- "aria-label": `${label} \u2014 ${messages["datetime.month"]}`,
928
- value: parts.month,
929
- onChange: (event) => change({ month: Number(event.target.value) }),
930
- className: FIELD_CLASS,
931
- children: months.map((name, index) => /* @__PURE__ */ jsx5("option", { value: index + 1, children: name }, name))
932
- }
933
- ),
934
- /* @__PURE__ */ jsx5(
935
- "select",
936
- {
937
- "aria-label": `${label} \u2014 ${messages["datetime.day"]}`,
938
- value: Math.min(parts.day, days),
939
- onChange: (event) => change({ day: Number(event.target.value) }),
940
- className: FIELD_CLASS,
941
- children: Array.from({ length: days }, (_, index) => index + 1).map((day) => /* @__PURE__ */ jsx5("option", { value: day, children: day }, day))
942
- }
943
- ),
944
- /* @__PURE__ */ jsx5(
945
- "input",
946
- {
947
- type: "time",
948
- "aria-label": `${label} \u2014 ${messages["datetime.time"]}`,
949
- value: parts.time,
950
- onChange: (event) => change({ time: event.target.value }),
951
- className: FIELD_CLASS
952
- }
953
- )
1685
+ )
1686
+ ] })
954
1687
  ] });
955
1688
  }
956
1689
 
957
- // src/components/datetimeLocal.util.ts
958
- function getTimeZoneOffsetMs(instant, timeZone) {
959
- const parts = new Intl.DateTimeFormat("en-US", {
960
- timeZone,
961
- year: "numeric",
962
- month: "2-digit",
963
- day: "2-digit",
964
- hour: "2-digit",
965
- minute: "2-digit",
966
- second: "2-digit",
967
- hourCycle: "h23"
968
- }).formatToParts(instant);
969
- const get = (type) => Number(parts.find((part) => part.type === type)?.value ?? 0);
970
- const asUtcOfWallClock = Date.UTC(
971
- get("year"),
972
- get("month") - 1,
973
- get("day"),
974
- get("hour") % 24,
975
- get("minute"),
976
- get("second")
977
- );
978
- return asUtcOfWallClock - instant.getTime();
979
- }
980
- function parseDateTimeLocalInTimeZone(value, timeZone) {
981
- const naiveUtc = /* @__PURE__ */ new Date(`${value}:00.000Z`);
982
- const firstOffset = getTimeZoneOffsetMs(naiveUtc, timeZone);
983
- const firstPass = new Date(naiveUtc.getTime() - firstOffset);
984
- const secondOffset = getTimeZoneOffsetMs(firstPass, timeZone);
985
- return new Date(naiveUtc.getTime() - secondOffset);
986
- }
987
- function formatDateTimeLocalInTimeZone(instant, timeZone) {
988
- const parts = new Intl.DateTimeFormat("en-US", {
989
- timeZone,
990
- year: "numeric",
991
- month: "2-digit",
992
- day: "2-digit",
993
- hour: "2-digit",
994
- minute: "2-digit",
995
- hourCycle: "h23"
996
- }).formatToParts(instant);
997
- const get = (type) => parts.find((part) => part.type === type)?.value ?? "00";
998
- const hour = get("hour") === "24" ? "00" : get("hour");
999
- return `${get("year")}-${get("month")}-${get("day")}T${hour}:${get("minute")}`;
1000
- }
1001
-
1002
1690
  // src/components/AvailabilityExceptionsEditor.tsx
1003
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1691
+ import { Plus as Plus2, Trash2 as Trash22 } from "lucide-react";
1692
+ import { useState as useState5, useMemo as useMemo6 } from "react";
1693
+ import { AVAILABILITY_EXCEPTION_KIND } from "@adatechnology/scheduling-contracts";
1694
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1004
1695
  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";
1005
1696
  function AvailabilityExceptionsEditor({ resourceId, timezone }) {
1006
1697
  const { locale } = useSchedulingConfig();
1007
1698
  const messages = resolveSchedulingMessages(locale);
1699
+ const kindOptions = useMemo6(
1700
+ () => [
1701
+ { value: AVAILABILITY_EXCEPTION_KIND.BLOCK, label: messages["availability.exceptionKind.blocked"] },
1702
+ { value: AVAILABILITY_EXCEPTION_KIND.EXTRA, label: messages["availability.exceptionKind.extra"] }
1703
+ ],
1704
+ [messages]
1705
+ );
1008
1706
  const { data, isLoading, isError: isLoadError } = useAvailabilityExceptions(resourceId);
1009
1707
  const addException = useAddAvailabilityException();
1010
1708
  const removeException = useRemoveAvailabilityException();
1011
1709
  const emptyValue = () => formatDateTimeLocalInTimeZone(/* @__PURE__ */ new Date(), timezone);
1012
- const [from, setFrom] = useState3(emptyValue);
1013
- const [until, setUntil] = useState3(emptyValue);
1014
- const [kind, setKind] = useState3(AVAILABILITY_EXCEPTION_KIND.BLOCK);
1015
- const [reason, setReason] = useState3("");
1710
+ const [from, setFrom] = useState5(emptyValue);
1711
+ const [until, setUntil] = useState5(emptyValue);
1712
+ const [kind, setKind] = useState5(AVAILABILITY_EXCEPTION_KIND.BLOCK);
1713
+ const [reason, setReason] = useState5("");
1016
1714
  async function handleAdd() {
1017
1715
  if (!from || !until) return;
1018
1716
  try {
@@ -1031,13 +1729,13 @@ function AvailabilityExceptionsEditor({ resourceId, timezone }) {
1031
1729
  } catch {
1032
1730
  }
1033
1731
  }
1034
- return /* @__PURE__ */ jsxs5("section", { className: "space-y-3", children: [
1035
- /* @__PURE__ */ jsx6("h3", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: messages["availability.exceptionsTitle"] }),
1036
- (addException.isError || removeException.isError) && /* @__PURE__ */ jsx6("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.actionFailure"] }),
1037
- isLoadError && /* @__PURE__ */ jsx6("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.loadFailure"] }),
1038
- isLoading && /* @__PURE__ */ jsx6("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.loading"] }),
1039
- /* @__PURE__ */ jsx6("ul", { className: "space-y-2", children: (data ?? []).map((exception) => /* @__PURE__ */ jsxs5("li", { className: "flex items-center gap-2 text-sm", children: [
1040
- /* @__PURE__ */ jsxs5("span", { className: "flex-1", children: [
1732
+ return /* @__PURE__ */ jsxs9("section", { className: "space-y-3", children: [
1733
+ /* @__PURE__ */ jsx12("h3", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: messages["availability.exceptionsTitle"] }),
1734
+ (addException.isError || removeException.isError) && /* @__PURE__ */ jsx12("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.actionFailure"] }),
1735
+ isLoadError && /* @__PURE__ */ jsx12("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.loadFailure"] }),
1736
+ isLoading && /* @__PURE__ */ jsx12("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.loading"] }),
1737
+ /* @__PURE__ */ jsx12("ul", { className: "space-y-2", children: (data ?? []).map((exception) => /* @__PURE__ */ jsxs9("li", { className: "flex items-center gap-2 text-sm", children: [
1738
+ /* @__PURE__ */ jsxs9("span", { className: "flex-1", children: [
1041
1739
  messages[`availability.exceptionKind.${exception.kind === "block" ? "blocked" : "extra"}`],
1042
1740
  " \u2014",
1043
1741
  " ",
@@ -1046,19 +1744,19 @@ function AvailabilityExceptionsEditor({ resourceId, timezone }) {
1046
1744
  exception.during.end.toLocaleString(locale),
1047
1745
  exception.reason ? ` (${exception.reason})` : ""
1048
1746
  ] }),
1049
- /* @__PURE__ */ jsx6(
1747
+ /* @__PURE__ */ jsx12(
1050
1748
  "button",
1051
1749
  {
1052
1750
  type: "button",
1053
1751
  onClick: () => removeException.mutate({ id: exception.id, resourceId }),
1054
1752
  "aria-label": messages["availability.removeException"],
1055
1753
  className: "min-h-11 min-w-11 inline-flex items-center justify-center rounded-lg text-red-700 hover:bg-red-50",
1056
- children: /* @__PURE__ */ jsx6(Trash22, { "aria-hidden": "true", className: "w-4 h-4" })
1754
+ children: /* @__PURE__ */ jsx12(Trash22, { "aria-hidden": "true", className: "w-4 h-4" })
1057
1755
  }
1058
1756
  )
1059
1757
  ] }, exception.id)) }),
1060
- /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center gap-2", children: [
1061
- /* @__PURE__ */ jsx6(
1758
+ /* @__PURE__ */ jsxs9("div", { className: "flex flex-wrap items-center gap-2", children: [
1759
+ /* @__PURE__ */ jsx12(
1062
1760
  DateTimeField,
1063
1761
  {
1064
1762
  label: messages["availability.exceptionFrom"],
@@ -1066,7 +1764,7 @@ function AvailabilityExceptionsEditor({ resourceId, timezone }) {
1066
1764
  onChange: setFrom
1067
1765
  }
1068
1766
  ),
1069
- /* @__PURE__ */ jsx6(
1767
+ /* @__PURE__ */ jsx12(
1070
1768
  DateTimeField,
1071
1769
  {
1072
1770
  label: messages["availability.exceptionUntil"],
@@ -1074,20 +1772,17 @@ function AvailabilityExceptionsEditor({ resourceId, timezone }) {
1074
1772
  onChange: setUntil
1075
1773
  }
1076
1774
  ),
1077
- /* @__PURE__ */ jsxs5(
1078
- "select",
1775
+ /* @__PURE__ */ jsx12(
1776
+ SelectField,
1079
1777
  {
1080
- "aria-label": messages["availability.exceptionKind.blocked"],
1778
+ hideLabel: true,
1779
+ label: messages["availability.exceptionsTitle"],
1081
1780
  value: kind,
1082
- onChange: (event) => setKind(event.target.value),
1083
- className: SELECT_CLASS,
1084
- children: [
1085
- /* @__PURE__ */ jsx6("option", { value: AVAILABILITY_EXCEPTION_KIND.BLOCK, children: messages["availability.exceptionKind.blocked"] }),
1086
- /* @__PURE__ */ jsx6("option", { value: AVAILABILITY_EXCEPTION_KIND.EXTRA, children: messages["availability.exceptionKind.extra"] })
1087
- ]
1781
+ options: kindOptions,
1782
+ onChange: (next) => setKind(next)
1088
1783
  }
1089
1784
  ),
1090
- /* @__PURE__ */ jsx6(
1785
+ /* @__PURE__ */ jsx12(
1091
1786
  "input",
1092
1787
  {
1093
1788
  "aria-label": messages["availability.exceptionReason"],
@@ -1098,7 +1793,7 @@ function AvailabilityExceptionsEditor({ resourceId, timezone }) {
1098
1793
  className: SELECT_CLASS
1099
1794
  }
1100
1795
  ),
1101
- /* @__PURE__ */ jsxs5(
1796
+ /* @__PURE__ */ jsxs9(
1102
1797
  "button",
1103
1798
  {
1104
1799
  type: "button",
@@ -1106,7 +1801,7 @@ function AvailabilityExceptionsEditor({ resourceId, timezone }) {
1106
1801
  disabled: addException.isPending,
1107
1802
  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",
1108
1803
  children: [
1109
- /* @__PURE__ */ jsx6(Plus2, { "aria-hidden": "true", className: "w-4 h-4" }),
1804
+ /* @__PURE__ */ jsx12(Plus2, { "aria-hidden": "true", className: "w-4 h-4" }),
1110
1805
  messages["availability.addException"]
1111
1806
  ]
1112
1807
  }
@@ -1116,51 +1811,54 @@ function AvailabilityExceptionsEditor({ resourceId, timezone }) {
1116
1811
  }
1117
1812
 
1118
1813
  // src/components/AvailabilityEditor.tsx
1119
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1814
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1120
1815
  function AvailabilityEditor({ resourceId, timezone }) {
1121
1816
  const { locale } = useSchedulingConfig();
1122
1817
  const messages = resolveSchedulingMessages(locale);
1123
- return /* @__PURE__ */ jsxs6("div", { className: "space-y-6", children: [
1124
- /* @__PURE__ */ jsxs6("p", { className: "inline-flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400", children: [
1125
- /* @__PURE__ */ jsxs6("span", { className: "font-medium", children: [
1818
+ return /* @__PURE__ */ jsxs10("div", { className: "space-y-6", children: [
1819
+ /* @__PURE__ */ jsxs10("p", { className: "inline-flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400", children: [
1820
+ /* @__PURE__ */ jsxs10("span", { className: "font-medium", children: [
1126
1821
  messages["availability.resourceTimezone"],
1127
1822
  ":"
1128
1823
  ] }),
1129
1824
  timezone
1130
1825
  ] }),
1131
- /* @__PURE__ */ jsx7(WeeklyRulesEditor, { resourceId }),
1132
- /* @__PURE__ */ jsx7(AvailabilityExceptionsEditor, { resourceId, timezone })
1826
+ /* @__PURE__ */ jsx13(WeeklyRulesEditor, { resourceId }),
1827
+ /* @__PURE__ */ jsx13(AvailabilityExceptionsEditor, { resourceId, timezone })
1133
1828
  ] });
1134
1829
  }
1135
1830
 
1136
1831
  // src/workspace/AvailabilityArea.tsx
1137
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1832
+ import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1138
1833
  function AvailabilityArea() {
1139
1834
  const { locale } = useSchedulingConfig();
1140
1835
  const messages = resolveSchedulingMessages(locale);
1141
1836
  const { data, isLoading, isError } = useResources({ active: true, pageSize: MAX_PAGE_SIZE2 });
1142
- const [resourceId, setResourceId] = useState4("");
1837
+ const [resourceId, setResourceId] = useState6("");
1143
1838
  const resources = data?.data ?? [];
1144
1839
  const selectedResource = resources.find((resource) => resource.id === resourceId);
1145
- return /* @__PURE__ */ jsxs7("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col p-4 space-y-4", children: [
1146
- /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2 text-sm", children: [
1147
- /* @__PURE__ */ jsx8("span", { className: "font-medium text-gray-700 dark:text-gray-300", children: messages["agenda.resourceLabel"] }),
1148
- /* @__PURE__ */ jsxs7(
1149
- "select",
1150
- {
1151
- value: resourceId,
1152
- onChange: (event) => setResourceId(event.target.value),
1153
- className: "min-h-11 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 px-2 text-sm",
1154
- children: [
1155
- /* @__PURE__ */ jsx8("option", { value: "", children: "\u2014" }),
1156
- resources.map((resource) => /* @__PURE__ */ jsx8("option", { value: resource.id, children: resource.name }, resource.id))
1157
- ]
1158
- }
1159
- )
1160
- ] }),
1161
- isError && /* @__PURE__ */ jsx8("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.loadFailure"] }),
1162
- isLoading && /* @__PURE__ */ jsx8("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.loading"] }),
1163
- selectedResource && /* @__PURE__ */ jsx8(
1840
+ return /* @__PURE__ */ jsxs11("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col gap-4 overflow-y-auto p-4", children: [
1841
+ /* @__PURE__ */ jsx14(
1842
+ ResourceSelect,
1843
+ {
1844
+ label: messages["agenda.resourceLabel"],
1845
+ emptyOptionLabel: messages["availability.selectResourceTitle"],
1846
+ resources,
1847
+ value: resourceId,
1848
+ onChange: setResourceId
1849
+ }
1850
+ ),
1851
+ isError && /* @__PURE__ */ jsx14(ErrorBanner, { message: messages["common.loadFailure"] }),
1852
+ isLoading && /* @__PURE__ */ jsx14(ListSkeleton, { label: messages["common.loading"], rows: 3 }),
1853
+ !isLoading && !isError && !selectedResource && /* @__PURE__ */ jsx14(
1854
+ EmptyState,
1855
+ {
1856
+ icon: Clock,
1857
+ title: messages["availability.selectResourceTitle"],
1858
+ hint: messages["availability.selectResourceHint"]
1859
+ }
1860
+ ),
1861
+ selectedResource && /* @__PURE__ */ jsx14(
1164
1862
  AvailabilityEditor,
1165
1863
  {
1166
1864
  resourceId: selectedResource.id,
@@ -1172,194 +1870,24 @@ function AvailabilityArea() {
1172
1870
  }
1173
1871
 
1174
1872
  // src/workspace/BookingsArea.tsx
1175
- import { useState as useState8 } from "react";
1873
+ import { ClipboardList } from "lucide-react";
1874
+ import { useState as useState9 } from "react";
1176
1875
  import { MAX_PAGE_SIZE as MAX_PAGE_SIZE3 } from "@adatechnology/scheduling-contracts";
1177
1876
 
1178
- // src/components/BookingDrawer.tsx
1179
- import { XCircle } from "lucide-react";
1180
- import { useState as useState5 } from "react";
1181
- import { BOOKING_STATUS } from "@adatechnology/scheduling-contracts";
1182
-
1183
- // src/components/SidePanel.tsx
1184
- import { X } from "lucide-react";
1185
- import { useEffect } from "react";
1186
- import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1187
- var BUTTON_CLASS = "inline-flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-colors min-h-11";
1188
- function SidePanel({ title, closeLabel, onClose, headerActions, children }) {
1189
- useEffect(() => {
1190
- function handleKeyDown(event) {
1191
- if (event.key === "Escape") onClose();
1192
- }
1193
- window.addEventListener("keydown", handleKeyDown);
1194
- return () => window.removeEventListener("keydown", handleKeyDown);
1195
- }, [onClose]);
1196
- return /* @__PURE__ */ jsxs8(Fragment2, { children: [
1197
- /* @__PURE__ */ jsx9(
1198
- "button",
1199
- {
1200
- type: "button",
1201
- "aria-hidden": "true",
1202
- tabIndex: -1,
1203
- onClick: onClose,
1204
- className: "absolute inset-0 z-10 bg-gray-900/20 wide:hidden"
1205
- }
1206
- ),
1207
- /* @__PURE__ */ jsxs8(
1208
- "section",
1209
- {
1210
- "aria-label": title,
1211
- 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",
1212
- children: [
1213
- /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2 border-b border-gray-200 px-4 py-3 dark:border-gray-700", children: [
1214
- /* @__PURE__ */ jsx9("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100 mr-auto", children: title }),
1215
- headerActions,
1216
- /* @__PURE__ */ jsxs8(
1217
- "button",
1218
- {
1219
- type: "button",
1220
- onClick: onClose,
1221
- className: `${BUTTON_CLASS} text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800`,
1222
- children: [
1223
- /* @__PURE__ */ jsx9(X, { "aria-hidden": "true", className: "w-4 h-4" }),
1224
- closeLabel
1225
- ]
1226
- }
1227
- )
1228
- ] }),
1229
- /* @__PURE__ */ jsx9("div", { className: "min-h-0 flex-1 overflow-y-auto p-4", children })
1230
- ]
1231
- }
1232
- )
1233
- ] });
1234
- }
1235
-
1236
- // src/components/BookingDrawer.tsx
1237
- import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1238
- var BROWSER_TIME_ZONE = Intl.DateTimeFormat().resolvedOptions().timeZone;
1239
- 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";
1240
- var PRIMARY_BUTTON_CLASS = "min-h-11 px-3 rounded-lg text-sm font-medium bg-brand-600 text-white hover:bg-brand-700";
1241
- 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";
1242
- 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";
1243
- function BookingDrawer({ booking, resourceTimezone, onClose }) {
1244
- const { locale } = useSchedulingConfig();
1245
- const messages = resolveSchedulingMessages(locale);
1246
- const confirmBooking = useConfirmBooking();
1247
- const completeBooking = useCompleteBooking();
1248
- const markNoShow = useMarkNoShow();
1249
- const cancelBooking = useCancelBooking();
1250
- const rescheduleBooking = useRescheduleBooking();
1251
- const timezone = resourceTimezone ?? BROWSER_TIME_ZONE;
1252
- const [isCancelling, setIsCancelling] = useState5(false);
1253
- const [cancelledBy, setCancelledBy] = useState5("");
1254
- const [cancellationReason, setCancellationReason] = useState5("");
1255
- const [isRescheduling, setIsRescheduling] = useState5(false);
1256
- const [start, setStart] = useState5(() => formatDateTimeLocalInTimeZone(booking.startsAt, timezone));
1257
- const [end, setEnd] = useState5(() => formatDateTimeLocalInTimeZone(booking.endsAt, timezone));
1258
- async function handleCancel() {
1259
- if (!cancelledBy) return;
1260
- try {
1261
- await cancelBooking.mutateAsync({
1262
- id: booking.id,
1263
- input: { cancelledBy, ...cancellationReason ? { cancellationReason } : {} }
1264
- });
1265
- onClose();
1266
- } catch {
1267
- }
1268
- }
1269
- async function handleReschedule() {
1270
- try {
1271
- await rescheduleBooking.mutateAsync({
1272
- id: booking.id,
1273
- input: {
1274
- during: {
1275
- start: parseDateTimeLocalInTimeZone(start, timezone),
1276
- end: parseDateTimeLocalInTimeZone(end, timezone)
1277
- }
1278
- }
1279
- });
1280
- setIsRescheduling(false);
1281
- } catch {
1282
- }
1283
- }
1284
- const isTerminal = booking.status === BOOKING_STATUS.CANCELLED || booking.status === BOOKING_STATUS.COMPLETED || booking.status === BOOKING_STATUS.NO_SHOW;
1285
- return /* @__PURE__ */ jsx10(SidePanel, { title: messages["booking.detailTitle"], closeLabel: messages["common.close"], onClose, children: /* @__PURE__ */ jsxs9("div", { className: "space-y-4", children: [
1286
- /* @__PURE__ */ jsxs9("div", { children: [
1287
- /* @__PURE__ */ jsx10("p", { className: "text-base font-semibold text-gray-900 dark:text-gray-100", children: booking.title }),
1288
- /* @__PURE__ */ jsx10("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages[`booking.status.${booking.status === "no_show" ? "noShow" : booking.status}`] }),
1289
- /* @__PURE__ */ jsxs9("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: [
1290
- booking.startsAt.toLocaleString(locale),
1291
- " \u2192 ",
1292
- booking.endsAt.toLocaleString(locale)
1293
- ] })
1294
- ] }),
1295
- (confirmBooking.isError || completeBooking.isError || markNoShow.isError || cancelBooking.isError || rescheduleBooking.isError) && /* @__PURE__ */ jsx10("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.actionFailure"] }),
1296
- !isTerminal && /* @__PURE__ */ jsxs9("div", { className: "flex flex-wrap gap-2", children: [
1297
- booking.status === BOOKING_STATUS.REQUESTED && /* @__PURE__ */ jsx10("button", { type: "button", onClick: () => confirmBooking.mutate(booking.id), className: PRIMARY_BUTTON_CLASS, children: messages["booking.confirm"] }),
1298
- booking.status === BOOKING_STATUS.CONFIRMED && /* @__PURE__ */ jsxs9(Fragment3, { children: [
1299
- /* @__PURE__ */ jsx10("button", { type: "button", onClick: () => completeBooking.mutate(booking.id), className: ACTION_BUTTON_CLASS, children: messages["booking.complete"] }),
1300
- /* @__PURE__ */ jsx10("button", { type: "button", onClick: () => markNoShow.mutate(booking.id), className: ACTION_BUTTON_CLASS, children: messages["booking.markNoShow"] }),
1301
- /* @__PURE__ */ jsx10("button", { type: "button", onClick: () => setIsRescheduling(true), className: ACTION_BUTTON_CLASS, children: messages["booking.reschedule"] })
1302
- ] }),
1303
- /* @__PURE__ */ jsxs9("button", { type: "button", onClick: () => setIsCancelling(true), className: DANGER_BUTTON_CLASS, children: [
1304
- /* @__PURE__ */ jsx10(XCircle, { "aria-hidden": "true", className: "w-4 h-4" }),
1305
- messages["booking.cancel"]
1306
- ] })
1307
- ] }),
1308
- isRescheduling && /* @__PURE__ */ jsxs9("div", { className: "space-y-2 rounded-lg border border-gray-200 dark:border-gray-700 p-3", children: [
1309
- /* @__PURE__ */ jsx10(
1310
- DateTimeField,
1311
- {
1312
- label: messages["booking.rescheduleStart"],
1313
- value: start,
1314
- onChange: setStart
1315
- }
1316
- ),
1317
- /* @__PURE__ */ jsx10(DateTimeField, { label: messages["booking.rescheduleEnd"], value: end, onChange: setEnd }),
1318
- /* @__PURE__ */ jsx10("button", { type: "button", onClick: handleReschedule, disabled: rescheduleBooking.isPending, className: PRIMARY_BUTTON_CLASS, children: messages["common.save"] })
1319
- ] }),
1320
- isCancelling && /* @__PURE__ */ jsxs9("div", { className: "space-y-2 rounded-lg border border-gray-200 dark:border-gray-700 p-3", children: [
1321
- /* @__PURE__ */ jsx10(
1322
- "input",
1323
- {
1324
- type: "text",
1325
- placeholder: messages["booking.cancelledBy"],
1326
- value: cancelledBy,
1327
- onChange: (event) => setCancelledBy(event.target.value),
1328
- className: INPUT_CLASS
1329
- }
1330
- ),
1331
- /* @__PURE__ */ jsx10(
1332
- "input",
1333
- {
1334
- type: "text",
1335
- placeholder: messages["booking.cancelReason"],
1336
- value: cancellationReason,
1337
- onChange: (event) => setCancellationReason(event.target.value),
1338
- className: INPUT_CLASS
1339
- }
1340
- ),
1341
- /* @__PURE__ */ jsxs9("button", { type: "button", onClick: handleCancel, disabled: cancelBooking.isPending, className: DANGER_BUTTON_CLASS, children: [
1342
- /* @__PURE__ */ jsx10(XCircle, { "aria-hidden": "true", className: "w-4 h-4" }),
1343
- messages["booking.cancel"]
1344
- ] })
1345
- ] })
1346
- ] }) });
1347
- }
1348
-
1349
1877
  // src/components/BookingsTable.tsx
1350
1878
  import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, ArrowUpDown } from "lucide-react";
1351
- import { useState as useState6 } from "react";
1352
- import { BOOKING_STATUS as BOOKING_STATUS3 } from "@adatechnology/scheduling-contracts";
1879
+ import { useState as useState7 } from "react";
1880
+ import { BOOKING_STATUS as BOOKING_STATUS4 } from "@adatechnology/scheduling-contracts";
1353
1881
 
1354
1882
  // src/components/bookingsTableState.util.ts
1355
- import { BOOKING_STATUS as BOOKING_STATUS2 } from "@adatechnology/scheduling-contracts";
1883
+ import { BOOKING_STATUS as BOOKING_STATUS3 } from "@adatechnology/scheduling-contracts";
1356
1884
  var DEFAULT_BOOKINGS_TABLE_STATE = {
1357
1885
  sortDirection: "asc",
1358
1886
  statusFilters: [],
1359
1887
  page: 1
1360
1888
  };
1361
1889
  var SORT_COLUMNS = ["title", "status", "startsAt", "endsAt"];
1362
- var BOOKING_STATUSES = Object.values(BOOKING_STATUS2);
1890
+ var BOOKING_STATUSES = Object.values(BOOKING_STATUS3);
1363
1891
  function isBookingSortColumn(value) {
1364
1892
  return SORT_COLUMNS.includes(value);
1365
1893
  }
@@ -1398,8 +1926,8 @@ function filterBookingsByStatus(bookings, statusFilters) {
1398
1926
  }
1399
1927
 
1400
1928
  // src/components/BookingsTable.tsx
1401
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1402
- var ALL_STATUSES = Object.values(BOOKING_STATUS3);
1929
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
1930
+ var ALL_STATUSES = Object.values(BOOKING_STATUS4);
1403
1931
  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";
1404
1932
  var CHECKBOX_CELL_CLASS = "px-3 py-2";
1405
1933
  function nextSortState(column, currentColumn, currentDirection) {
@@ -1417,7 +1945,7 @@ function BookingsTable({
1417
1945
  }) {
1418
1946
  const { locale } = useSchedulingConfig();
1419
1947
  const messages = resolveSchedulingMessages(locale);
1420
- const [selected, setSelected] = useState6(/* @__PURE__ */ new Set());
1948
+ const [selected, setSelected] = useState7(/* @__PURE__ */ new Set());
1421
1949
  const visibleBookings = filterBookingsByStatus(bookings, state.statusFilters);
1422
1950
  const allSelected = visibleBookings.length > 0 && visibleBookings.every((booking) => selected.has(booking.id));
1423
1951
  function toggleSort(column) {
@@ -1447,53 +1975,67 @@ function BookingsTable({
1447
1975
  setSelected(next);
1448
1976
  }
1449
1977
  function renderSortIcon(column) {
1450
- if (state.sortColumn !== column) return /* @__PURE__ */ jsx11(ArrowUpDown, { "aria-hidden": "true", className: "w-3 h-3" });
1451
- return state.sortDirection === "asc" ? /* @__PURE__ */ jsx11(ArrowUp, { "aria-hidden": "true", className: "w-3 h-3" }) : /* @__PURE__ */ jsx11(ArrowDown, { "aria-hidden": "true", className: "w-3 h-3" });
1978
+ if (state.sortColumn !== column) return /* @__PURE__ */ jsx15(ArrowUpDown, { "aria-hidden": "true", className: "w-3 h-3" });
1979
+ return state.sortDirection === "asc" ? /* @__PURE__ */ jsx15(ArrowUp, { "aria-hidden": "true", className: "w-3 h-3" }) : /* @__PURE__ */ jsx15(ArrowDown, { "aria-hidden": "true", className: "w-3 h-3" });
1452
1980
  }
1453
1981
  function renderHeader(column, labelKey) {
1454
1982
  const ariaSort = state.sortColumn !== column ? "none" : state.sortDirection === "asc" ? "ascending" : "descending";
1455
- return /* @__PURE__ */ jsx11("th", { scope: "col", className: "px-3 py-2", "aria-sort": ariaSort, children: /* @__PURE__ */ jsxs10("button", { type: "button", onClick: () => toggleSort(column), className: HEADER_BUTTON_CLASS, children: [
1983
+ return /* @__PURE__ */ jsx15("th", { scope: "col", className: "px-3 py-2", "aria-sort": ariaSort, children: /* @__PURE__ */ jsxs12("button", { type: "button", onClick: () => toggleSort(column), className: HEADER_BUTTON_CLASS, children: [
1456
1984
  messages[labelKey],
1457
1985
  renderSortIcon(column)
1458
1986
  ] }) });
1459
1987
  }
1460
- return /* @__PURE__ */ jsxs10("div", { className: "space-y-3", children: [
1461
- /* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap items-center gap-3", children: [
1462
- ALL_STATUSES.map((status) => /* @__PURE__ */ jsxs10("label", { className: "flex items-center gap-1.5 text-sm min-h-11", children: [
1463
- /* @__PURE__ */ jsx11(
1464
- "input",
1465
- {
1466
- type: "checkbox",
1467
- checked: state.statusFilters.includes(status),
1468
- onChange: () => toggleStatusFilter(status)
1469
- }
1470
- ),
1471
- messages[`booking.status.${status === "no_show" ? "noShow" : status}`]
1472
- ] }, status)),
1473
- !isBookingsTableStateDefault(state) && /* @__PURE__ */ jsx11(
1988
+ return /* @__PURE__ */ jsxs12("div", { className: "space-y-3", children: [
1989
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-3", children: [
1990
+ /* @__PURE__ */ jsxs12("fieldset", { className: "flex flex-wrap items-center gap-2", children: [
1991
+ /* @__PURE__ */ jsx15("legend", { className: "sr-only", children: messages["booking.filterByStatus"] }),
1992
+ ALL_STATUSES.map((status) => {
1993
+ const isActive = state.statusFilters.includes(status);
1994
+ return /* @__PURE__ */ jsxs12(
1995
+ "label",
1996
+ {
1997
+ className: `flex cursor-pointer items-center gap-1.5 rounded-full border px-3 py-1.5 text-sm transition-colors ${isActive ? "border-brand-500 bg-brand-50 text-brand-800 dark:bg-brand-900/40 dark:text-brand-200" : "border-gray-300 text-gray-600 hover:bg-gray-50 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-800"}`,
1998
+ children: [
1999
+ /* @__PURE__ */ jsx15(
2000
+ "input",
2001
+ {
2002
+ type: "checkbox",
2003
+ className: "sr-only",
2004
+ checked: isActive,
2005
+ onChange: () => toggleStatusFilter(status)
2006
+ }
2007
+ ),
2008
+ bookingStatusLabel(messages, status)
2009
+ ]
2010
+ },
2011
+ status
2012
+ );
2013
+ })
2014
+ ] }),
2015
+ !isBookingsTableStateDefault(state) && /* @__PURE__ */ jsx15(
1474
2016
  "button",
1475
2017
  {
1476
2018
  type: "button",
1477
2019
  onClick: () => onStateChange?.(DEFAULT_BOOKINGS_TABLE_STATE),
1478
- className: "min-h-11 px-3 text-sm font-medium text-brand-700 hover:underline",
2020
+ className: "min-h-11 px-3 text-sm font-medium text-brand-700 hover:underline dark:text-brand-300",
1479
2021
  children: messages["common.clearFilters"]
1480
2022
  }
1481
2023
  )
1482
2024
  ] }),
1483
- selected.size > 0 && bulkActions.length > 0 && /* @__PURE__ */ jsx11("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__ */ jsx11(
2025
+ selected.size > 0 && bulkActions.length > 0 && /* @__PURE__ */ jsx15("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__ */ jsx15(
1484
2026
  "button",
1485
2027
  {
1486
2028
  type: "button",
1487
2029
  onClick: () => action.onRun(Array.from(selected)),
1488
- className: "min-h-11 px-3 rounded-lg text-sm font-medium bg-brand-600 text-white hover:bg-brand-700",
2030
+ className: BUTTON_PRIMARY,
1489
2031
  children: action.label
1490
2032
  },
1491
2033
  action.key
1492
2034
  )) }),
1493
- /* @__PURE__ */ jsxs10("div", { className: "overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700", children: [
1494
- /* @__PURE__ */ jsxs10("table", { className: "min-w-full text-sm", children: [
1495
- /* @__PURE__ */ jsx11("thead", { className: "border-b border-gray-200 dark:border-gray-700", children: /* @__PURE__ */ jsxs10("tr", { children: [
1496
- /* @__PURE__ */ jsx11("th", { scope: "col", className: CHECKBOX_CELL_CLASS, children: /* @__PURE__ */ jsx11(
2035
+ /* @__PURE__ */ jsxs12("div", { className: `${SURFACE_BORDER} overflow-x-auto`, children: [
2036
+ /* @__PURE__ */ jsxs12("table", { className: "min-w-full text-sm", children: [
2037
+ /* @__PURE__ */ jsx15("thead", { className: "border-b border-gray-200 bg-gray-50 dark:border-gray-800 dark:bg-gray-800/60", children: /* @__PURE__ */ jsxs12("tr", { children: [
2038
+ /* @__PURE__ */ jsx15("th", { scope: "col", className: CHECKBOX_CELL_CLASS, children: /* @__PURE__ */ jsx15(
1497
2039
  "input",
1498
2040
  {
1499
2041
  type: "checkbox",
@@ -1507,12 +2049,12 @@ function BookingsTable({
1507
2049
  renderHeader("startsAt", "booking.column.startsAt"),
1508
2050
  renderHeader("endsAt", "booking.column.endsAt")
1509
2051
  ] }) }),
1510
- /* @__PURE__ */ jsx11("tbody", { children: visibleBookings.map((booking, index) => /* @__PURE__ */ jsxs10(
2052
+ /* @__PURE__ */ jsx15("tbody", { children: visibleBookings.map((booking, index) => /* @__PURE__ */ jsxs12(
1511
2053
  "tr",
1512
2054
  {
1513
- className: index % 2 === 1 ? "bg-gray-50 dark:bg-gray-800/50" : void 0,
2055
+ className: `${index % 2 === 1 ? ROW_STRIPE : ""} hover:bg-brand-50/50 dark:hover:bg-brand-900/10`,
1514
2056
  children: [
1515
- /* @__PURE__ */ jsx11("td", { className: CHECKBOX_CELL_CLASS, children: /* @__PURE__ */ jsx11(
2057
+ /* @__PURE__ */ jsx15("td", { className: CHECKBOX_CELL_CLASS, children: /* @__PURE__ */ jsx15(
1516
2058
  "input",
1517
2059
  {
1518
2060
  type: "checkbox",
@@ -1521,43 +2063,43 @@ function BookingsTable({
1521
2063
  onChange: () => toggleRowSelected(booking.id)
1522
2064
  }
1523
2065
  ) }),
1524
- /* @__PURE__ */ jsx11("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsx11("button", { type: "button", onClick: () => onRowClick?.(booking), className: "min-h-11 text-left hover:underline", children: booking.title }) }),
1525
- /* @__PURE__ */ jsx11("td", { className: "px-3 py-2", children: messages[`booking.status.${booking.status === "no_show" ? "noShow" : booking.status}`] }),
1526
- /* @__PURE__ */ jsx11("td", { className: "px-3 py-2", children: booking.startsAt.toLocaleString(locale) }),
1527
- /* @__PURE__ */ jsx11("td", { className: "px-3 py-2", children: booking.endsAt.toLocaleString(locale) })
2066
+ /* @__PURE__ */ jsx15("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsx15("button", { type: "button", onClick: () => onRowClick?.(booking), className: "min-h-11 text-left hover:underline", children: booking.title }) }),
2067
+ /* @__PURE__ */ jsx15("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsx15(BookingStatusBadge, { status: booking.status }) }),
2068
+ /* @__PURE__ */ jsx15("td", { className: "px-3 py-2 tabular-nums whitespace-nowrap", children: booking.startsAt.toLocaleString(locale) }),
2069
+ /* @__PURE__ */ jsx15("td", { className: "px-3 py-2 tabular-nums whitespace-nowrap", children: booking.endsAt.toLocaleString(locale) })
1528
2070
  ]
1529
2071
  },
1530
2072
  booking.id
1531
2073
  )) })
1532
2074
  ] }),
1533
- visibleBookings.length === 0 && /* @__PURE__ */ jsx11("p", { className: "px-3 py-4 text-sm text-gray-500 dark:text-gray-400", children: messages["common.empty"] })
2075
+ visibleBookings.length === 0 && /* @__PURE__ */ jsx15("p", { className: "px-3 py-8 text-center text-sm text-gray-500 dark:text-gray-400", children: messages["common.empty"] })
1534
2076
  ] }),
1535
- pagination && pagination.totalPages > 1 && /* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-end gap-3", children: [
1536
- /* @__PURE__ */ jsx11(
2077
+ pagination && pagination.totalPages > 1 && /* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-end gap-3", children: [
2078
+ /* @__PURE__ */ jsx15(
1537
2079
  "button",
1538
2080
  {
1539
2081
  type: "button",
1540
2082
  "aria-label": messages["common.previousPage"],
1541
2083
  disabled: state.page <= 1,
1542
2084
  onClick: () => goToPage(state.page - 1),
1543
- 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",
1544
- children: /* @__PURE__ */ jsx11(ArrowLeft, { "aria-hidden": "true", className: "w-4 h-4" })
2085
+ className: ICON_BUTTON,
2086
+ children: /* @__PURE__ */ jsx15(ArrowLeft, { "aria-hidden": "true", className: "w-4 h-4" })
1545
2087
  }
1546
2088
  ),
1547
- /* @__PURE__ */ jsxs10("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: [
2089
+ /* @__PURE__ */ jsxs12("span", { className: "text-sm tabular-nums text-gray-500 dark:text-gray-400", children: [
1548
2090
  state.page,
1549
2091
  " / ",
1550
2092
  pagination.totalPages
1551
2093
  ] }),
1552
- /* @__PURE__ */ jsx11(
2094
+ /* @__PURE__ */ jsx15(
1553
2095
  "button",
1554
2096
  {
1555
2097
  type: "button",
1556
2098
  "aria-label": messages["common.nextPage"],
1557
2099
  disabled: state.page >= pagination.totalPages,
1558
2100
  onClick: () => goToPage(state.page + 1),
1559
- 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",
1560
- children: /* @__PURE__ */ jsx11(ArrowRight, { "aria-hidden": "true", className: "w-4 h-4" })
2101
+ className: ICON_BUTTON,
2102
+ children: /* @__PURE__ */ jsx15(ArrowRight, { "aria-hidden": "true", className: "w-4 h-4" })
1561
2103
  }
1562
2104
  )
1563
2105
  ] })
@@ -1565,15 +2107,15 @@ function BookingsTable({
1565
2107
  }
1566
2108
 
1567
2109
  // src/hooks/useBookingsTableState.hook.ts
1568
- import { useEffect as useEffect2, useState as useState7 } from "react";
2110
+ import { useEffect as useEffect4, useState as useState8 } from "react";
1569
2111
  var OWNED_PARAM_KEYS = ["sortBy", "sortDirection", "status", "page"];
1570
2112
  function readInitialState() {
1571
2113
  if (typeof window === "undefined") return DEFAULT_BOOKINGS_TABLE_STATE;
1572
2114
  return parseBookingsTableState(window.location.search);
1573
2115
  }
1574
2116
  function useBookingsTableState() {
1575
- const [state, setState] = useState7(readInitialState);
1576
- useEffect2(() => {
2117
+ const [state, setState] = useState8(readInitialState);
2118
+ useEffect4(() => {
1577
2119
  if (typeof window === "undefined") return;
1578
2120
  const params = new URLSearchParams(window.location.search);
1579
2121
  for (const key of OWNED_PARAM_KEYS) params.delete(key);
@@ -1586,7 +2128,7 @@ function useBookingsTableState() {
1586
2128
  }
1587
2129
 
1588
2130
  // src/workspace/BookingsArea.tsx
1589
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
2131
+ import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1590
2132
  var PAGE_SIZE = 20;
1591
2133
  function BookingsArea() {
1592
2134
  const { locale } = useSchedulingConfig();
@@ -1602,7 +2144,7 @@ function BookingsArea() {
1602
2144
  });
1603
2145
  const confirmBooking = useConfirmBooking();
1604
2146
  const { data: resourcesData } = useResources({ pageSize: MAX_PAGE_SIZE3 });
1605
- const [selectedBookingId, setSelectedBookingId] = useState8(void 0);
2147
+ const [selectedBookingId, setSelectedBookingId] = useState9(void 0);
1606
2148
  const selectedBooking = data?.data.find((booking) => booking.id === selectedBookingId);
1607
2149
  const selectedBookingResourceTimezone = resourcesData?.data.find(
1608
2150
  (resource) => resource.id === selectedBooking?.resourceIds[0]
@@ -1610,21 +2152,32 @@ function BookingsArea() {
1610
2152
  function bulkConfirm(ids) {
1611
2153
  for (const id of ids) confirmBooking.mutate(id);
1612
2154
  }
1613
- return /* @__PURE__ */ jsxs11("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col p-4 space-y-4", children: [
1614
- isError && /* @__PURE__ */ jsx12("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.loadFailure"] }),
1615
- confirmBooking.isError && /* @__PURE__ */ jsx12("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.actionFailure"] }),
1616
- isLoading ? /* @__PURE__ */ jsx12("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.loading"] }) : /* @__PURE__ */ jsx12(
1617
- BookingsTable,
1618
- {
1619
- bookings: data?.data ?? [],
1620
- state: tableState,
1621
- onStateChange: setTableState,
1622
- pagination: data ? { totalPages: data.totalPages } : void 0,
1623
- onRowClick: (booking) => setSelectedBookingId(booking.id),
1624
- bulkActions: [{ key: "confirm", label: messages["booking.confirm"], onRun: bulkConfirm }]
1625
- }
1626
- ),
1627
- selectedBooking && /* @__PURE__ */ jsx12(
2155
+ return /* @__PURE__ */ jsxs13("div", { className: "flex flex-1 min-h-0 min-w-0", children: [
2156
+ /* @__PURE__ */ jsxs13("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col gap-4 overflow-y-auto p-4", children: [
2157
+ isError && /* @__PURE__ */ jsx16(ErrorBanner, { message: messages["common.loadFailure"] }),
2158
+ confirmBooking.isError && /* @__PURE__ */ jsx16(ErrorBanner, { message: messages["common.actionFailure"] }),
2159
+ isLoading && /* @__PURE__ */ jsx16(ListSkeleton, { label: messages["common.loading"], rows: 8 }),
2160
+ !isLoading && !isError && (data?.data.length ?? 0) === 0 && tableState.statusFilters.length === 0 && /* @__PURE__ */ jsx16(
2161
+ EmptyState,
2162
+ {
2163
+ icon: ClipboardList,
2164
+ title: messages["booking.emptyTitle"],
2165
+ hint: messages["booking.emptyHint"]
2166
+ }
2167
+ ),
2168
+ !isLoading && ((data?.data.length ?? 0) > 0 || tableState.statusFilters.length > 0) && /* @__PURE__ */ jsx16(
2169
+ BookingsTable,
2170
+ {
2171
+ bookings: data?.data ?? [],
2172
+ state: tableState,
2173
+ onStateChange: setTableState,
2174
+ pagination: data ? { totalPages: data.totalPages } : void 0,
2175
+ onRowClick: (booking) => setSelectedBookingId(booking.id),
2176
+ bulkActions: [{ key: "confirm", label: messages["booking.confirm"], onRun: bulkConfirm }]
2177
+ }
2178
+ )
2179
+ ] }),
2180
+ selectedBooking && /* @__PURE__ */ jsx16(
1628
2181
  BookingDrawer,
1629
2182
  {
1630
2183
  booking: selectedBooking,
@@ -1637,29 +2190,42 @@ function BookingsArea() {
1637
2190
  }
1638
2191
 
1639
2192
  // src/workspace/ResourcesArea.tsx
1640
- import { Plus as Plus3, Trash2 as Trash23 } from "lucide-react";
1641
- import { useState as useState10 } from "react";
2193
+ import { Plus as Plus3, Trash2 as Trash23, Users } from "lucide-react";
2194
+ import { useState as useState11 } from "react";
1642
2195
  import { MAX_PAGE_SIZE as MAX_PAGE_SIZE4 } from "@adatechnology/scheduling-contracts";
1643
2196
 
1644
2197
  // src/components/ResourceForm.tsx
1645
- import { useId as useId2, useState as useState9 } from "react";
2198
+ import { useId as useId3, useMemo as useMemo7, useState as useState10 } from "react";
1646
2199
  import { RESOURCE_KIND } from "@adatechnology/scheduling-contracts";
1647
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
2200
+ import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1648
2201
  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";
1649
2202
  var LABEL_CLASS = "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1";
1650
- 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";
2203
+ var BUTTON_PRIMARY2 = "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";
2204
+ var FALLBACK_TIMEZONES = ["America/Sao_Paulo", "America/Manaus", "America/Belem", "UTC"];
2205
+ function buildTimezoneOptions(current) {
2206
+ const supported = Intl.supportedValuesOf?.("timeZone") ?? FALLBACK_TIMEZONES;
2207
+ const zones = supported.includes(current) ? supported : [current, ...supported];
2208
+ return zones.map((zone) => ({ value: zone, label: zone.replace(/_/g, " ") }));
2209
+ }
1651
2210
  function ResourceForm({ initialValues, onSubmit }) {
1652
2211
  const { locale } = useSchedulingConfig();
1653
2212
  const messages = resolveSchedulingMessages(locale);
1654
- const formId = useId2();
2213
+ const formId = useId3();
1655
2214
  const nameId = `${formId}-name`;
1656
- const kindId = `${formId}-kind`;
1657
- const timezoneId = `${formId}-timezone`;
1658
- const [name, setName] = useState9(initialValues?.name ?? "");
1659
- const [kind, setKind] = useState9(initialValues?.kind ?? RESOURCE_KIND.PERSON);
1660
- const [timezone, setTimezone] = useState9(initialValues?.timezone ?? "America/Sao_Paulo");
1661
- const [active, setActive] = useState9(initialValues?.active ?? true);
1662
- const [submitting, setSubmitting] = useState9(false);
2215
+ const [name, setName] = useState10(initialValues?.name ?? "");
2216
+ const [kind, setKind] = useState10(initialValues?.kind ?? RESOURCE_KIND.PERSON);
2217
+ const [timezone, setTimezone] = useState10(initialValues?.timezone ?? "America/Sao_Paulo");
2218
+ const [active, setActive] = useState10(initialValues?.active ?? true);
2219
+ const [submitting, setSubmitting] = useState10(false);
2220
+ const kindOptions = useMemo7(
2221
+ () => [
2222
+ { value: RESOURCE_KIND.PERSON, label: messages["resource.kind.person"] },
2223
+ { value: RESOURCE_KIND.ROOM, label: messages["resource.kind.room"] },
2224
+ { value: RESOURCE_KIND.EQUIPMENT, label: messages["resource.kind.equipment"] }
2225
+ ],
2226
+ [messages]
2227
+ );
2228
+ const timezoneOptions = useMemo7(() => buildTimezoneOptions(timezone), [timezone]);
1663
2229
  async function handleSubmit(event) {
1664
2230
  event.preventDefault();
1665
2231
  setSubmitting(true);
@@ -1669,10 +2235,10 @@ function ResourceForm({ initialValues, onSubmit }) {
1669
2235
  setSubmitting(false);
1670
2236
  }
1671
2237
  }
1672
- return /* @__PURE__ */ jsxs12("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
1673
- /* @__PURE__ */ jsxs12("div", { children: [
1674
- /* @__PURE__ */ jsx13("label", { htmlFor: nameId, className: LABEL_CLASS, children: messages["resource.name"] }),
1675
- /* @__PURE__ */ jsx13(
2238
+ return /* @__PURE__ */ jsxs14("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
2239
+ /* @__PURE__ */ jsxs14("div", { children: [
2240
+ /* @__PURE__ */ jsx17("label", { htmlFor: nameId, className: LABEL_CLASS, children: messages["resource.name"] }),
2241
+ /* @__PURE__ */ jsx17(
1676
2242
  "input",
1677
2243
  {
1678
2244
  id: nameId,
@@ -1684,75 +2250,59 @@ function ResourceForm({ initialValues, onSubmit }) {
1684
2250
  }
1685
2251
  )
1686
2252
  ] }),
1687
- /* @__PURE__ */ jsxs12("div", { children: [
1688
- /* @__PURE__ */ jsx13("label", { htmlFor: kindId, className: LABEL_CLASS, children: messages["resource.kind"] }),
1689
- /* @__PURE__ */ jsxs12(
1690
- "select",
1691
- {
1692
- id: kindId,
1693
- value: kind,
1694
- onChange: (event) => setKind(event.target.value),
1695
- className: INPUT_CLASS2,
1696
- children: [
1697
- /* @__PURE__ */ jsx13("option", { value: RESOURCE_KIND.PERSON, children: messages["resource.kind.person"] }),
1698
- /* @__PURE__ */ jsx13("option", { value: RESOURCE_KIND.ROOM, children: messages["resource.kind.room"] }),
1699
- /* @__PURE__ */ jsx13("option", { value: RESOURCE_KIND.EQUIPMENT, children: messages["resource.kind.equipment"] })
1700
- ]
1701
- }
1702
- )
1703
- ] }),
1704
- /* @__PURE__ */ jsxs12("div", { children: [
1705
- /* @__PURE__ */ jsx13("label", { htmlFor: timezoneId, className: LABEL_CLASS, children: messages["resource.timezone"] }),
1706
- /* @__PURE__ */ jsx13(
1707
- "input",
1708
- {
1709
- id: timezoneId,
1710
- type: "text",
1711
- required: true,
1712
- value: timezone,
1713
- onChange: (event) => setTimezone(event.target.value),
1714
- placeholder: "America/Sao_Paulo",
1715
- className: INPUT_CLASS2
1716
- }
1717
- )
1718
- ] }),
1719
- initialValues && /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300 min-h-11", children: [
1720
- /* @__PURE__ */ jsx13("input", { type: "checkbox", checked: active, onChange: (event) => setActive(event.target.checked) }),
2253
+ /* @__PURE__ */ jsx17(
2254
+ SelectField,
2255
+ {
2256
+ label: messages["resource.kind"],
2257
+ value: kind,
2258
+ options: kindOptions,
2259
+ onChange: (next) => setKind(next)
2260
+ }
2261
+ ),
2262
+ /* @__PURE__ */ jsx17(
2263
+ SelectField,
2264
+ {
2265
+ label: messages["resource.timezone"],
2266
+ value: timezone,
2267
+ options: timezoneOptions,
2268
+ onChange: setTimezone,
2269
+ searchable: true
2270
+ }
2271
+ ),
2272
+ initialValues && /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300 min-h-11", children: [
2273
+ /* @__PURE__ */ jsx17("input", { type: "checkbox", checked: active, onChange: (event) => setActive(event.target.checked) }),
1721
2274
  messages["resource.active"]
1722
2275
  ] }),
1723
- /* @__PURE__ */ jsx13("button", { type: "submit", disabled: submitting, className: BUTTON_PRIMARY, children: messages["common.save"] })
2276
+ /* @__PURE__ */ jsx17("button", { type: "submit", disabled: submitting, className: BUTTON_PRIMARY2, children: messages["common.save"] })
1724
2277
  ] });
1725
2278
  }
1726
2279
 
1727
2280
  // src/components/ResourceList.tsx
1728
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
2281
+ import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
1729
2282
  function ResourceList({ resources, onSelect }) {
1730
2283
  const { locale } = useSchedulingConfig();
1731
2284
  const messages = resolveSchedulingMessages(locale);
1732
2285
  if (resources.length === 0) {
1733
- return /* @__PURE__ */ jsx14("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.empty"] });
2286
+ return /* @__PURE__ */ jsx18("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.empty"] });
1734
2287
  }
1735
- return /* @__PURE__ */ jsx14("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__ */ jsx14("li", { className: index % 2 === 1 ? "bg-gray-50 dark:bg-gray-800/50" : void 0, children: /* @__PURE__ */ jsxs13(
2288
+ return /* @__PURE__ */ jsx18("ul", { className: `${SURFACE_BORDER} divide-y divide-gray-200 overflow-hidden dark:divide-gray-800`, children: resources.map((resource, index) => /* @__PURE__ */ jsx18("li", { className: index % 2 === 1 ? ROW_STRIPE : void 0, children: /* @__PURE__ */ jsxs15(
1736
2289
  "button",
1737
2290
  {
1738
2291
  type: "button",
1739
2292
  onClick: () => onSelect(resource),
1740
- 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",
2293
+ className: "flex w-full items-center gap-3 px-4 py-3 min-h-11 text-left text-sm transition-colors hover:bg-brand-50/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-500 dark:hover:bg-brand-900/20",
1741
2294
  children: [
1742
- /* @__PURE__ */ jsx14("span", { className: "flex-1 font-medium text-gray-900 dark:text-gray-100", children: resource.name }),
1743
- /* @__PURE__ */ jsx14("span", { className: "text-gray-500 dark:text-gray-400", children: messages[`resource.kind.${resource.kind}`] }),
1744
- /* @__PURE__ */ jsx14("span", { className: "text-gray-500 dark:text-gray-400", children: resource.timezone }),
1745
- !resource.active && /* @__PURE__ */ jsx14("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"] })
2295
+ /* @__PURE__ */ jsx18("span", { className: "flex-1 font-medium text-gray-900 dark:text-gray-100", children: resource.name }),
2296
+ /* @__PURE__ */ jsx18("span", { className: "text-gray-500 dark:text-gray-400", children: messages[`resource.kind.${resource.kind}`] }),
2297
+ /* @__PURE__ */ jsx18("span", { className: "text-gray-500 dark:text-gray-400", children: resource.timezone }),
2298
+ !resource.active && /* @__PURE__ */ jsx18("span", { className: "rounded-full bg-gray-200 px-2 py-0.5 text-xs font-medium text-gray-600 dark:bg-gray-700 dark:text-gray-300", children: messages["resource.inactive"] })
1746
2299
  ]
1747
2300
  }
1748
2301
  ) }, resource.id)) });
1749
2302
  }
1750
2303
 
1751
2304
  // src/workspace/ResourcesArea.tsx
1752
- import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
1753
- var BUTTON_CLASS2 = "inline-flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-colors min-h-11";
1754
- var BUTTON_PRIMARY2 = `${BUTTON_CLASS2} bg-brand-600 text-white hover:bg-brand-700`;
1755
- var BUTTON_DANGER = `${BUTTON_CLASS2} text-red-700 hover:bg-red-50`;
2305
+ import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
1756
2306
  function ResourcesArea() {
1757
2307
  const { locale } = useSchedulingConfig();
1758
2308
  const messages = resolveSchedulingMessages(locale);
@@ -1760,9 +2310,10 @@ function ResourcesArea() {
1760
2310
  const createResource = useCreateResource();
1761
2311
  const updateResource = useUpdateResource();
1762
2312
  const deleteResource = useDeleteResource();
1763
- const [draft, setDraft] = useState10(void 0);
2313
+ const [draft, setDraft] = useState11(void 0);
1764
2314
  const isDraftOpen = draft !== void 0;
1765
2315
  const isEditing = Boolean(draft);
2316
+ const resources = data?.data ?? [];
1766
2317
  async function handleSubmit(input) {
1767
2318
  try {
1768
2319
  if (draft) {
@@ -1774,21 +2325,36 @@ function ResourcesArea() {
1774
2325
  } catch {
1775
2326
  }
1776
2327
  }
1777
- return /* @__PURE__ */ jsxs14("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col p-4 space-y-4", children: [
1778
- /* @__PURE__ */ jsx15("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxs14("button", { type: "button", onClick: () => setDraft(null), className: `${BUTTON_PRIMARY2} ml-auto`, children: [
1779
- /* @__PURE__ */ jsx15(Plus3, { "aria-hidden": "true", className: "w-4 h-4" }),
2328
+ function renderCreateButton() {
2329
+ return /* @__PURE__ */ jsxs16("button", { type: "button", onClick: () => setDraft(null), className: BUTTON_PRIMARY, children: [
2330
+ /* @__PURE__ */ jsx19(Plus3, { "aria-hidden": "true", className: "h-4 w-4" }),
1780
2331
  messages["resource.newResource"]
1781
- ] }) }),
1782
- isError && /* @__PURE__ */ jsx15("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.loadFailure"] }),
1783
- (createResource.isError || updateResource.isError || deleteResource.isError) && /* @__PURE__ */ jsx15("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.actionFailure"] }),
1784
- isLoading ? /* @__PURE__ */ jsx15("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.loading"] }) : /* @__PURE__ */ jsx15(ResourceList, { resources: data?.data ?? [], onSelect: setDraft }),
1785
- isDraftOpen && /* @__PURE__ */ jsx15(
2332
+ ] });
2333
+ }
2334
+ return /* @__PURE__ */ jsxs16("div", { className: "flex flex-1 min-h-0 min-w-0", children: [
2335
+ /* @__PURE__ */ jsxs16("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col gap-4 overflow-y-auto p-4", children: [
2336
+ /* @__PURE__ */ jsx19("div", { className: "flex justify-end", children: renderCreateButton() }),
2337
+ isError && /* @__PURE__ */ jsx19(ErrorBanner, { message: messages["common.loadFailure"] }),
2338
+ (createResource.isError || updateResource.isError || deleteResource.isError) && /* @__PURE__ */ jsx19(ErrorBanner, { message: messages["common.actionFailure"] }),
2339
+ isLoading && /* @__PURE__ */ jsx19(ListSkeleton, { label: messages["common.loading"] }),
2340
+ !isLoading && !isError && resources.length === 0 && /* @__PURE__ */ jsx19(
2341
+ EmptyState,
2342
+ {
2343
+ icon: Users,
2344
+ title: messages["resource.emptyTitle"],
2345
+ hint: messages["resource.emptyHint"],
2346
+ action: renderCreateButton()
2347
+ }
2348
+ ),
2349
+ !isLoading && resources.length > 0 && /* @__PURE__ */ jsx19(ResourceList, { resources, onSelect: setDraft })
2350
+ ] }),
2351
+ isDraftOpen && /* @__PURE__ */ jsx19(
1786
2352
  SidePanel,
1787
2353
  {
1788
2354
  title: isEditing ? messages["resource.editTitle"] : messages["resource.createTitle"],
1789
2355
  closeLabel: messages["common.close"],
1790
2356
  onClose: () => setDraft(void 0),
1791
- headerActions: isEditing ? /* @__PURE__ */ jsxs14(
2357
+ headerActions: isEditing ? /* @__PURE__ */ jsxs16(
1792
2358
  "button",
1793
2359
  {
1794
2360
  type: "button",
@@ -1798,12 +2364,12 @@ function ResourcesArea() {
1798
2364
  },
1799
2365
  className: BUTTON_DANGER,
1800
2366
  children: [
1801
- /* @__PURE__ */ jsx15(Trash23, { "aria-hidden": "true", className: "w-4 h-4" }),
2367
+ /* @__PURE__ */ jsx19(Trash23, { "aria-hidden": "true", className: "h-4 w-4" }),
1802
2368
  messages["common.remove"]
1803
2369
  ]
1804
2370
  }
1805
2371
  ) : void 0,
1806
- children: /* @__PURE__ */ jsx15(
2372
+ children: /* @__PURE__ */ jsx19(
1807
2373
  ResourceForm,
1808
2374
  {
1809
2375
  ...draft ? { initialValues: draft } : {},
@@ -1817,25 +2383,25 @@ function ResourcesArea() {
1817
2383
  }
1818
2384
 
1819
2385
  // src/workspace/ServicesArea.tsx
1820
- import { Plus as Plus4, Trash2 as Trash24 } from "lucide-react";
1821
- import { useState as useState12 } from "react";
2386
+ import { Plus as Plus4, Trash2 as Trash24, Wrench } from "lucide-react";
2387
+ import { useState as useState13 } from "react";
1822
2388
  import { MAX_PAGE_SIZE as MAX_PAGE_SIZE5 } from "@adatechnology/scheduling-contracts";
1823
2389
 
1824
2390
  // src/components/ServiceForm.tsx
1825
- import { useState as useState11 } from "react";
1826
- import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
2391
+ import { useState as useState12 } from "react";
2392
+ import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
1827
2393
  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";
1828
2394
  var LABEL_CLASS2 = "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1";
1829
2395
  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";
1830
2396
  function ServiceForm({ initialValues, onSubmit }) {
1831
2397
  const { locale } = useSchedulingConfig();
1832
2398
  const messages = resolveSchedulingMessages(locale);
1833
- const [name, setName] = useState11(initialValues?.name ?? "");
1834
- const [durationMinutes, setDurationMinutes] = useState11(initialValues?.durationMinutes ?? 30);
1835
- const [bufferBeforeMinutes, setBufferBeforeMinutes] = useState11(initialValues?.bufferBeforeMinutes ?? 0);
1836
- const [bufferAfterMinutes, setBufferAfterMinutes] = useState11(initialValues?.bufferAfterMinutes ?? 0);
1837
- const [active, setActive] = useState11(initialValues?.active ?? true);
1838
- const [submitting, setSubmitting] = useState11(false);
2399
+ const [name, setName] = useState12(initialValues?.name ?? "");
2400
+ const [durationMinutes, setDurationMinutes] = useState12(initialValues?.durationMinutes ?? 30);
2401
+ const [bufferBeforeMinutes, setBufferBeforeMinutes] = useState12(initialValues?.bufferBeforeMinutes ?? 0);
2402
+ const [bufferAfterMinutes, setBufferAfterMinutes] = useState12(initialValues?.bufferAfterMinutes ?? 0);
2403
+ const [active, setActive] = useState12(initialValues?.active ?? true);
2404
+ const [submitting, setSubmitting] = useState12(false);
1839
2405
  async function handleSubmit(event) {
1840
2406
  event.preventDefault();
1841
2407
  setSubmitting(true);
@@ -1845,10 +2411,10 @@ function ServiceForm({ initialValues, onSubmit }) {
1845
2411
  setSubmitting(false);
1846
2412
  }
1847
2413
  }
1848
- return /* @__PURE__ */ jsxs15("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
1849
- /* @__PURE__ */ jsxs15("div", { children: [
1850
- /* @__PURE__ */ jsx16("label", { htmlFor: "scheduling-service-name", className: LABEL_CLASS2, children: messages["service.name"] }),
1851
- /* @__PURE__ */ jsx16(
2414
+ return /* @__PURE__ */ jsxs17("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
2415
+ /* @__PURE__ */ jsxs17("div", { children: [
2416
+ /* @__PURE__ */ jsx20("label", { htmlFor: "scheduling-service-name", className: LABEL_CLASS2, children: messages["service.name"] }),
2417
+ /* @__PURE__ */ jsx20(
1852
2418
  "input",
1853
2419
  {
1854
2420
  id: "scheduling-service-name",
@@ -1860,9 +2426,9 @@ function ServiceForm({ initialValues, onSubmit }) {
1860
2426
  }
1861
2427
  )
1862
2428
  ] }),
1863
- /* @__PURE__ */ jsxs15("div", { children: [
1864
- /* @__PURE__ */ jsx16("label", { htmlFor: "scheduling-service-duration", className: LABEL_CLASS2, children: messages["service.durationMinutes"] }),
1865
- /* @__PURE__ */ jsx16(
2429
+ /* @__PURE__ */ jsxs17("div", { children: [
2430
+ /* @__PURE__ */ jsx20("label", { htmlFor: "scheduling-service-duration", className: LABEL_CLASS2, children: messages["service.durationMinutes"] }),
2431
+ /* @__PURE__ */ jsx20(
1866
2432
  "input",
1867
2433
  {
1868
2434
  id: "scheduling-service-duration",
@@ -1875,10 +2441,10 @@ function ServiceForm({ initialValues, onSubmit }) {
1875
2441
  }
1876
2442
  )
1877
2443
  ] }),
1878
- /* @__PURE__ */ jsxs15("div", { className: "grid grid-cols-2 gap-3", children: [
1879
- /* @__PURE__ */ jsxs15("div", { children: [
1880
- /* @__PURE__ */ jsx16("label", { htmlFor: "scheduling-service-buffer-before", className: LABEL_CLASS2, children: messages["service.bufferBeforeMinutes"] }),
1881
- /* @__PURE__ */ jsx16(
2444
+ /* @__PURE__ */ jsxs17("div", { className: "grid grid-cols-2 gap-3", children: [
2445
+ /* @__PURE__ */ jsxs17("div", { children: [
2446
+ /* @__PURE__ */ jsx20("label", { htmlFor: "scheduling-service-buffer-before", className: LABEL_CLASS2, children: messages["service.bufferBeforeMinutes"] }),
2447
+ /* @__PURE__ */ jsx20(
1882
2448
  "input",
1883
2449
  {
1884
2450
  id: "scheduling-service-buffer-before",
@@ -1890,9 +2456,9 @@ function ServiceForm({ initialValues, onSubmit }) {
1890
2456
  }
1891
2457
  )
1892
2458
  ] }),
1893
- /* @__PURE__ */ jsxs15("div", { children: [
1894
- /* @__PURE__ */ jsx16("label", { htmlFor: "scheduling-service-buffer-after", className: LABEL_CLASS2, children: messages["service.bufferAfterMinutes"] }),
1895
- /* @__PURE__ */ jsx16(
2459
+ /* @__PURE__ */ jsxs17("div", { children: [
2460
+ /* @__PURE__ */ jsx20("label", { htmlFor: "scheduling-service-buffer-after", className: LABEL_CLASS2, children: messages["service.bufferAfterMinutes"] }),
2461
+ /* @__PURE__ */ jsx20(
1896
2462
  "input",
1897
2463
  {
1898
2464
  id: "scheduling-service-buffer-after",
@@ -1905,45 +2471,42 @@ function ServiceForm({ initialValues, onSubmit }) {
1905
2471
  )
1906
2472
  ] })
1907
2473
  ] }),
1908
- initialValues && /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300 min-h-11", children: [
1909
- /* @__PURE__ */ jsx16("input", { type: "checkbox", checked: active, onChange: (event) => setActive(event.target.checked) }),
2474
+ initialValues && /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300 min-h-11", children: [
2475
+ /* @__PURE__ */ jsx20("input", { type: "checkbox", checked: active, onChange: (event) => setActive(event.target.checked) }),
1910
2476
  messages["service.active"]
1911
2477
  ] }),
1912
- /* @__PURE__ */ jsx16("button", { type: "submit", disabled: submitting, className: BUTTON_PRIMARY3, children: messages["common.save"] })
2478
+ /* @__PURE__ */ jsx20("button", { type: "submit", disabled: submitting, className: BUTTON_PRIMARY3, children: messages["common.save"] })
1913
2479
  ] });
1914
2480
  }
1915
2481
 
1916
2482
  // src/components/ServiceList.tsx
1917
- import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
2483
+ import { jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
1918
2484
  function ServiceList({ services, onSelect }) {
1919
2485
  const { locale } = useSchedulingConfig();
1920
2486
  const messages = resolveSchedulingMessages(locale);
1921
2487
  if (services.length === 0) {
1922
- return /* @__PURE__ */ jsx17("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.empty"] });
2488
+ return /* @__PURE__ */ jsx21("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.empty"] });
1923
2489
  }
1924
- return /* @__PURE__ */ jsx17("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__ */ jsx17("li", { className: index % 2 === 1 ? "bg-gray-50 dark:bg-gray-800/50" : void 0, children: /* @__PURE__ */ jsxs16(
2490
+ return /* @__PURE__ */ jsx21("ul", { className: `${SURFACE_BORDER} divide-y divide-gray-200 overflow-hidden dark:divide-gray-800`, children: services.map((service, index) => /* @__PURE__ */ jsx21("li", { className: index % 2 === 1 ? ROW_STRIPE : void 0, children: /* @__PURE__ */ jsxs18(
1925
2491
  "button",
1926
2492
  {
1927
2493
  type: "button",
1928
2494
  onClick: () => onSelect(service),
1929
- 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",
2495
+ className: "flex w-full items-center gap-3 px-4 py-3 min-h-11 text-left text-sm transition-colors hover:bg-brand-50/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-500 dark:hover:bg-brand-900/20",
1930
2496
  children: [
1931
- /* @__PURE__ */ jsx17("span", { className: "flex-1 font-medium text-gray-900 dark:text-gray-100", children: service.name }),
1932
- /* @__PURE__ */ jsxs16("span", { className: "text-gray-500 dark:text-gray-400", children: [
2497
+ /* @__PURE__ */ jsx21("span", { className: "flex-1 font-medium text-gray-900 dark:text-gray-100", children: service.name }),
2498
+ /* @__PURE__ */ jsxs18("span", { className: "text-gray-500 dark:text-gray-400", children: [
1933
2499
  service.durationMinutes,
1934
2500
  " min"
1935
2501
  ] }),
1936
- !service.active && /* @__PURE__ */ jsx17("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"] })
2502
+ !service.active && /* @__PURE__ */ jsx21("span", { className: "rounded-full bg-gray-200 px-2 py-0.5 text-xs font-medium text-gray-600 dark:bg-gray-700 dark:text-gray-300", children: messages["service.inactive"] })
1937
2503
  ]
1938
2504
  }
1939
2505
  ) }, service.id)) });
1940
2506
  }
1941
2507
 
1942
2508
  // src/workspace/ServicesArea.tsx
1943
- import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
1944
- var BUTTON_CLASS3 = "inline-flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-colors min-h-11";
1945
- var BUTTON_PRIMARY4 = `${BUTTON_CLASS3} bg-brand-600 text-white hover:bg-brand-700`;
1946
- var BUTTON_DANGER2 = `${BUTTON_CLASS3} text-red-700 hover:bg-red-50`;
2509
+ import { jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
1947
2510
  function ServicesArea() {
1948
2511
  const { locale } = useSchedulingConfig();
1949
2512
  const messages = resolveSchedulingMessages(locale);
@@ -1951,9 +2514,10 @@ function ServicesArea() {
1951
2514
  const createService = useCreateService();
1952
2515
  const updateService = useUpdateService();
1953
2516
  const deleteService = useDeleteService();
1954
- const [draft, setDraft] = useState12(void 0);
2517
+ const [draft, setDraft] = useState13(void 0);
1955
2518
  const isDraftOpen = draft !== void 0;
1956
2519
  const isEditing = Boolean(draft);
2520
+ const services = data?.data ?? [];
1957
2521
  async function handleSubmit(input) {
1958
2522
  try {
1959
2523
  if (draft) {
@@ -1965,21 +2529,36 @@ function ServicesArea() {
1965
2529
  } catch {
1966
2530
  }
1967
2531
  }
1968
- return /* @__PURE__ */ jsxs17("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col p-4 space-y-4", children: [
1969
- /* @__PURE__ */ jsx18("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxs17("button", { type: "button", onClick: () => setDraft(null), className: `${BUTTON_PRIMARY4} ml-auto`, children: [
1970
- /* @__PURE__ */ jsx18(Plus4, { "aria-hidden": "true", className: "w-4 h-4" }),
2532
+ function renderCreateButton() {
2533
+ return /* @__PURE__ */ jsxs19("button", { type: "button", onClick: () => setDraft(null), className: BUTTON_PRIMARY, children: [
2534
+ /* @__PURE__ */ jsx22(Plus4, { "aria-hidden": "true", className: "h-4 w-4" }),
1971
2535
  messages["service.newService"]
1972
- ] }) }),
1973
- isError && /* @__PURE__ */ jsx18("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.loadFailure"] }),
1974
- (createService.isError || updateService.isError || deleteService.isError) && /* @__PURE__ */ jsx18("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.actionFailure"] }),
1975
- isLoading ? /* @__PURE__ */ jsx18("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.loading"] }) : /* @__PURE__ */ jsx18(ServiceList, { services: data?.data ?? [], onSelect: setDraft }),
1976
- isDraftOpen && /* @__PURE__ */ jsx18(
2536
+ ] });
2537
+ }
2538
+ return /* @__PURE__ */ jsxs19("div", { className: "flex flex-1 min-h-0 min-w-0", children: [
2539
+ /* @__PURE__ */ jsxs19("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col gap-4 overflow-y-auto p-4", children: [
2540
+ /* @__PURE__ */ jsx22("div", { className: "flex justify-end", children: renderCreateButton() }),
2541
+ isError && /* @__PURE__ */ jsx22(ErrorBanner, { message: messages["common.loadFailure"] }),
2542
+ (createService.isError || updateService.isError || deleteService.isError) && /* @__PURE__ */ jsx22(ErrorBanner, { message: messages["common.actionFailure"] }),
2543
+ isLoading && /* @__PURE__ */ jsx22(ListSkeleton, { label: messages["common.loading"] }),
2544
+ !isLoading && !isError && services.length === 0 && /* @__PURE__ */ jsx22(
2545
+ EmptyState,
2546
+ {
2547
+ icon: Wrench,
2548
+ title: messages["service.emptyTitle"],
2549
+ hint: messages["service.emptyHint"],
2550
+ action: renderCreateButton()
2551
+ }
2552
+ ),
2553
+ !isLoading && services.length > 0 && /* @__PURE__ */ jsx22(ServiceList, { services, onSelect: setDraft })
2554
+ ] }),
2555
+ isDraftOpen && /* @__PURE__ */ jsx22(
1977
2556
  SidePanel,
1978
2557
  {
1979
2558
  title: isEditing ? messages["service.editTitle"] : messages["service.createTitle"],
1980
2559
  closeLabel: messages["common.close"],
1981
2560
  onClose: () => setDraft(void 0),
1982
- headerActions: isEditing ? /* @__PURE__ */ jsxs17(
2561
+ headerActions: isEditing ? /* @__PURE__ */ jsxs19(
1983
2562
  "button",
1984
2563
  {
1985
2564
  type: "button",
@@ -1987,14 +2566,14 @@ function ServicesArea() {
1987
2566
  if (draft) void deleteService.mutateAsync(draft.id).then(() => setDraft(void 0)).catch(() => {
1988
2567
  });
1989
2568
  },
1990
- className: BUTTON_DANGER2,
2569
+ className: BUTTON_DANGER,
1991
2570
  children: [
1992
- /* @__PURE__ */ jsx18(Trash24, { "aria-hidden": "true", className: "w-4 h-4" }),
2571
+ /* @__PURE__ */ jsx22(Trash24, { "aria-hidden": "true", className: "h-4 w-4" }),
1993
2572
  messages["common.remove"]
1994
2573
  ]
1995
2574
  }
1996
2575
  ) : void 0,
1997
- children: /* @__PURE__ */ jsx18(
2576
+ children: /* @__PURE__ */ jsx22(
1998
2577
  ServiceForm,
1999
2578
  {
2000
2579
  ...draft ? { initialValues: draft } : {},
@@ -2008,7 +2587,7 @@ function ServicesArea() {
2008
2587
  }
2009
2588
 
2010
2589
  // src/workspace/WorkspaceAreaNav.tsx
2011
- import { CalendarDays, Clock, ClipboardList, Users, Wrench } from "lucide-react";
2590
+ import { CalendarDays as CalendarDays2, Clock as Clock2, ClipboardList as ClipboardList2, Users as Users2, Wrench as Wrench2 } from "lucide-react";
2012
2591
 
2013
2592
  // src/workspace/workspace.constant.ts
2014
2593
  var SCHEDULING_WORKSPACE_AREA = {
@@ -2024,16 +2603,16 @@ function isSchedulingWorkspaceArea(value) {
2024
2603
  }
2025
2604
 
2026
2605
  // src/workspace/WorkspaceAreaNav.tsx
2027
- import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
2028
- 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";
2029
- var ITEM_ACTIVE = "border-brand-600 text-brand-700 dark:text-brand-400";
2606
+ import { jsx as jsx23, jsxs as jsxs20 } from "react/jsx-runtime";
2607
+ 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 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-500";
2608
+ var ITEM_ACTIVE = "border-brand-600 text-brand-700 dark:text-brand-300";
2030
2609
  var ITEM_IDLE = "border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100";
2031
2610
  var AREA_ICON = {
2032
- [SCHEDULING_WORKSPACE_AREA.AGENDA]: CalendarDays,
2033
- [SCHEDULING_WORKSPACE_AREA.BOOKINGS]: ClipboardList,
2034
- [SCHEDULING_WORKSPACE_AREA.RESOURCES]: Users,
2035
- [SCHEDULING_WORKSPACE_AREA.SERVICES]: Wrench,
2036
- [SCHEDULING_WORKSPACE_AREA.AVAILABILITY]: Clock
2611
+ [SCHEDULING_WORKSPACE_AREA.AGENDA]: CalendarDays2,
2612
+ [SCHEDULING_WORKSPACE_AREA.BOOKINGS]: ClipboardList2,
2613
+ [SCHEDULING_WORKSPACE_AREA.RESOURCES]: Users2,
2614
+ [SCHEDULING_WORKSPACE_AREA.SERVICES]: Wrench2,
2615
+ [SCHEDULING_WORKSPACE_AREA.AVAILABILITY]: Clock2
2037
2616
  };
2038
2617
  function WorkspaceAreaNav({ area, labels, onSelect }) {
2039
2618
  const items = [
@@ -2043,9 +2622,9 @@ function WorkspaceAreaNav({ area, labels, onSelect }) {
2043
2622
  [SCHEDULING_WORKSPACE_AREA.SERVICES, labels.servicesTab],
2044
2623
  [SCHEDULING_WORKSPACE_AREA.AVAILABILITY, labels.availabilityTab]
2045
2624
  ];
2046
- return /* @__PURE__ */ jsx19("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]) => {
2625
+ return /* @__PURE__ */ jsx23("nav", { "aria-label": labels.areaNav, className: "flex shrink-0 gap-1 overflow-x-auto border-b border-gray-200 px-4 dark:border-gray-800", children: items.map(([value, label]) => {
2047
2626
  const Icon = AREA_ICON[value];
2048
- return /* @__PURE__ */ jsxs18(
2627
+ return /* @__PURE__ */ jsxs20(
2049
2628
  "button",
2050
2629
  {
2051
2630
  type: "button",
@@ -2053,7 +2632,7 @@ function WorkspaceAreaNav({ area, labels, onSelect }) {
2053
2632
  "aria-current": value === area ? "page" : void 0,
2054
2633
  className: `${ITEM_BASE} ${value === area ? ITEM_ACTIVE : ITEM_IDLE} whitespace-nowrap`,
2055
2634
  children: [
2056
- /* @__PURE__ */ jsx19(Icon, { "aria-hidden": "true", className: "w-4 h-4 shrink-0" }),
2635
+ /* @__PURE__ */ jsx23(Icon, { "aria-hidden": "true", className: "w-4 h-4 shrink-0" }),
2057
2636
  label
2058
2637
  ]
2059
2638
  },
@@ -2063,7 +2642,7 @@ function WorkspaceAreaNav({ area, labels, onSelect }) {
2063
2642
  }
2064
2643
 
2065
2644
  // src/workspace/SchedulingWorkspace.tsx
2066
- import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
2645
+ import { jsx as jsx24, jsxs as jsxs21 } from "react/jsx-runtime";
2067
2646
  var AREA_COMPONENT = {
2068
2647
  [SCHEDULING_WORKSPACE_AREA.AGENDA]: AgendaArea,
2069
2648
  [SCHEDULING_WORKSPACE_AREA.BOOKINGS]: BookingsArea,
@@ -2078,20 +2657,20 @@ function SchedulingWorkspace({
2078
2657
  onAreaChange
2079
2658
  }) {
2080
2659
  const labels = { ...DEFAULT_SCHEDULING_WORKSPACE_LABELS, ...labelsOverride };
2081
- const [internalArea, setInternalArea] = useState13(SCHEDULING_WORKSPACE_AREA.AGENDA);
2660
+ const [internalArea, setInternalArea] = useState14(SCHEDULING_WORKSPACE_AREA.AGENDA);
2082
2661
  const area = areaProp ?? internalArea;
2083
2662
  const AreaComponent = AREA_COMPONENT[area];
2084
2663
  function handleSelectArea(next) {
2085
2664
  setInternalArea(next);
2086
2665
  onAreaChange?.(next);
2087
2666
  }
2088
- return /* @__PURE__ */ jsxs19("div", { className: "flex flex-col h-full", children: [
2089
- /* @__PURE__ */ jsxs19("header", { className: "flex flex-wrap items-center gap-3 px-4 py-3", children: [
2090
- /* @__PURE__ */ jsx20("h1", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100 mr-auto", children: labels.title }),
2667
+ return /* @__PURE__ */ jsxs21("div", { className: "flex h-full flex-col bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100", children: [
2668
+ /* @__PURE__ */ jsxs21("header", { className: "flex shrink-0 flex-wrap items-center gap-3 px-4 pb-2 pt-4", children: [
2669
+ /* @__PURE__ */ jsx24("h1", { className: "mr-auto text-xl font-semibold tracking-tight text-gray-900 dark:text-gray-100", children: labels.title }),
2091
2670
  renderHeaderActions?.()
2092
2671
  ] }),
2093
- /* @__PURE__ */ jsx20(WorkspaceAreaNav, { area, labels, onSelect: handleSelectArea }),
2094
- /* @__PURE__ */ jsx20("div", { className: "relative flex flex-1 min-h-0 overflow-hidden", children: /* @__PURE__ */ jsx20(AreaComponent, {}) })
2672
+ /* @__PURE__ */ jsx24(WorkspaceAreaNav, { area, labels, onSelect: handleSelectArea }),
2673
+ /* @__PURE__ */ jsx24("div", { className: "relative flex flex-1 min-h-0 overflow-hidden", children: /* @__PURE__ */ jsx24(AreaComponent, {}) })
2095
2674
  ] });
2096
2675
  }
2097
2676
  export {