@adatechnology/scheduling-ui 0.1.0-rc.3 → 0.1.0-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -316,7 +316,8 @@ var DEFAULT_SCHEDULING_WORKSPACE_LABELS = {
316
316
  import { useState as useState13 } 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 useEffect2, useState as useState2 } from "react";
320
321
  import { MAX_PAGE_SIZE } from "@adatechnology/scheduling-contracts";
321
322
 
322
323
  // src/locales/pt-BR.json
@@ -410,7 +411,20 @@ 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."
414
428
  };
415
429
 
416
430
  // src/locales/en.json
@@ -504,7 +518,20 @@ var en_default = {
504
518
  "datetime.year": "Year",
505
519
  "datetime.month": "Month",
506
520
  "datetime.day": "Day",
507
- "datetime.time": "Time"
521
+ "datetime.time": "Time",
522
+ "common.emptyHint": "When there is something here, it shows up in this list.",
523
+ "agenda.allResources": "All",
524
+ "agenda.viewLabel": "View",
525
+ "agenda.now": "Now",
526
+ "agenda.emptyTitle": "Nothing scheduled",
527
+ "resource.emptyTitle": "No resources yet",
528
+ "resource.emptyHint": "Resources are who or what serves \u2014 people, rooms, equipment.",
529
+ "service.emptyTitle": "No services yet",
530
+ "service.emptyHint": "Services define what gets booked and how long it takes.",
531
+ "booking.emptyTitle": "No bookings",
532
+ "booking.emptyHint": "Adjust the status filters or wait for new requests.",
533
+ "availability.selectResourceTitle": "Pick a resource",
534
+ "availability.selectResourceHint": "Availability rules are defined per resource."
508
535
  };
509
536
 
510
537
  // src/locales/index.ts
@@ -517,6 +544,47 @@ function resolveSchedulingMessages(locale) {
517
544
  return MESSAGES_BY_LOCALE[locale] ?? MESSAGES_BY_LOCALE[DEFAULT_SCHEDULING_LOCALE];
518
545
  }
519
546
 
547
+ // src/components/BookingStatusBadge.tsx
548
+ import { BOOKING_STATUS } from "@adatechnology/scheduling-contracts";
549
+ import { jsx as jsx2 } from "react/jsx-runtime";
550
+ var BOOKING_STATUS_TONE = {
551
+ [BOOKING_STATUS.REQUESTED]: {
552
+ badge: "bg-amber-100 text-amber-800 dark:bg-amber-900/50 dark:text-amber-200",
553
+ block: "border-amber-400 bg-amber-50 text-amber-900 dark:border-amber-600 dark:bg-amber-900/40 dark:text-amber-100"
554
+ },
555
+ [BOOKING_STATUS.CONFIRMED]: {
556
+ badge: "bg-brand-100 text-brand-800 dark:bg-brand-900/50 dark:text-brand-200",
557
+ block: "border-brand-400 bg-brand-50 text-brand-900 dark:border-brand-600 dark:bg-brand-900/40 dark:text-brand-100"
558
+ },
559
+ [BOOKING_STATUS.CANCELLED]: {
560
+ badge: "bg-gray-200 text-gray-600 line-through dark:bg-gray-700 dark:text-gray-400",
561
+ block: "border-gray-300 bg-gray-100 text-gray-500 line-through dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400"
562
+ },
563
+ [BOOKING_STATUS.COMPLETED]: {
564
+ badge: "bg-emerald-100 text-emerald-800 dark:bg-emerald-900/50 dark:text-emerald-200",
565
+ block: "border-emerald-400 bg-emerald-50 text-emerald-900 dark:border-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-100"
566
+ },
567
+ [BOOKING_STATUS.NO_SHOW]: {
568
+ badge: "bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-200",
569
+ block: "border-red-400 bg-red-50 text-red-900 dark:border-red-600 dark:bg-red-900/40 dark:text-red-100"
570
+ }
571
+ };
572
+ function bookingStatusLabel(messages, status) {
573
+ const key = status === BOOKING_STATUS.NO_SHOW ? "booking.status.noShow" : `booking.status.${status}`;
574
+ return messages[key];
575
+ }
576
+ function BookingStatusBadge({ status }) {
577
+ const { locale } = useSchedulingConfig();
578
+ const messages = resolveSchedulingMessages(locale);
579
+ return /* @__PURE__ */ jsx2(
580
+ "span",
581
+ {
582
+ className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${BOOKING_STATUS_TONE[status].badge}`,
583
+ children: bookingStatusLabel(messages, status)
584
+ }
585
+ );
586
+ }
587
+
520
588
  // src/components/agendaLayout.util.ts
521
589
  function groupOverlappingBookings(sortedBookings) {
522
590
  const clusters = [];
@@ -565,7 +633,9 @@ function startOfDay(date) {
565
633
  }
566
634
 
567
635
  // src/components/AgendaGrid.tsx
568
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
636
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
637
+ var HOUR_HEIGHT_REM = 4;
638
+ 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
639
  function buildHours(startHour, endHour) {
570
640
  return Array.from({ length: endHour - startHour }, (_hour, index) => startHour + index);
571
641
  }
@@ -575,14 +645,18 @@ function bookingsOnDay(bookings, day, startHour, endHour) {
575
645
  const visibleEnd = new Date(dayStart.getTime() + endHour * 60 * 6e4);
576
646
  return bookings.filter((booking) => booking.startsAt < visibleEnd && booking.endsAt > visibleStart);
577
647
  }
578
- function AgendaGrid({ bookings, days }) {
648
+ function isSameDay(left, right) {
649
+ return startOfDay(left).getTime() === startOfDay(right).getTime();
650
+ }
651
+ function AgendaGrid({ bookings, days, now, onSelect }) {
579
652
  const { locale, agendaStartHour, agendaEndHour } = useSchedulingConfig();
580
653
  const messages = resolveSchedulingMessages(locale);
581
654
  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: [
655
+ const columnHeight = `${hours.length * HOUR_HEIGHT_REM}rem`;
656
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-1 min-h-0 overflow-auto rounded-xl border border-gray-200 dark:border-gray-800", children: [
657
+ /* @__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: [
658
+ /* @__PURE__ */ jsx3("div", { className: `${HEADER_CLASS} border-b border-gray-200 dark:border-gray-800` }),
659
+ 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
660
  String(hour).padStart(2, "0"),
587
661
  ":00"
588
662
  ] }, hour))
@@ -591,47 +665,498 @@ function AgendaGrid({ bookings, days }) {
591
665
  const dayBookings = bookingsOnDay(bookings, day, agendaStartHour, agendaEndHour);
592
666
  const positioned = layoutDayBookings(dayBookings);
593
667
  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(
668
+ const isToday = isSameDay(day, now);
669
+ const nowPercent = toVisiblePercent({
670
+ minutesSinceDayStart: minutesFromDayStart(now, dayStart),
671
+ startHour: agendaStartHour,
672
+ endHour: agendaEndHour
673
+ });
674
+ const showNowLine = isToday && nowPercent >= 0 && nowPercent <= 100;
675
+ return /* @__PURE__ */ jsxs(
676
+ "div",
677
+ {
678
+ 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" : ""}`,
679
+ children: [
680
+ /* @__PURE__ */ jsxs(
611
681
  "div",
612
682
  {
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}%`
683
+ 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" : ""}`,
684
+ children: [
685
+ day.toLocaleDateString(locale, { weekday: "short", day: "2-digit", month: "2-digit" }),
686
+ isToday && /* @__PURE__ */ jsx3("span", { className: "h-1.5 w-1.5 rounded-full bg-brand-600", "aria-hidden": "true" })
687
+ ]
688
+ }
689
+ ),
690
+ /* @__PURE__ */ jsxs("div", { className: "relative", style: { height: columnHeight }, children: [
691
+ hours.map((hour, index) => /* @__PURE__ */ jsx3(
692
+ "div",
693
+ {
694
+ "aria-hidden": "true",
695
+ className: "absolute inset-x-0 border-b border-gray-100 dark:border-gray-800/60",
696
+ style: { top: `${index / hours.length * 100}%`, height: `${100 / hours.length}%` }
620
697
  },
621
- children: booking.title
622
- },
623
- booking.id
624
- );
625
- })
626
- ] })
627
- ] }, day.toISOString());
698
+ hour
699
+ )),
700
+ 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"] }),
701
+ positioned.map(({ booking, columnIndex, columnCount }) => {
702
+ const top = toVisiblePercent({
703
+ minutesSinceDayStart: minutesFromDayStart(booking.startsAt, dayStart),
704
+ startHour: agendaStartHour,
705
+ endHour: agendaEndHour
706
+ });
707
+ const bottom = toVisiblePercent({
708
+ minutesSinceDayStart: minutesFromDayStart(booking.endsAt, dayStart),
709
+ startHour: agendaStartHour,
710
+ endHour: agendaEndHour
711
+ });
712
+ const width = 100 / columnCount;
713
+ const startLabel = booking.startsAt.toLocaleTimeString(locale, { hour: "2-digit", minute: "2-digit" });
714
+ return /* @__PURE__ */ jsxs(
715
+ "button",
716
+ {
717
+ type: "button",
718
+ onClick: () => onSelect?.(booking),
719
+ title: `${startLabel} \xB7 ${booking.title}`,
720
+ 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}`,
721
+ style: {
722
+ top: `${top}%`,
723
+ height: `${Math.max(bottom - top, 2)}%`,
724
+ left: `${columnIndex * width}%`,
725
+ width: `${width}%`
726
+ },
727
+ children: [
728
+ /* @__PURE__ */ jsx3("span", { className: "block font-medium tabular-nums opacity-80", children: startLabel }),
729
+ /* @__PURE__ */ jsx3("span", { className: "block truncate", children: booking.title })
730
+ ]
731
+ },
732
+ booking.id
733
+ );
734
+ }),
735
+ showNowLine && /* @__PURE__ */ jsxs(
736
+ "div",
737
+ {
738
+ "aria-label": messages["agenda.now"],
739
+ className: "pointer-events-none absolute inset-x-0 z-10 flex items-center",
740
+ style: { top: `${nowPercent}%` },
741
+ children: [
742
+ /* @__PURE__ */ jsx3("span", { className: "h-2 w-2 shrink-0 rounded-full bg-red-500" }),
743
+ /* @__PURE__ */ jsx3("span", { className: "h-px flex-1 bg-red-500" })
744
+ ]
745
+ }
746
+ )
747
+ ] })
748
+ ]
749
+ },
750
+ day.toISOString()
751
+ );
628
752
  })
629
753
  ] });
630
754
  }
631
755
 
756
+ // src/components/BookingDrawer.tsx
757
+ import { XCircle } from "lucide-react";
758
+ import { useState } from "react";
759
+ import { BOOKING_STATUS as BOOKING_STATUS2 } from "@adatechnology/scheduling-contracts";
760
+
761
+ // src/components/DateTimeField.tsx
762
+ import { useId } from "react";
763
+
764
+ // src/components/dateTimeParts.util.ts
765
+ var DATE_TIME_LOCAL_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/;
766
+ var MONTHS_IN_YEAR = 12;
767
+ function parseDateTimeParts(value) {
768
+ const match = DATE_TIME_LOCAL_PATTERN.exec(value);
769
+ if (!match) return void 0;
770
+ const [, year, month, day, hour, minute] = match;
771
+ return { year: Number(year), month: Number(month), day: Number(day), time: `${hour}:${minute}` };
772
+ }
773
+ function daysInMonth(params) {
774
+ return new Date(Date.UTC(params.year, params.month, 0)).getUTCDate();
775
+ }
776
+ function pad(value) {
777
+ return String(value).padStart(2, "0");
778
+ }
779
+ function formatDateTimeParts(parts) {
780
+ const lastDay = daysInMonth({ year: parts.year, month: parts.month });
781
+ const day = Math.min(Math.max(parts.day, 1), lastDay);
782
+ return `${String(parts.year).padStart(4, "0")}-${pad(parts.month)}-${pad(day)}T${parts.time}`;
783
+ }
784
+ function buildYearOptions(params) {
785
+ const current = params.today.getFullYear();
786
+ const first = Math.min(current - params.past, params.year);
787
+ const last = Math.max(current + params.future, params.year);
788
+ return Array.from({ length: last - first + 1 }, (_, index) => first + index);
789
+ }
790
+
791
+ // src/components/DateTimeField.tsx
792
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
793
+ 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";
794
+ var YEAR_WINDOW = { PAST: 1, FUTURE: 2 };
795
+ function partsOf(value, fallback) {
796
+ return parseDateTimeParts(value) ?? {
797
+ year: fallback.getFullYear(),
798
+ month: fallback.getMonth() + 1,
799
+ day: fallback.getDate(),
800
+ time: "09:00"
801
+ };
802
+ }
803
+ function monthLabels(locale) {
804
+ const format = new Intl.DateTimeFormat(locale, { month: "long", timeZone: "UTC" });
805
+ return Array.from(
806
+ { length: MONTHS_IN_YEAR },
807
+ (_, index) => format.format(new Date(Date.UTC(2026, index, 1)))
808
+ );
809
+ }
810
+ function DateTimeField({ label, value, onChange, emptyDefault }) {
811
+ const { locale } = useSchedulingConfig();
812
+ const messages = resolveSchedulingMessages(locale);
813
+ const groupId = useId();
814
+ const today = emptyDefault ?? /* @__PURE__ */ new Date();
815
+ const parts = partsOf(value, today);
816
+ const years = buildYearOptions({
817
+ year: parts.year,
818
+ today,
819
+ past: YEAR_WINDOW.PAST,
820
+ future: YEAR_WINDOW.FUTURE
821
+ });
822
+ const days = daysInMonth({ year: parts.year, month: parts.month });
823
+ const months = monthLabels(locale);
824
+ function change(patch) {
825
+ onChange(formatDateTimeParts({ ...parts, ...patch }));
826
+ }
827
+ return /* @__PURE__ */ jsxs2("div", { role: "group", "aria-labelledby": groupId, className: "flex flex-wrap items-center gap-2", children: [
828
+ /* @__PURE__ */ jsx4("span", { id: groupId, className: "sr-only", children: label }),
829
+ /* @__PURE__ */ jsx4(
830
+ "select",
831
+ {
832
+ "aria-label": `${label} \u2014 ${messages["datetime.year"]}`,
833
+ value: parts.year,
834
+ onChange: (event) => change({ year: Number(event.target.value) }),
835
+ className: FIELD_CLASS,
836
+ children: years.map((year) => /* @__PURE__ */ jsx4("option", { value: year, children: year }, year))
837
+ }
838
+ ),
839
+ /* @__PURE__ */ jsx4(
840
+ "select",
841
+ {
842
+ "aria-label": `${label} \u2014 ${messages["datetime.month"]}`,
843
+ value: parts.month,
844
+ onChange: (event) => change({ month: Number(event.target.value) }),
845
+ className: FIELD_CLASS,
846
+ children: months.map((name, index) => /* @__PURE__ */ jsx4("option", { value: index + 1, children: name }, name))
847
+ }
848
+ ),
849
+ /* @__PURE__ */ jsx4(
850
+ "select",
851
+ {
852
+ "aria-label": `${label} \u2014 ${messages["datetime.day"]}`,
853
+ value: Math.min(parts.day, days),
854
+ onChange: (event) => change({ day: Number(event.target.value) }),
855
+ className: FIELD_CLASS,
856
+ children: Array.from({ length: days }, (_, index) => index + 1).map((day) => /* @__PURE__ */ jsx4("option", { value: day, children: day }, day))
857
+ }
858
+ ),
859
+ /* @__PURE__ */ jsx4(
860
+ "input",
861
+ {
862
+ type: "time",
863
+ "aria-label": `${label} \u2014 ${messages["datetime.time"]}`,
864
+ value: parts.time,
865
+ onChange: (event) => change({ time: event.target.value }),
866
+ className: FIELD_CLASS
867
+ }
868
+ )
869
+ ] });
870
+ }
871
+
872
+ // src/components/datetimeLocal.util.ts
873
+ function getTimeZoneOffsetMs(instant, timeZone) {
874
+ const parts = new Intl.DateTimeFormat("en-US", {
875
+ timeZone,
876
+ year: "numeric",
877
+ month: "2-digit",
878
+ day: "2-digit",
879
+ hour: "2-digit",
880
+ minute: "2-digit",
881
+ second: "2-digit",
882
+ hourCycle: "h23"
883
+ }).formatToParts(instant);
884
+ const get = (type) => Number(parts.find((part) => part.type === type)?.value ?? 0);
885
+ const asUtcOfWallClock = Date.UTC(
886
+ get("year"),
887
+ get("month") - 1,
888
+ get("day"),
889
+ get("hour") % 24,
890
+ get("minute"),
891
+ get("second")
892
+ );
893
+ return asUtcOfWallClock - instant.getTime();
894
+ }
895
+ function parseDateTimeLocalInTimeZone(value, timeZone) {
896
+ const naiveUtc = /* @__PURE__ */ new Date(`${value}:00.000Z`);
897
+ const firstOffset = getTimeZoneOffsetMs(naiveUtc, timeZone);
898
+ const firstPass = new Date(naiveUtc.getTime() - firstOffset);
899
+ const secondOffset = getTimeZoneOffsetMs(firstPass, timeZone);
900
+ return new Date(naiveUtc.getTime() - secondOffset);
901
+ }
902
+ function formatDateTimeLocalInTimeZone(instant, timeZone) {
903
+ const parts = new Intl.DateTimeFormat("en-US", {
904
+ timeZone,
905
+ year: "numeric",
906
+ month: "2-digit",
907
+ day: "2-digit",
908
+ hour: "2-digit",
909
+ minute: "2-digit",
910
+ hourCycle: "h23"
911
+ }).formatToParts(instant);
912
+ const get = (type) => parts.find((part) => part.type === type)?.value ?? "00";
913
+ const hour = get("hour") === "24" ? "00" : get("hour");
914
+ return `${get("year")}-${get("month")}-${get("day")}T${hour}:${get("minute")}`;
915
+ }
916
+
917
+ // src/components/SidePanel.tsx
918
+ import { X } from "lucide-react";
919
+
920
+ // src/components/ui.constant.ts
921
+ 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";
922
+ var BUTTON_PRIMARY = `${BUTTON_BASE} bg-brand-600 text-white hover:bg-brand-700`;
923
+ 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`;
924
+ var BUTTON_GHOST = `${BUTTON_BASE} text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800`;
925
+ var BUTTON_DANGER = `${BUTTON_BASE} text-red-700 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950/40`;
926
+ var ICON_BUTTON = `${BUTTON_SECONDARY} min-w-11 px-0`;
927
+ 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";
928
+ var FIELD_LABEL = "text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400";
929
+ var SURFACE_BORDER = "rounded-xl border border-gray-200 dark:border-gray-800";
930
+ var ROW_STRIPE = "bg-gray-50 dark:bg-gray-800/40";
931
+
932
+ // src/components/SidePanel.tsx
933
+ import { useEffect } from "react";
934
+ import { Fragment, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
935
+ function SidePanel({ title, closeLabel, onClose, headerActions, children }) {
936
+ useEffect(() => {
937
+ function handleKeyDown(event) {
938
+ if (event.key === "Escape") onClose();
939
+ }
940
+ window.addEventListener("keydown", handleKeyDown);
941
+ return () => window.removeEventListener("keydown", handleKeyDown);
942
+ }, [onClose]);
943
+ return /* @__PURE__ */ jsxs3(Fragment, { children: [
944
+ /* @__PURE__ */ jsx5(
945
+ "button",
946
+ {
947
+ type: "button",
948
+ "aria-hidden": "true",
949
+ tabIndex: -1,
950
+ onClick: onClose,
951
+ className: "absolute inset-0 z-10 bg-gray-900/30 backdrop-blur-[1px] wide:hidden"
952
+ }
953
+ ),
954
+ /* @__PURE__ */ jsxs3(
955
+ "section",
956
+ {
957
+ "aria-label": title,
958
+ 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",
959
+ children: [
960
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2 border-b border-gray-200 px-4 py-3 dark:border-gray-700", children: [
961
+ /* @__PURE__ */ jsx5("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100 mr-auto", children: title }),
962
+ headerActions,
963
+ /* @__PURE__ */ jsxs3(
964
+ "button",
965
+ {
966
+ type: "button",
967
+ onClick: onClose,
968
+ className: BUTTON_GHOST,
969
+ children: [
970
+ /* @__PURE__ */ jsx5(X, { "aria-hidden": "true", className: "w-4 h-4" }),
971
+ closeLabel
972
+ ]
973
+ }
974
+ )
975
+ ] }),
976
+ /* @__PURE__ */ jsx5("div", { className: "min-h-0 flex-1 overflow-y-auto p-4", children })
977
+ ]
978
+ }
979
+ )
980
+ ] });
981
+ }
982
+
983
+ // src/components/BookingDrawer.tsx
984
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
985
+ var BROWSER_TIME_ZONE = Intl.DateTimeFormat().resolvedOptions().timeZone;
986
+ 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";
987
+ var PRIMARY_BUTTON_CLASS = "min-h-11 px-3 rounded-lg text-sm font-medium bg-brand-600 text-white hover:bg-brand-700";
988
+ 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";
989
+ 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";
990
+ function BookingDrawer({ booking, resourceTimezone, onClose }) {
991
+ const { locale } = useSchedulingConfig();
992
+ const messages = resolveSchedulingMessages(locale);
993
+ const confirmBooking = useConfirmBooking();
994
+ const completeBooking = useCompleteBooking();
995
+ const markNoShow = useMarkNoShow();
996
+ const cancelBooking = useCancelBooking();
997
+ const rescheduleBooking = useRescheduleBooking();
998
+ const timezone = resourceTimezone ?? BROWSER_TIME_ZONE;
999
+ const [isCancelling, setIsCancelling] = useState(false);
1000
+ const [cancelledBy, setCancelledBy] = useState("");
1001
+ const [cancellationReason, setCancellationReason] = useState("");
1002
+ const [isRescheduling, setIsRescheduling] = useState(false);
1003
+ const [start, setStart] = useState(() => formatDateTimeLocalInTimeZone(booking.startsAt, timezone));
1004
+ const [end, setEnd] = useState(() => formatDateTimeLocalInTimeZone(booking.endsAt, timezone));
1005
+ async function handleCancel() {
1006
+ if (!cancelledBy) return;
1007
+ try {
1008
+ await cancelBooking.mutateAsync({
1009
+ id: booking.id,
1010
+ input: { cancelledBy, ...cancellationReason ? { cancellationReason } : {} }
1011
+ });
1012
+ onClose();
1013
+ } catch {
1014
+ }
1015
+ }
1016
+ async function handleReschedule() {
1017
+ try {
1018
+ await rescheduleBooking.mutateAsync({
1019
+ id: booking.id,
1020
+ input: {
1021
+ during: {
1022
+ start: parseDateTimeLocalInTimeZone(start, timezone),
1023
+ end: parseDateTimeLocalInTimeZone(end, timezone)
1024
+ }
1025
+ }
1026
+ });
1027
+ setIsRescheduling(false);
1028
+ } catch {
1029
+ }
1030
+ }
1031
+ const isTerminal = booking.status === BOOKING_STATUS2.CANCELLED || booking.status === BOOKING_STATUS2.COMPLETED || booking.status === BOOKING_STATUS2.NO_SHOW;
1032
+ return /* @__PURE__ */ jsx6(SidePanel, { title: messages["booking.detailTitle"], closeLabel: messages["common.close"], onClose, children: /* @__PURE__ */ jsxs4("div", { className: "space-y-4", children: [
1033
+ /* @__PURE__ */ jsxs4("div", { children: [
1034
+ /* @__PURE__ */ jsx6("p", { className: "text-base font-semibold text-gray-900 dark:text-gray-100", children: booking.title }),
1035
+ /* @__PURE__ */ jsx6("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages[`booking.status.${booking.status === "no_show" ? "noShow" : booking.status}`] }),
1036
+ /* @__PURE__ */ jsxs4("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: [
1037
+ booking.startsAt.toLocaleString(locale),
1038
+ " \u2192 ",
1039
+ booking.endsAt.toLocaleString(locale)
1040
+ ] })
1041
+ ] }),
1042
+ (confirmBooking.isError || completeBooking.isError || markNoShow.isError || cancelBooking.isError || rescheduleBooking.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"] }),
1043
+ !isTerminal && /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap gap-2", children: [
1044
+ booking.status === BOOKING_STATUS2.REQUESTED && /* @__PURE__ */ jsx6("button", { type: "button", onClick: () => confirmBooking.mutate(booking.id), className: PRIMARY_BUTTON_CLASS, children: messages["booking.confirm"] }),
1045
+ booking.status === BOOKING_STATUS2.CONFIRMED && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1046
+ /* @__PURE__ */ jsx6("button", { type: "button", onClick: () => completeBooking.mutate(booking.id), className: ACTION_BUTTON_CLASS, children: messages["booking.complete"] }),
1047
+ /* @__PURE__ */ jsx6("button", { type: "button", onClick: () => markNoShow.mutate(booking.id), className: ACTION_BUTTON_CLASS, children: messages["booking.markNoShow"] }),
1048
+ /* @__PURE__ */ jsx6("button", { type: "button", onClick: () => setIsRescheduling(true), className: ACTION_BUTTON_CLASS, children: messages["booking.reschedule"] })
1049
+ ] }),
1050
+ /* @__PURE__ */ jsxs4("button", { type: "button", onClick: () => setIsCancelling(true), className: DANGER_BUTTON_CLASS, children: [
1051
+ /* @__PURE__ */ jsx6(XCircle, { "aria-hidden": "true", className: "w-4 h-4" }),
1052
+ messages["booking.cancel"]
1053
+ ] })
1054
+ ] }),
1055
+ isRescheduling && /* @__PURE__ */ jsxs4("div", { className: "space-y-2 rounded-lg border border-gray-200 dark:border-gray-700 p-3", children: [
1056
+ /* @__PURE__ */ jsx6(
1057
+ DateTimeField,
1058
+ {
1059
+ label: messages["booking.rescheduleStart"],
1060
+ value: start,
1061
+ onChange: setStart
1062
+ }
1063
+ ),
1064
+ /* @__PURE__ */ jsx6(DateTimeField, { label: messages["booking.rescheduleEnd"], value: end, onChange: setEnd }),
1065
+ /* @__PURE__ */ jsx6("button", { type: "button", onClick: handleReschedule, disabled: rescheduleBooking.isPending, className: PRIMARY_BUTTON_CLASS, children: messages["common.save"] })
1066
+ ] }),
1067
+ isCancelling && /* @__PURE__ */ jsxs4("div", { className: "space-y-2 rounded-lg border border-gray-200 dark:border-gray-700 p-3", children: [
1068
+ /* @__PURE__ */ jsx6(
1069
+ "input",
1070
+ {
1071
+ type: "text",
1072
+ placeholder: messages["booking.cancelledBy"],
1073
+ value: cancelledBy,
1074
+ onChange: (event) => setCancelledBy(event.target.value),
1075
+ className: INPUT_CLASS
1076
+ }
1077
+ ),
1078
+ /* @__PURE__ */ jsx6(
1079
+ "input",
1080
+ {
1081
+ type: "text",
1082
+ placeholder: messages["booking.cancelReason"],
1083
+ value: cancellationReason,
1084
+ onChange: (event) => setCancellationReason(event.target.value),
1085
+ className: INPUT_CLASS
1086
+ }
1087
+ ),
1088
+ /* @__PURE__ */ jsxs4("button", { type: "button", onClick: handleCancel, disabled: cancelBooking.isPending, className: DANGER_BUTTON_CLASS, children: [
1089
+ /* @__PURE__ */ jsx6(XCircle, { "aria-hidden": "true", className: "w-4 h-4" }),
1090
+ messages["booking.cancel"]
1091
+ ] })
1092
+ ] })
1093
+ ] }) });
1094
+ }
1095
+
1096
+ // src/components/ResourceSelect.tsx
1097
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1098
+ function ResourceSelect({ label, emptyOptionLabel, resources, value, onChange }) {
1099
+ return /* @__PURE__ */ jsxs5("label", { className: "flex flex-col gap-1", children: [
1100
+ /* @__PURE__ */ jsx7("span", { className: FIELD_LABEL, children: label }),
1101
+ /* @__PURE__ */ jsxs5(
1102
+ "select",
1103
+ {
1104
+ value,
1105
+ onChange: (event) => onChange(event.target.value),
1106
+ className: `${FIELD_CONTROL} min-w-44`,
1107
+ children: [
1108
+ /* @__PURE__ */ jsx7("option", { value: "", children: emptyOptionLabel }),
1109
+ resources.map((resource) => /* @__PURE__ */ jsx7("option", { value: resource.id, children: resource.name }, resource.id))
1110
+ ]
1111
+ }
1112
+ )
1113
+ ] });
1114
+ }
1115
+
1116
+ // src/components/StateFeedback.tsx
1117
+ import { AlertTriangle } from "lucide-react";
1118
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1119
+ function ErrorBanner({ message }) {
1120
+ return /* @__PURE__ */ jsxs6(
1121
+ "p",
1122
+ {
1123
+ role: "alert",
1124
+ 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",
1125
+ children: [
1126
+ /* @__PURE__ */ jsx8(AlertTriangle, { "aria-hidden": "true", className: "mt-0.5 h-4 w-4 shrink-0" }),
1127
+ message
1128
+ ]
1129
+ }
1130
+ );
1131
+ }
1132
+ function EmptyState({ icon: Icon, title, hint, action }) {
1133
+ return /* @__PURE__ */ jsxs6("div", { className: `${SURFACE_BORDER} flex flex-col items-center gap-2 px-6 py-12 text-center`, children: [
1134
+ /* @__PURE__ */ jsx8("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__ */ jsx8(Icon, { "aria-hidden": "true", className: "h-6 w-6" }) }),
1135
+ /* @__PURE__ */ jsx8("p", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: title }),
1136
+ hint && /* @__PURE__ */ jsx8("p", { className: "max-w-sm text-sm text-gray-500 dark:text-gray-400", children: hint }),
1137
+ action && /* @__PURE__ */ jsx8("div", { className: "mt-2", children: action })
1138
+ ] });
1139
+ }
1140
+ var SKELETON_ROW = "h-11 animate-pulse rounded-lg bg-gray-100 dark:bg-gray-800";
1141
+ function ListSkeleton({ label, rows = 5 }) {
1142
+ return /* @__PURE__ */ jsx8("div", { "aria-busy": "true", "aria-label": label, role: "status", className: "space-y-2", children: Array.from({ length: rows }, (_row, index) => /* @__PURE__ */ jsx8("div", { className: SKELETON_ROW }, index)) });
1143
+ }
1144
+ function BlockSkeleton({ label }) {
1145
+ return /* @__PURE__ */ jsx8(
1146
+ "div",
1147
+ {
1148
+ "aria-busy": "true",
1149
+ "aria-label": label,
1150
+ role: "status",
1151
+ className: "min-h-64 flex-1 animate-pulse rounded-xl bg-gray-100 dark:bg-gray-800"
1152
+ }
1153
+ );
1154
+ }
1155
+
632
1156
  // src/workspace/AgendaArea.tsx
633
- import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1157
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
634
1158
  var DAY_IN_MS = 24 * 60 * 6e4;
1159
+ var NOW_TICK_MS = 6e4;
635
1160
  function startOfWeek(date, weekStartsOn) {
636
1161
  const start = startOfDay(date);
637
1162
  const offset = (start.getDay() - weekStartsOn + 7) % 7;
@@ -642,14 +1167,32 @@ function buildVisibleDays(anchorDate, view, weekStartsOn) {
642
1167
  const weekStart = startOfWeek(anchorDate, weekStartsOn);
643
1168
  return Array.from({ length: 7 }, (_day, index) => new Date(weekStart.getTime() + index * DAY_IN_MS));
644
1169
  }
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";
1170
+ function formatPeriod(days, locale) {
1171
+ const first = days[0];
1172
+ if (days.length === 1) {
1173
+ return first.toLocaleDateString(locale, { weekday: "long", day: "2-digit", month: "long", year: "numeric" });
1174
+ }
1175
+ const last = days[days.length - 1];
1176
+ const short = { day: "2-digit", month: "short" };
1177
+ return `${first.toLocaleDateString(locale, short)} \u2013 ${last.toLocaleDateString(locale, { ...short, year: "numeric" })}`;
1178
+ }
1179
+ function useNow() {
1180
+ const [now, setNow] = useState2(() => /* @__PURE__ */ new Date());
1181
+ useEffect2(() => {
1182
+ const timer = window.setInterval(() => setNow(/* @__PURE__ */ new Date()), NOW_TICK_MS);
1183
+ return () => window.clearInterval(timer);
1184
+ }, []);
1185
+ return now;
1186
+ }
646
1187
  function AgendaArea() {
647
1188
  const { locale, weekStartsOn } = useSchedulingConfig();
648
1189
  const messages = resolveSchedulingMessages(locale);
1190
+ const now = useNow();
649
1191
  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());
1192
+ const [resourceId, setResourceId] = useState2("");
1193
+ const [view, setView] = useState2("day");
1194
+ const [anchorDate, setAnchorDate] = useState2(() => /* @__PURE__ */ new Date());
1195
+ const [selectedBookingId, setSelectedBookingId] = useState2(void 0);
653
1196
  const resources = resourcesPage?.data ?? [];
654
1197
  const days = buildVisibleDays(anchorDate, view, weekStartsOn);
655
1198
  const from = days[0];
@@ -657,70 +1200,115 @@ function AgendaArea() {
657
1200
  const { data: bookingsPage, isLoading, isError } = useBookings(
658
1201
  resourceId ? { resourceId, from, until, pageSize: MAX_PAGE_SIZE } : { from, until, pageSize: MAX_PAGE_SIZE }
659
1202
  );
660
- function shiftAnchor(days_) {
661
- setAnchorDate((current) => new Date(current.getTime() + days_ * DAY_IN_MS));
1203
+ const visibleBookings = bookingsPage?.data ?? [];
1204
+ const selectedBooking = visibleBookings.find((booking) => booking.id === selectedBookingId);
1205
+ const selectedResourceTimezone = resources.find(
1206
+ (resource) => resource.id === selectedBooking?.resourceIds[0]
1207
+ )?.timezone;
1208
+ function shiftAnchor(direction) {
1209
+ const step = view === "day" ? 1 : 7;
1210
+ setAnchorDate((current) => new Date(current.getTime() + direction * step * DAY_IN_MS));
1211
+ }
1212
+ function renderViewOption(value, label) {
1213
+ const isActive = view === value;
1214
+ return /* @__PURE__ */ jsx9(
1215
+ "button",
1216
+ {
1217
+ type: "button",
1218
+ onClick: () => setView(value),
1219
+ "aria-pressed": isActive,
1220
+ 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"}`,
1221
+ children: label
1222
+ }
1223
+ );
662
1224
  }
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",
1225
+ return /* @__PURE__ */ jsxs7("div", { className: "flex flex-1 min-h-0 min-w-0", children: [
1226
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col gap-4 overflow-y-auto p-4", children: [
1227
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-end gap-3", children: [
1228
+ /* @__PURE__ */ jsx9(
1229
+ ResourceSelect,
669
1230
  {
1231
+ label: messages["agenda.resourceLabel"],
1232
+ emptyOptionLabel: messages["agenda.allResources"],
1233
+ resources,
670
1234
  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"]
1235
+ onChange: setResourceId
694
1236
  }
695
1237
  ),
696
- /* @__PURE__ */ jsx3(
697
- "button",
1238
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1", children: [
1239
+ /* @__PURE__ */ jsx9(
1240
+ "button",
1241
+ {
1242
+ type: "button",
1243
+ "aria-label": messages["agenda.previous"],
1244
+ onClick: () => shiftAnchor(-1),
1245
+ className: ICON_BUTTON,
1246
+ children: /* @__PURE__ */ jsx9(ChevronLeft, { "aria-hidden": "true", className: "h-4 w-4" })
1247
+ }
1248
+ ),
1249
+ /* @__PURE__ */ jsx9("button", { type: "button", onClick: () => setAnchorDate(/* @__PURE__ */ new Date()), className: BUTTON_SECONDARY, children: messages["agenda.today"] }),
1250
+ /* @__PURE__ */ jsx9(
1251
+ "button",
1252
+ {
1253
+ type: "button",
1254
+ "aria-label": messages["agenda.next"],
1255
+ onClick: () => shiftAnchor(1),
1256
+ className: ICON_BUTTON,
1257
+ children: /* @__PURE__ */ jsx9(ChevronRight, { "aria-hidden": "true", className: "h-4 w-4" })
1258
+ }
1259
+ )
1260
+ ] }),
1261
+ /* @__PURE__ */ jsx9(
1262
+ "p",
698
1263
  {
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"]
1264
+ "aria-live": "polite",
1265
+ className: "min-w-0 flex-1 truncate text-sm font-semibold text-gray-900 first-letter:uppercase dark:text-gray-100",
1266
+ children: formatPeriod(days, locale)
704
1267
  }
705
- )
706
- ] })
1268
+ ),
1269
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1", children: [
1270
+ /* @__PURE__ */ jsx9("span", { className: FIELD_LABEL, children: messages["agenda.viewLabel"] }),
1271
+ /* @__PURE__ */ jsxs7("div", { className: "flex gap-1 rounded-lg bg-gray-100 p-1 dark:bg-gray-800", children: [
1272
+ renderViewOption("day", messages["agenda.viewDay"]),
1273
+ renderViewOption("week", messages["agenda.viewWeek"])
1274
+ ] })
1275
+ ] })
1276
+ ] }),
1277
+ isError && /* @__PURE__ */ jsx9(ErrorBanner, { message: messages["common.loadFailure"] }),
1278
+ bookingsPage && bookingsPage.total > bookingsPage.data.length && /* @__PURE__ */ jsx9("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"] }),
1279
+ isLoading && /* @__PURE__ */ jsx9(BlockSkeleton, { label: messages["common.loading"] }),
1280
+ !isLoading && !isError && visibleBookings.length === 0 && /* @__PURE__ */ jsx9(EmptyState, { icon: CalendarDays, title: messages["agenda.emptyTitle"], hint: messages["agenda.empty"] }),
1281
+ !isLoading && visibleBookings.length > 0 && /* @__PURE__ */ jsx9(
1282
+ AgendaGrid,
1283
+ {
1284
+ bookings: visibleBookings,
1285
+ days,
1286
+ now,
1287
+ onSelect: (booking) => setSelectedBookingId(booking.id)
1288
+ }
1289
+ )
707
1290
  ] }),
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
- ] })
1291
+ selectedBooking && /* @__PURE__ */ jsx9(
1292
+ BookingDrawer,
1293
+ {
1294
+ booking: selectedBooking,
1295
+ resourceTimezone: selectedResourceTimezone,
1296
+ onClose: () => setSelectedBookingId(void 0)
1297
+ },
1298
+ selectedBooking.id
1299
+ )
713
1300
  ] });
714
1301
  }
715
1302
 
716
1303
  // src/workspace/AvailabilityArea.tsx
717
- import { useState as useState4 } from "react";
1304
+ import { Clock } from "lucide-react";
1305
+ import { useState as useState5 } from "react";
718
1306
  import { MAX_PAGE_SIZE as MAX_PAGE_SIZE2 } from "@adatechnology/scheduling-contracts";
719
1307
 
720
1308
  // src/components/WeeklyRulesEditor.tsx
721
1309
  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";
1310
+ import { useState as useState3 } from "react";
1311
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
724
1312
  var WEEKDAYS = [0, 1, 2, 3, 4, 5, 6];
725
1313
  function toDraft(rule) {
726
1314
  return { weekday: rule.weekday, startsAtLocal: rule.startsAtLocal, endsAtLocal: rule.endsAtLocal };
@@ -733,7 +1321,7 @@ function WeeklyRulesEditor({ resourceId }) {
733
1321
  const messages = resolveSchedulingMessages(locale);
734
1322
  const { data, isSuccess: isRulesLoaded, isError: isRulesLoadError } = useAvailabilityRules(resourceId);
735
1323
  const setAvailabilityRules = useSetAvailabilityRules();
736
- const [draft, setDraft] = useState2(void 0);
1324
+ const [draft, setDraft] = useState3(void 0);
737
1325
  const rules = draft ?? (data ? data.map(toDraft) : []);
738
1326
  function updateRule(index, patch) {
739
1327
  setDraft(rules.map((rule, ruleIndex) => ruleIndex === index ? { ...rule, ...patch } : rule));
@@ -755,32 +1343,32 @@ function WeeklyRulesEditor({ resourceId }) {
755
1343
  }
756
1344
  }
757
1345
  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"] })
1346
+ return /* @__PURE__ */ jsxs8("section", { className: "space-y-3", children: [
1347
+ /* @__PURE__ */ jsx10("h3", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: messages["availability.weeklyRulesTitle"] }),
1348
+ /* @__PURE__ */ jsx10("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.loadFailure"] })
761
1349
  ] });
762
1350
  }
763
1351
  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"] })
1352
+ return /* @__PURE__ */ jsxs8("section", { className: "space-y-3", children: [
1353
+ /* @__PURE__ */ jsx10("h3", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: messages["availability.weeklyRulesTitle"] }),
1354
+ /* @__PURE__ */ jsx10("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.loading"] })
767
1355
  ] });
768
1356
  }
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(
1357
+ return /* @__PURE__ */ jsxs8("section", { className: "space-y-3", children: [
1358
+ /* @__PURE__ */ jsx10("h3", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: messages["availability.weeklyRulesTitle"] }),
1359
+ setAvailabilityRules.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"] }),
1360
+ /* @__PURE__ */ jsx10("ul", { className: "space-y-2", children: rules.map((rule, index) => /* @__PURE__ */ jsxs8("li", { className: "flex items-center gap-2", children: [
1361
+ /* @__PURE__ */ jsx10(
774
1362
  "select",
775
1363
  {
776
1364
  "aria-label": messages["availability.weekdayLabel"],
777
1365
  value: rule.weekday,
778
1366
  onChange: (event) => updateRule(index, { weekday: Number(event.target.value) }),
779
1367
  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))
1368
+ children: WEEKDAYS.map((weekday) => /* @__PURE__ */ jsx10("option", { value: weekday, children: messages[`availability.weekday.${weekday}`] }, weekday))
781
1369
  }
782
1370
  ),
783
- /* @__PURE__ */ jsx4(
1371
+ /* @__PURE__ */ jsx10(
784
1372
  "input",
785
1373
  {
786
1374
  "aria-label": messages["availability.startsAtLocal"],
@@ -790,7 +1378,7 @@ function WeeklyRulesEditor({ resourceId }) {
790
1378
  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
1379
  }
792
1380
  ),
793
- /* @__PURE__ */ jsx4(
1381
+ /* @__PURE__ */ jsx10(
794
1382
  "input",
795
1383
  {
796
1384
  "aria-label": messages["availability.endsAtLocal"],
@@ -800,207 +1388,49 @@ function WeeklyRulesEditor({ resourceId }) {
800
1388
  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
1389
  }
802
1390
  ),
803
- /* @__PURE__ */ jsx4(
1391
+ /* @__PURE__ */ jsx10(
804
1392
  "button",
805
1393
  {
806
1394
  type: "button",
807
1395
  onClick: () => removeRule(index),
808
1396
  "aria-label": messages["availability.removeRule"],
809
1397
  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" })
1398
+ children: /* @__PURE__ */ jsx10(Trash2, { "aria-hidden": "true", className: "w-4 h-4" })
811
1399
  }
812
1400
  )
813
1401
  ] }, index)) }),
814
- /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
815
- /* @__PURE__ */ jsxs3(
1402
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
1403
+ /* @__PURE__ */ jsxs8(
816
1404
  "button",
817
1405
  {
818
1406
  type: "button",
819
1407
  onClick: addRule,
820
1408
  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
1409
  children: [
822
- /* @__PURE__ */ jsx4(Plus, { "aria-hidden": "true", className: "w-4 h-4" }),
1410
+ /* @__PURE__ */ jsx10(Plus, { "aria-hidden": "true", className: "w-4 h-4" }),
823
1411
  messages["availability.addRule"]
824
1412
  ]
825
1413
  }
826
1414
  ),
827
- /* @__PURE__ */ jsx4(
1415
+ /* @__PURE__ */ jsx10(
828
1416
  "button",
829
1417
  {
830
1418
  type: "button",
831
1419
  onClick: handleSave,
832
- disabled: setAvailabilityRules.isPending,
833
- 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
- children: messages["common.save"]
835
- }
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
- )
1420
+ disabled: setAvailabilityRules.isPending,
1421
+ 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",
1422
+ children: messages["common.save"]
1423
+ }
1424
+ )
1425
+ ] })
954
1426
  ] });
955
1427
  }
956
1428
 
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
1429
  // src/components/AvailabilityExceptionsEditor.tsx
1003
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1430
+ import { Plus as Plus2, Trash2 as Trash22 } from "lucide-react";
1431
+ import { useState as useState4 } from "react";
1432
+ import { AVAILABILITY_EXCEPTION_KIND } from "@adatechnology/scheduling-contracts";
1433
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1004
1434
  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
1435
  function AvailabilityExceptionsEditor({ resourceId, timezone }) {
1006
1436
  const { locale } = useSchedulingConfig();
@@ -1009,10 +1439,10 @@ function AvailabilityExceptionsEditor({ resourceId, timezone }) {
1009
1439
  const addException = useAddAvailabilityException();
1010
1440
  const removeException = useRemoveAvailabilityException();
1011
1441
  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("");
1442
+ const [from, setFrom] = useState4(emptyValue);
1443
+ const [until, setUntil] = useState4(emptyValue);
1444
+ const [kind, setKind] = useState4(AVAILABILITY_EXCEPTION_KIND.BLOCK);
1445
+ const [reason, setReason] = useState4("");
1016
1446
  async function handleAdd() {
1017
1447
  if (!from || !until) return;
1018
1448
  try {
@@ -1031,13 +1461,13 @@ function AvailabilityExceptionsEditor({ resourceId, timezone }) {
1031
1461
  } catch {
1032
1462
  }
1033
1463
  }
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: [
1464
+ return /* @__PURE__ */ jsxs9("section", { className: "space-y-3", children: [
1465
+ /* @__PURE__ */ jsx11("h3", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: messages["availability.exceptionsTitle"] }),
1466
+ (addException.isError || removeException.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"] }),
1467
+ isLoadError && /* @__PURE__ */ jsx11("p", { role: "alert", className: "text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2", children: messages["common.loadFailure"] }),
1468
+ isLoading && /* @__PURE__ */ jsx11("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.loading"] }),
1469
+ /* @__PURE__ */ jsx11("ul", { className: "space-y-2", children: (data ?? []).map((exception) => /* @__PURE__ */ jsxs9("li", { className: "flex items-center gap-2 text-sm", children: [
1470
+ /* @__PURE__ */ jsxs9("span", { className: "flex-1", children: [
1041
1471
  messages[`availability.exceptionKind.${exception.kind === "block" ? "blocked" : "extra"}`],
1042
1472
  " \u2014",
1043
1473
  " ",
@@ -1046,19 +1476,19 @@ function AvailabilityExceptionsEditor({ resourceId, timezone }) {
1046
1476
  exception.during.end.toLocaleString(locale),
1047
1477
  exception.reason ? ` (${exception.reason})` : ""
1048
1478
  ] }),
1049
- /* @__PURE__ */ jsx6(
1479
+ /* @__PURE__ */ jsx11(
1050
1480
  "button",
1051
1481
  {
1052
1482
  type: "button",
1053
1483
  onClick: () => removeException.mutate({ id: exception.id, resourceId }),
1054
1484
  "aria-label": messages["availability.removeException"],
1055
1485
  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" })
1486
+ children: /* @__PURE__ */ jsx11(Trash22, { "aria-hidden": "true", className: "w-4 h-4" })
1057
1487
  }
1058
1488
  )
1059
1489
  ] }, exception.id)) }),
1060
- /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center gap-2", children: [
1061
- /* @__PURE__ */ jsx6(
1490
+ /* @__PURE__ */ jsxs9("div", { className: "flex flex-wrap items-center gap-2", children: [
1491
+ /* @__PURE__ */ jsx11(
1062
1492
  DateTimeField,
1063
1493
  {
1064
1494
  label: messages["availability.exceptionFrom"],
@@ -1066,7 +1496,7 @@ function AvailabilityExceptionsEditor({ resourceId, timezone }) {
1066
1496
  onChange: setFrom
1067
1497
  }
1068
1498
  ),
1069
- /* @__PURE__ */ jsx6(
1499
+ /* @__PURE__ */ jsx11(
1070
1500
  DateTimeField,
1071
1501
  {
1072
1502
  label: messages["availability.exceptionUntil"],
@@ -1074,7 +1504,7 @@ function AvailabilityExceptionsEditor({ resourceId, timezone }) {
1074
1504
  onChange: setUntil
1075
1505
  }
1076
1506
  ),
1077
- /* @__PURE__ */ jsxs5(
1507
+ /* @__PURE__ */ jsxs9(
1078
1508
  "select",
1079
1509
  {
1080
1510
  "aria-label": messages["availability.exceptionKind.blocked"],
@@ -1082,12 +1512,12 @@ function AvailabilityExceptionsEditor({ resourceId, timezone }) {
1082
1512
  onChange: (event) => setKind(event.target.value),
1083
1513
  className: SELECT_CLASS,
1084
1514
  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"] })
1515
+ /* @__PURE__ */ jsx11("option", { value: AVAILABILITY_EXCEPTION_KIND.BLOCK, children: messages["availability.exceptionKind.blocked"] }),
1516
+ /* @__PURE__ */ jsx11("option", { value: AVAILABILITY_EXCEPTION_KIND.EXTRA, children: messages["availability.exceptionKind.extra"] })
1087
1517
  ]
1088
1518
  }
1089
1519
  ),
1090
- /* @__PURE__ */ jsx6(
1520
+ /* @__PURE__ */ jsx11(
1091
1521
  "input",
1092
1522
  {
1093
1523
  "aria-label": messages["availability.exceptionReason"],
@@ -1098,7 +1528,7 @@ function AvailabilityExceptionsEditor({ resourceId, timezone }) {
1098
1528
  className: SELECT_CLASS
1099
1529
  }
1100
1530
  ),
1101
- /* @__PURE__ */ jsxs5(
1531
+ /* @__PURE__ */ jsxs9(
1102
1532
  "button",
1103
1533
  {
1104
1534
  type: "button",
@@ -1106,7 +1536,7 @@ function AvailabilityExceptionsEditor({ resourceId, timezone }) {
1106
1536
  disabled: addException.isPending,
1107
1537
  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
1538
  children: [
1109
- /* @__PURE__ */ jsx6(Plus2, { "aria-hidden": "true", className: "w-4 h-4" }),
1539
+ /* @__PURE__ */ jsx11(Plus2, { "aria-hidden": "true", className: "w-4 h-4" }),
1110
1540
  messages["availability.addException"]
1111
1541
  ]
1112
1542
  }
@@ -1116,51 +1546,54 @@ function AvailabilityExceptionsEditor({ resourceId, timezone }) {
1116
1546
  }
1117
1547
 
1118
1548
  // src/components/AvailabilityEditor.tsx
1119
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1549
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1120
1550
  function AvailabilityEditor({ resourceId, timezone }) {
1121
1551
  const { locale } = useSchedulingConfig();
1122
1552
  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: [
1553
+ return /* @__PURE__ */ jsxs10("div", { className: "space-y-6", children: [
1554
+ /* @__PURE__ */ jsxs10("p", { className: "inline-flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400", children: [
1555
+ /* @__PURE__ */ jsxs10("span", { className: "font-medium", children: [
1126
1556
  messages["availability.resourceTimezone"],
1127
1557
  ":"
1128
1558
  ] }),
1129
1559
  timezone
1130
1560
  ] }),
1131
- /* @__PURE__ */ jsx7(WeeklyRulesEditor, { resourceId }),
1132
- /* @__PURE__ */ jsx7(AvailabilityExceptionsEditor, { resourceId, timezone })
1561
+ /* @__PURE__ */ jsx12(WeeklyRulesEditor, { resourceId }),
1562
+ /* @__PURE__ */ jsx12(AvailabilityExceptionsEditor, { resourceId, timezone })
1133
1563
  ] });
1134
1564
  }
1135
1565
 
1136
1566
  // src/workspace/AvailabilityArea.tsx
1137
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1567
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1138
1568
  function AvailabilityArea() {
1139
1569
  const { locale } = useSchedulingConfig();
1140
1570
  const messages = resolveSchedulingMessages(locale);
1141
1571
  const { data, isLoading, isError } = useResources({ active: true, pageSize: MAX_PAGE_SIZE2 });
1142
- const [resourceId, setResourceId] = useState4("");
1572
+ const [resourceId, setResourceId] = useState5("");
1143
1573
  const resources = data?.data ?? [];
1144
1574
  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(
1575
+ return /* @__PURE__ */ jsxs11("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col gap-4 overflow-y-auto p-4", children: [
1576
+ /* @__PURE__ */ jsx13(
1577
+ ResourceSelect,
1578
+ {
1579
+ label: messages["agenda.resourceLabel"],
1580
+ emptyOptionLabel: messages["availability.selectResourceTitle"],
1581
+ resources,
1582
+ value: resourceId,
1583
+ onChange: setResourceId
1584
+ }
1585
+ ),
1586
+ isError && /* @__PURE__ */ jsx13(ErrorBanner, { message: messages["common.loadFailure"] }),
1587
+ isLoading && /* @__PURE__ */ jsx13(ListSkeleton, { label: messages["common.loading"], rows: 3 }),
1588
+ !isLoading && !isError && !selectedResource && /* @__PURE__ */ jsx13(
1589
+ EmptyState,
1590
+ {
1591
+ icon: Clock,
1592
+ title: messages["availability.selectResourceTitle"],
1593
+ hint: messages["availability.selectResourceHint"]
1594
+ }
1595
+ ),
1596
+ selectedResource && /* @__PURE__ */ jsx13(
1164
1597
  AvailabilityEditor,
1165
1598
  {
1166
1599
  resourceId: selectedResource.id,
@@ -1172,194 +1605,24 @@ function AvailabilityArea() {
1172
1605
  }
1173
1606
 
1174
1607
  // src/workspace/BookingsArea.tsx
1608
+ import { ClipboardList } from "lucide-react";
1175
1609
  import { useState as useState8 } from "react";
1176
1610
  import { MAX_PAGE_SIZE as MAX_PAGE_SIZE3 } from "@adatechnology/scheduling-contracts";
1177
1611
 
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
1612
  // src/components/BookingsTable.tsx
1350
1613
  import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, ArrowUpDown } from "lucide-react";
1351
1614
  import { useState as useState6 } from "react";
1352
- import { BOOKING_STATUS as BOOKING_STATUS3 } from "@adatechnology/scheduling-contracts";
1615
+ import { BOOKING_STATUS as BOOKING_STATUS4 } from "@adatechnology/scheduling-contracts";
1353
1616
 
1354
1617
  // src/components/bookingsTableState.util.ts
1355
- import { BOOKING_STATUS as BOOKING_STATUS2 } from "@adatechnology/scheduling-contracts";
1618
+ import { BOOKING_STATUS as BOOKING_STATUS3 } from "@adatechnology/scheduling-contracts";
1356
1619
  var DEFAULT_BOOKINGS_TABLE_STATE = {
1357
1620
  sortDirection: "asc",
1358
1621
  statusFilters: [],
1359
1622
  page: 1
1360
1623
  };
1361
1624
  var SORT_COLUMNS = ["title", "status", "startsAt", "endsAt"];
1362
- var BOOKING_STATUSES = Object.values(BOOKING_STATUS2);
1625
+ var BOOKING_STATUSES = Object.values(BOOKING_STATUS3);
1363
1626
  function isBookingSortColumn(value) {
1364
1627
  return SORT_COLUMNS.includes(value);
1365
1628
  }
@@ -1398,8 +1661,8 @@ function filterBookingsByStatus(bookings, statusFilters) {
1398
1661
  }
1399
1662
 
1400
1663
  // src/components/BookingsTable.tsx
1401
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1402
- var ALL_STATUSES = Object.values(BOOKING_STATUS3);
1664
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1665
+ var ALL_STATUSES = Object.values(BOOKING_STATUS4);
1403
1666
  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
1667
  var CHECKBOX_CELL_CLASS = "px-3 py-2";
1405
1668
  function nextSortState(column, currentColumn, currentDirection) {
@@ -1447,53 +1710,67 @@ function BookingsTable({
1447
1710
  setSelected(next);
1448
1711
  }
1449
1712
  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" });
1713
+ if (state.sortColumn !== column) return /* @__PURE__ */ jsx14(ArrowUpDown, { "aria-hidden": "true", className: "w-3 h-3" });
1714
+ return state.sortDirection === "asc" ? /* @__PURE__ */ jsx14(ArrowUp, { "aria-hidden": "true", className: "w-3 h-3" }) : /* @__PURE__ */ jsx14(ArrowDown, { "aria-hidden": "true", className: "w-3 h-3" });
1452
1715
  }
1453
1716
  function renderHeader(column, labelKey) {
1454
1717
  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: [
1718
+ return /* @__PURE__ */ jsx14("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
1719
  messages[labelKey],
1457
1720
  renderSortIcon(column)
1458
1721
  ] }) });
1459
1722
  }
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(
1723
+ return /* @__PURE__ */ jsxs12("div", { className: "space-y-3", children: [
1724
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-3", children: [
1725
+ /* @__PURE__ */ jsxs12("fieldset", { className: "flex flex-wrap items-center gap-2", children: [
1726
+ /* @__PURE__ */ jsx14("legend", { className: "sr-only", children: messages["booking.filterByStatus"] }),
1727
+ ALL_STATUSES.map((status) => {
1728
+ const isActive = state.statusFilters.includes(status);
1729
+ return /* @__PURE__ */ jsxs12(
1730
+ "label",
1731
+ {
1732
+ 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"}`,
1733
+ children: [
1734
+ /* @__PURE__ */ jsx14(
1735
+ "input",
1736
+ {
1737
+ type: "checkbox",
1738
+ className: "sr-only",
1739
+ checked: isActive,
1740
+ onChange: () => toggleStatusFilter(status)
1741
+ }
1742
+ ),
1743
+ bookingStatusLabel(messages, status)
1744
+ ]
1745
+ },
1746
+ status
1747
+ );
1748
+ })
1749
+ ] }),
1750
+ !isBookingsTableStateDefault(state) && /* @__PURE__ */ jsx14(
1474
1751
  "button",
1475
1752
  {
1476
1753
  type: "button",
1477
1754
  onClick: () => onStateChange?.(DEFAULT_BOOKINGS_TABLE_STATE),
1478
- className: "min-h-11 px-3 text-sm font-medium text-brand-700 hover:underline",
1755
+ className: "min-h-11 px-3 text-sm font-medium text-brand-700 hover:underline dark:text-brand-300",
1479
1756
  children: messages["common.clearFilters"]
1480
1757
  }
1481
1758
  )
1482
1759
  ] }),
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(
1760
+ selected.size > 0 && bulkActions.length > 0 && /* @__PURE__ */ jsx14("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__ */ jsx14(
1484
1761
  "button",
1485
1762
  {
1486
1763
  type: "button",
1487
1764
  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",
1765
+ className: BUTTON_PRIMARY,
1489
1766
  children: action.label
1490
1767
  },
1491
1768
  action.key
1492
1769
  )) }),
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(
1770
+ /* @__PURE__ */ jsxs12("div", { className: `${SURFACE_BORDER} overflow-x-auto`, children: [
1771
+ /* @__PURE__ */ jsxs12("table", { className: "min-w-full text-sm", children: [
1772
+ /* @__PURE__ */ jsx14("thead", { className: "border-b border-gray-200 bg-gray-50 dark:border-gray-800 dark:bg-gray-800/60", children: /* @__PURE__ */ jsxs12("tr", { children: [
1773
+ /* @__PURE__ */ jsx14("th", { scope: "col", className: CHECKBOX_CELL_CLASS, children: /* @__PURE__ */ jsx14(
1497
1774
  "input",
1498
1775
  {
1499
1776
  type: "checkbox",
@@ -1507,12 +1784,12 @@ function BookingsTable({
1507
1784
  renderHeader("startsAt", "booking.column.startsAt"),
1508
1785
  renderHeader("endsAt", "booking.column.endsAt")
1509
1786
  ] }) }),
1510
- /* @__PURE__ */ jsx11("tbody", { children: visibleBookings.map((booking, index) => /* @__PURE__ */ jsxs10(
1787
+ /* @__PURE__ */ jsx14("tbody", { children: visibleBookings.map((booking, index) => /* @__PURE__ */ jsxs12(
1511
1788
  "tr",
1512
1789
  {
1513
- className: index % 2 === 1 ? "bg-gray-50 dark:bg-gray-800/50" : void 0,
1790
+ className: `${index % 2 === 1 ? ROW_STRIPE : ""} hover:bg-brand-50/50 dark:hover:bg-brand-900/10`,
1514
1791
  children: [
1515
- /* @__PURE__ */ jsx11("td", { className: CHECKBOX_CELL_CLASS, children: /* @__PURE__ */ jsx11(
1792
+ /* @__PURE__ */ jsx14("td", { className: CHECKBOX_CELL_CLASS, children: /* @__PURE__ */ jsx14(
1516
1793
  "input",
1517
1794
  {
1518
1795
  type: "checkbox",
@@ -1521,43 +1798,43 @@ function BookingsTable({
1521
1798
  onChange: () => toggleRowSelected(booking.id)
1522
1799
  }
1523
1800
  ) }),
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) })
1801
+ /* @__PURE__ */ jsx14("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsx14("button", { type: "button", onClick: () => onRowClick?.(booking), className: "min-h-11 text-left hover:underline", children: booking.title }) }),
1802
+ /* @__PURE__ */ jsx14("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsx14(BookingStatusBadge, { status: booking.status }) }),
1803
+ /* @__PURE__ */ jsx14("td", { className: "px-3 py-2 tabular-nums whitespace-nowrap", children: booking.startsAt.toLocaleString(locale) }),
1804
+ /* @__PURE__ */ jsx14("td", { className: "px-3 py-2 tabular-nums whitespace-nowrap", children: booking.endsAt.toLocaleString(locale) })
1528
1805
  ]
1529
1806
  },
1530
1807
  booking.id
1531
1808
  )) })
1532
1809
  ] }),
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"] })
1810
+ visibleBookings.length === 0 && /* @__PURE__ */ jsx14("p", { className: "px-3 py-8 text-center text-sm text-gray-500 dark:text-gray-400", children: messages["common.empty"] })
1534
1811
  ] }),
1535
- pagination && pagination.totalPages > 1 && /* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-end gap-3", children: [
1536
- /* @__PURE__ */ jsx11(
1812
+ pagination && pagination.totalPages > 1 && /* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-end gap-3", children: [
1813
+ /* @__PURE__ */ jsx14(
1537
1814
  "button",
1538
1815
  {
1539
1816
  type: "button",
1540
1817
  "aria-label": messages["common.previousPage"],
1541
1818
  disabled: state.page <= 1,
1542
1819
  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" })
1820
+ className: ICON_BUTTON,
1821
+ children: /* @__PURE__ */ jsx14(ArrowLeft, { "aria-hidden": "true", className: "w-4 h-4" })
1545
1822
  }
1546
1823
  ),
1547
- /* @__PURE__ */ jsxs10("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: [
1824
+ /* @__PURE__ */ jsxs12("span", { className: "text-sm tabular-nums text-gray-500 dark:text-gray-400", children: [
1548
1825
  state.page,
1549
1826
  " / ",
1550
1827
  pagination.totalPages
1551
1828
  ] }),
1552
- /* @__PURE__ */ jsx11(
1829
+ /* @__PURE__ */ jsx14(
1553
1830
  "button",
1554
1831
  {
1555
1832
  type: "button",
1556
1833
  "aria-label": messages["common.nextPage"],
1557
1834
  disabled: state.page >= pagination.totalPages,
1558
1835
  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" })
1836
+ className: ICON_BUTTON,
1837
+ children: /* @__PURE__ */ jsx14(ArrowRight, { "aria-hidden": "true", className: "w-4 h-4" })
1561
1838
  }
1562
1839
  )
1563
1840
  ] })
@@ -1565,7 +1842,7 @@ function BookingsTable({
1565
1842
  }
1566
1843
 
1567
1844
  // src/hooks/useBookingsTableState.hook.ts
1568
- import { useEffect as useEffect2, useState as useState7 } from "react";
1845
+ import { useEffect as useEffect3, useState as useState7 } from "react";
1569
1846
  var OWNED_PARAM_KEYS = ["sortBy", "sortDirection", "status", "page"];
1570
1847
  function readInitialState() {
1571
1848
  if (typeof window === "undefined") return DEFAULT_BOOKINGS_TABLE_STATE;
@@ -1573,7 +1850,7 @@ function readInitialState() {
1573
1850
  }
1574
1851
  function useBookingsTableState() {
1575
1852
  const [state, setState] = useState7(readInitialState);
1576
- useEffect2(() => {
1853
+ useEffect3(() => {
1577
1854
  if (typeof window === "undefined") return;
1578
1855
  const params = new URLSearchParams(window.location.search);
1579
1856
  for (const key of OWNED_PARAM_KEYS) params.delete(key);
@@ -1586,7 +1863,7 @@ function useBookingsTableState() {
1586
1863
  }
1587
1864
 
1588
1865
  // src/workspace/BookingsArea.tsx
1589
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1866
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
1590
1867
  var PAGE_SIZE = 20;
1591
1868
  function BookingsArea() {
1592
1869
  const { locale } = useSchedulingConfig();
@@ -1610,21 +1887,32 @@ function BookingsArea() {
1610
1887
  function bulkConfirm(ids) {
1611
1888
  for (const id of ids) confirmBooking.mutate(id);
1612
1889
  }
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(
1890
+ return /* @__PURE__ */ jsxs13("div", { className: "flex flex-1 min-h-0 min-w-0", children: [
1891
+ /* @__PURE__ */ jsxs13("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col gap-4 overflow-y-auto p-4", children: [
1892
+ isError && /* @__PURE__ */ jsx15(ErrorBanner, { message: messages["common.loadFailure"] }),
1893
+ confirmBooking.isError && /* @__PURE__ */ jsx15(ErrorBanner, { message: messages["common.actionFailure"] }),
1894
+ isLoading && /* @__PURE__ */ jsx15(ListSkeleton, { label: messages["common.loading"], rows: 8 }),
1895
+ !isLoading && !isError && (data?.data.length ?? 0) === 0 && tableState.statusFilters.length === 0 && /* @__PURE__ */ jsx15(
1896
+ EmptyState,
1897
+ {
1898
+ icon: ClipboardList,
1899
+ title: messages["booking.emptyTitle"],
1900
+ hint: messages["booking.emptyHint"]
1901
+ }
1902
+ ),
1903
+ !isLoading && ((data?.data.length ?? 0) > 0 || tableState.statusFilters.length > 0) && /* @__PURE__ */ jsx15(
1904
+ BookingsTable,
1905
+ {
1906
+ bookings: data?.data ?? [],
1907
+ state: tableState,
1908
+ onStateChange: setTableState,
1909
+ pagination: data ? { totalPages: data.totalPages } : void 0,
1910
+ onRowClick: (booking) => setSelectedBookingId(booking.id),
1911
+ bulkActions: [{ key: "confirm", label: messages["booking.confirm"], onRun: bulkConfirm }]
1912
+ }
1913
+ )
1914
+ ] }),
1915
+ selectedBooking && /* @__PURE__ */ jsx15(
1628
1916
  BookingDrawer,
1629
1917
  {
1630
1918
  booking: selectedBooking,
@@ -1637,17 +1925,17 @@ function BookingsArea() {
1637
1925
  }
1638
1926
 
1639
1927
  // src/workspace/ResourcesArea.tsx
1640
- import { Plus as Plus3, Trash2 as Trash23 } from "lucide-react";
1928
+ import { Plus as Plus3, Trash2 as Trash23, Users } from "lucide-react";
1641
1929
  import { useState as useState10 } from "react";
1642
1930
  import { MAX_PAGE_SIZE as MAX_PAGE_SIZE4 } from "@adatechnology/scheduling-contracts";
1643
1931
 
1644
1932
  // src/components/ResourceForm.tsx
1645
1933
  import { useId as useId2, useState as useState9 } from "react";
1646
1934
  import { RESOURCE_KIND } from "@adatechnology/scheduling-contracts";
1647
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1935
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
1648
1936
  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
1937
  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";
1938
+ 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";
1651
1939
  function ResourceForm({ initialValues, onSubmit }) {
1652
1940
  const { locale } = useSchedulingConfig();
1653
1941
  const messages = resolveSchedulingMessages(locale);
@@ -1669,10 +1957,10 @@ function ResourceForm({ initialValues, onSubmit }) {
1669
1957
  setSubmitting(false);
1670
1958
  }
1671
1959
  }
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(
1960
+ return /* @__PURE__ */ jsxs14("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
1961
+ /* @__PURE__ */ jsxs14("div", { children: [
1962
+ /* @__PURE__ */ jsx16("label", { htmlFor: nameId, className: LABEL_CLASS, children: messages["resource.name"] }),
1963
+ /* @__PURE__ */ jsx16(
1676
1964
  "input",
1677
1965
  {
1678
1966
  id: nameId,
@@ -1684,9 +1972,9 @@ function ResourceForm({ initialValues, onSubmit }) {
1684
1972
  }
1685
1973
  )
1686
1974
  ] }),
1687
- /* @__PURE__ */ jsxs12("div", { children: [
1688
- /* @__PURE__ */ jsx13("label", { htmlFor: kindId, className: LABEL_CLASS, children: messages["resource.kind"] }),
1689
- /* @__PURE__ */ jsxs12(
1975
+ /* @__PURE__ */ jsxs14("div", { children: [
1976
+ /* @__PURE__ */ jsx16("label", { htmlFor: kindId, className: LABEL_CLASS, children: messages["resource.kind"] }),
1977
+ /* @__PURE__ */ jsxs14(
1690
1978
  "select",
1691
1979
  {
1692
1980
  id: kindId,
@@ -1694,16 +1982,16 @@ function ResourceForm({ initialValues, onSubmit }) {
1694
1982
  onChange: (event) => setKind(event.target.value),
1695
1983
  className: INPUT_CLASS2,
1696
1984
  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"] })
1985
+ /* @__PURE__ */ jsx16("option", { value: RESOURCE_KIND.PERSON, children: messages["resource.kind.person"] }),
1986
+ /* @__PURE__ */ jsx16("option", { value: RESOURCE_KIND.ROOM, children: messages["resource.kind.room"] }),
1987
+ /* @__PURE__ */ jsx16("option", { value: RESOURCE_KIND.EQUIPMENT, children: messages["resource.kind.equipment"] })
1700
1988
  ]
1701
1989
  }
1702
1990
  )
1703
1991
  ] }),
1704
- /* @__PURE__ */ jsxs12("div", { children: [
1705
- /* @__PURE__ */ jsx13("label", { htmlFor: timezoneId, className: LABEL_CLASS, children: messages["resource.timezone"] }),
1706
- /* @__PURE__ */ jsx13(
1992
+ /* @__PURE__ */ jsxs14("div", { children: [
1993
+ /* @__PURE__ */ jsx16("label", { htmlFor: timezoneId, className: LABEL_CLASS, children: messages["resource.timezone"] }),
1994
+ /* @__PURE__ */ jsx16(
1707
1995
  "input",
1708
1996
  {
1709
1997
  id: timezoneId,
@@ -1716,43 +2004,40 @@ function ResourceForm({ initialValues, onSubmit }) {
1716
2004
  }
1717
2005
  )
1718
2006
  ] }),
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) }),
2007
+ initialValues && /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300 min-h-11", children: [
2008
+ /* @__PURE__ */ jsx16("input", { type: "checkbox", checked: active, onChange: (event) => setActive(event.target.checked) }),
1721
2009
  messages["resource.active"]
1722
2010
  ] }),
1723
- /* @__PURE__ */ jsx13("button", { type: "submit", disabled: submitting, className: BUTTON_PRIMARY, children: messages["common.save"] })
2011
+ /* @__PURE__ */ jsx16("button", { type: "submit", disabled: submitting, className: BUTTON_PRIMARY2, children: messages["common.save"] })
1724
2012
  ] });
1725
2013
  }
1726
2014
 
1727
2015
  // src/components/ResourceList.tsx
1728
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
2016
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
1729
2017
  function ResourceList({ resources, onSelect }) {
1730
2018
  const { locale } = useSchedulingConfig();
1731
2019
  const messages = resolveSchedulingMessages(locale);
1732
2020
  if (resources.length === 0) {
1733
- return /* @__PURE__ */ jsx14("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.empty"] });
2021
+ return /* @__PURE__ */ jsx17("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.empty"] });
1734
2022
  }
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(
2023
+ return /* @__PURE__ */ jsx17("ul", { className: `${SURFACE_BORDER} divide-y divide-gray-200 overflow-hidden dark:divide-gray-800`, children: resources.map((resource, index) => /* @__PURE__ */ jsx17("li", { className: index % 2 === 1 ? ROW_STRIPE : void 0, children: /* @__PURE__ */ jsxs15(
1736
2024
  "button",
1737
2025
  {
1738
2026
  type: "button",
1739
2027
  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",
2028
+ 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
2029
  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"] })
2030
+ /* @__PURE__ */ jsx17("span", { className: "flex-1 font-medium text-gray-900 dark:text-gray-100", children: resource.name }),
2031
+ /* @__PURE__ */ jsx17("span", { className: "text-gray-500 dark:text-gray-400", children: messages[`resource.kind.${resource.kind}`] }),
2032
+ /* @__PURE__ */ jsx17("span", { className: "text-gray-500 dark:text-gray-400", children: resource.timezone }),
2033
+ !resource.active && /* @__PURE__ */ jsx17("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
2034
  ]
1747
2035
  }
1748
2036
  ) }, resource.id)) });
1749
2037
  }
1750
2038
 
1751
2039
  // 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`;
2040
+ import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
1756
2041
  function ResourcesArea() {
1757
2042
  const { locale } = useSchedulingConfig();
1758
2043
  const messages = resolveSchedulingMessages(locale);
@@ -1763,6 +2048,7 @@ function ResourcesArea() {
1763
2048
  const [draft, setDraft] = useState10(void 0);
1764
2049
  const isDraftOpen = draft !== void 0;
1765
2050
  const isEditing = Boolean(draft);
2051
+ const resources = data?.data ?? [];
1766
2052
  async function handleSubmit(input) {
1767
2053
  try {
1768
2054
  if (draft) {
@@ -1774,21 +2060,36 @@ function ResourcesArea() {
1774
2060
  } catch {
1775
2061
  }
1776
2062
  }
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" }),
2063
+ function renderCreateButton() {
2064
+ return /* @__PURE__ */ jsxs16("button", { type: "button", onClick: () => setDraft(null), className: BUTTON_PRIMARY, children: [
2065
+ /* @__PURE__ */ jsx18(Plus3, { "aria-hidden": "true", className: "h-4 w-4" }),
1780
2066
  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(
2067
+ ] });
2068
+ }
2069
+ return /* @__PURE__ */ jsxs16("div", { className: "flex flex-1 min-h-0 min-w-0", children: [
2070
+ /* @__PURE__ */ jsxs16("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col gap-4 overflow-y-auto p-4", children: [
2071
+ /* @__PURE__ */ jsx18("div", { className: "flex justify-end", children: renderCreateButton() }),
2072
+ isError && /* @__PURE__ */ jsx18(ErrorBanner, { message: messages["common.loadFailure"] }),
2073
+ (createResource.isError || updateResource.isError || deleteResource.isError) && /* @__PURE__ */ jsx18(ErrorBanner, { message: messages["common.actionFailure"] }),
2074
+ isLoading && /* @__PURE__ */ jsx18(ListSkeleton, { label: messages["common.loading"] }),
2075
+ !isLoading && !isError && resources.length === 0 && /* @__PURE__ */ jsx18(
2076
+ EmptyState,
2077
+ {
2078
+ icon: Users,
2079
+ title: messages["resource.emptyTitle"],
2080
+ hint: messages["resource.emptyHint"],
2081
+ action: renderCreateButton()
2082
+ }
2083
+ ),
2084
+ !isLoading && resources.length > 0 && /* @__PURE__ */ jsx18(ResourceList, { resources, onSelect: setDraft })
2085
+ ] }),
2086
+ isDraftOpen && /* @__PURE__ */ jsx18(
1786
2087
  SidePanel,
1787
2088
  {
1788
2089
  title: isEditing ? messages["resource.editTitle"] : messages["resource.createTitle"],
1789
2090
  closeLabel: messages["common.close"],
1790
2091
  onClose: () => setDraft(void 0),
1791
- headerActions: isEditing ? /* @__PURE__ */ jsxs14(
2092
+ headerActions: isEditing ? /* @__PURE__ */ jsxs16(
1792
2093
  "button",
1793
2094
  {
1794
2095
  type: "button",
@@ -1798,12 +2099,12 @@ function ResourcesArea() {
1798
2099
  },
1799
2100
  className: BUTTON_DANGER,
1800
2101
  children: [
1801
- /* @__PURE__ */ jsx15(Trash23, { "aria-hidden": "true", className: "w-4 h-4" }),
2102
+ /* @__PURE__ */ jsx18(Trash23, { "aria-hidden": "true", className: "h-4 w-4" }),
1802
2103
  messages["common.remove"]
1803
2104
  ]
1804
2105
  }
1805
2106
  ) : void 0,
1806
- children: /* @__PURE__ */ jsx15(
2107
+ children: /* @__PURE__ */ jsx18(
1807
2108
  ResourceForm,
1808
2109
  {
1809
2110
  ...draft ? { initialValues: draft } : {},
@@ -1817,13 +2118,13 @@ function ResourcesArea() {
1817
2118
  }
1818
2119
 
1819
2120
  // src/workspace/ServicesArea.tsx
1820
- import { Plus as Plus4, Trash2 as Trash24 } from "lucide-react";
2121
+ import { Plus as Plus4, Trash2 as Trash24, Wrench } from "lucide-react";
1821
2122
  import { useState as useState12 } from "react";
1822
2123
  import { MAX_PAGE_SIZE as MAX_PAGE_SIZE5 } from "@adatechnology/scheduling-contracts";
1823
2124
 
1824
2125
  // src/components/ServiceForm.tsx
1825
2126
  import { useState as useState11 } from "react";
1826
- import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
2127
+ import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
1827
2128
  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
2129
  var LABEL_CLASS2 = "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1";
1829
2130
  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";
@@ -1845,10 +2146,10 @@ function ServiceForm({ initialValues, onSubmit }) {
1845
2146
  setSubmitting(false);
1846
2147
  }
1847
2148
  }
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(
2149
+ return /* @__PURE__ */ jsxs17("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
2150
+ /* @__PURE__ */ jsxs17("div", { children: [
2151
+ /* @__PURE__ */ jsx19("label", { htmlFor: "scheduling-service-name", className: LABEL_CLASS2, children: messages["service.name"] }),
2152
+ /* @__PURE__ */ jsx19(
1852
2153
  "input",
1853
2154
  {
1854
2155
  id: "scheduling-service-name",
@@ -1860,9 +2161,9 @@ function ServiceForm({ initialValues, onSubmit }) {
1860
2161
  }
1861
2162
  )
1862
2163
  ] }),
1863
- /* @__PURE__ */ jsxs15("div", { children: [
1864
- /* @__PURE__ */ jsx16("label", { htmlFor: "scheduling-service-duration", className: LABEL_CLASS2, children: messages["service.durationMinutes"] }),
1865
- /* @__PURE__ */ jsx16(
2164
+ /* @__PURE__ */ jsxs17("div", { children: [
2165
+ /* @__PURE__ */ jsx19("label", { htmlFor: "scheduling-service-duration", className: LABEL_CLASS2, children: messages["service.durationMinutes"] }),
2166
+ /* @__PURE__ */ jsx19(
1866
2167
  "input",
1867
2168
  {
1868
2169
  id: "scheduling-service-duration",
@@ -1875,10 +2176,10 @@ function ServiceForm({ initialValues, onSubmit }) {
1875
2176
  }
1876
2177
  )
1877
2178
  ] }),
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(
2179
+ /* @__PURE__ */ jsxs17("div", { className: "grid grid-cols-2 gap-3", children: [
2180
+ /* @__PURE__ */ jsxs17("div", { children: [
2181
+ /* @__PURE__ */ jsx19("label", { htmlFor: "scheduling-service-buffer-before", className: LABEL_CLASS2, children: messages["service.bufferBeforeMinutes"] }),
2182
+ /* @__PURE__ */ jsx19(
1882
2183
  "input",
1883
2184
  {
1884
2185
  id: "scheduling-service-buffer-before",
@@ -1890,9 +2191,9 @@ function ServiceForm({ initialValues, onSubmit }) {
1890
2191
  }
1891
2192
  )
1892
2193
  ] }),
1893
- /* @__PURE__ */ jsxs15("div", { children: [
1894
- /* @__PURE__ */ jsx16("label", { htmlFor: "scheduling-service-buffer-after", className: LABEL_CLASS2, children: messages["service.bufferAfterMinutes"] }),
1895
- /* @__PURE__ */ jsx16(
2194
+ /* @__PURE__ */ jsxs17("div", { children: [
2195
+ /* @__PURE__ */ jsx19("label", { htmlFor: "scheduling-service-buffer-after", className: LABEL_CLASS2, children: messages["service.bufferAfterMinutes"] }),
2196
+ /* @__PURE__ */ jsx19(
1896
2197
  "input",
1897
2198
  {
1898
2199
  id: "scheduling-service-buffer-after",
@@ -1905,45 +2206,42 @@ function ServiceForm({ initialValues, onSubmit }) {
1905
2206
  )
1906
2207
  ] })
1907
2208
  ] }),
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) }),
2209
+ initialValues && /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300 min-h-11", children: [
2210
+ /* @__PURE__ */ jsx19("input", { type: "checkbox", checked: active, onChange: (event) => setActive(event.target.checked) }),
1910
2211
  messages["service.active"]
1911
2212
  ] }),
1912
- /* @__PURE__ */ jsx16("button", { type: "submit", disabled: submitting, className: BUTTON_PRIMARY3, children: messages["common.save"] })
2213
+ /* @__PURE__ */ jsx19("button", { type: "submit", disabled: submitting, className: BUTTON_PRIMARY3, children: messages["common.save"] })
1913
2214
  ] });
1914
2215
  }
1915
2216
 
1916
2217
  // src/components/ServiceList.tsx
1917
- import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
2218
+ import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
1918
2219
  function ServiceList({ services, onSelect }) {
1919
2220
  const { locale } = useSchedulingConfig();
1920
2221
  const messages = resolveSchedulingMessages(locale);
1921
2222
  if (services.length === 0) {
1922
- return /* @__PURE__ */ jsx17("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.empty"] });
2223
+ return /* @__PURE__ */ jsx20("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: messages["common.empty"] });
1923
2224
  }
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(
2225
+ return /* @__PURE__ */ jsx20("ul", { className: `${SURFACE_BORDER} divide-y divide-gray-200 overflow-hidden dark:divide-gray-800`, children: services.map((service, index) => /* @__PURE__ */ jsx20("li", { className: index % 2 === 1 ? ROW_STRIPE : void 0, children: /* @__PURE__ */ jsxs18(
1925
2226
  "button",
1926
2227
  {
1927
2228
  type: "button",
1928
2229
  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",
2230
+ 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
2231
  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: [
2232
+ /* @__PURE__ */ jsx20("span", { className: "flex-1 font-medium text-gray-900 dark:text-gray-100", children: service.name }),
2233
+ /* @__PURE__ */ jsxs18("span", { className: "text-gray-500 dark:text-gray-400", children: [
1933
2234
  service.durationMinutes,
1934
2235
  " min"
1935
2236
  ] }),
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"] })
2237
+ !service.active && /* @__PURE__ */ jsx20("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
2238
  ]
1938
2239
  }
1939
2240
  ) }, service.id)) });
1940
2241
  }
1941
2242
 
1942
2243
  // 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`;
2244
+ import { jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
1947
2245
  function ServicesArea() {
1948
2246
  const { locale } = useSchedulingConfig();
1949
2247
  const messages = resolveSchedulingMessages(locale);
@@ -1954,6 +2252,7 @@ function ServicesArea() {
1954
2252
  const [draft, setDraft] = useState12(void 0);
1955
2253
  const isDraftOpen = draft !== void 0;
1956
2254
  const isEditing = Boolean(draft);
2255
+ const services = data?.data ?? [];
1957
2256
  async function handleSubmit(input) {
1958
2257
  try {
1959
2258
  if (draft) {
@@ -1965,21 +2264,36 @@ function ServicesArea() {
1965
2264
  } catch {
1966
2265
  }
1967
2266
  }
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" }),
2267
+ function renderCreateButton() {
2268
+ return /* @__PURE__ */ jsxs19("button", { type: "button", onClick: () => setDraft(null), className: BUTTON_PRIMARY, children: [
2269
+ /* @__PURE__ */ jsx21(Plus4, { "aria-hidden": "true", className: "h-4 w-4" }),
1971
2270
  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(
2271
+ ] });
2272
+ }
2273
+ return /* @__PURE__ */ jsxs19("div", { className: "flex flex-1 min-h-0 min-w-0", children: [
2274
+ /* @__PURE__ */ jsxs19("div", { className: "flex flex-1 min-h-0 min-w-0 flex-col gap-4 overflow-y-auto p-4", children: [
2275
+ /* @__PURE__ */ jsx21("div", { className: "flex justify-end", children: renderCreateButton() }),
2276
+ isError && /* @__PURE__ */ jsx21(ErrorBanner, { message: messages["common.loadFailure"] }),
2277
+ (createService.isError || updateService.isError || deleteService.isError) && /* @__PURE__ */ jsx21(ErrorBanner, { message: messages["common.actionFailure"] }),
2278
+ isLoading && /* @__PURE__ */ jsx21(ListSkeleton, { label: messages["common.loading"] }),
2279
+ !isLoading && !isError && services.length === 0 && /* @__PURE__ */ jsx21(
2280
+ EmptyState,
2281
+ {
2282
+ icon: Wrench,
2283
+ title: messages["service.emptyTitle"],
2284
+ hint: messages["service.emptyHint"],
2285
+ action: renderCreateButton()
2286
+ }
2287
+ ),
2288
+ !isLoading && services.length > 0 && /* @__PURE__ */ jsx21(ServiceList, { services, onSelect: setDraft })
2289
+ ] }),
2290
+ isDraftOpen && /* @__PURE__ */ jsx21(
1977
2291
  SidePanel,
1978
2292
  {
1979
2293
  title: isEditing ? messages["service.editTitle"] : messages["service.createTitle"],
1980
2294
  closeLabel: messages["common.close"],
1981
2295
  onClose: () => setDraft(void 0),
1982
- headerActions: isEditing ? /* @__PURE__ */ jsxs17(
2296
+ headerActions: isEditing ? /* @__PURE__ */ jsxs19(
1983
2297
  "button",
1984
2298
  {
1985
2299
  type: "button",
@@ -1987,14 +2301,14 @@ function ServicesArea() {
1987
2301
  if (draft) void deleteService.mutateAsync(draft.id).then(() => setDraft(void 0)).catch(() => {
1988
2302
  });
1989
2303
  },
1990
- className: BUTTON_DANGER2,
2304
+ className: BUTTON_DANGER,
1991
2305
  children: [
1992
- /* @__PURE__ */ jsx18(Trash24, { "aria-hidden": "true", className: "w-4 h-4" }),
2306
+ /* @__PURE__ */ jsx21(Trash24, { "aria-hidden": "true", className: "h-4 w-4" }),
1993
2307
  messages["common.remove"]
1994
2308
  ]
1995
2309
  }
1996
2310
  ) : void 0,
1997
- children: /* @__PURE__ */ jsx18(
2311
+ children: /* @__PURE__ */ jsx21(
1998
2312
  ServiceForm,
1999
2313
  {
2000
2314
  ...draft ? { initialValues: draft } : {},
@@ -2008,7 +2322,7 @@ function ServicesArea() {
2008
2322
  }
2009
2323
 
2010
2324
  // src/workspace/WorkspaceAreaNav.tsx
2011
- import { CalendarDays, Clock, ClipboardList, Users, Wrench } from "lucide-react";
2325
+ import { CalendarDays as CalendarDays2, Clock as Clock2, ClipboardList as ClipboardList2, Users as Users2, Wrench as Wrench2 } from "lucide-react";
2012
2326
 
2013
2327
  // src/workspace/workspace.constant.ts
2014
2328
  var SCHEDULING_WORKSPACE_AREA = {
@@ -2024,16 +2338,16 @@ function isSchedulingWorkspaceArea(value) {
2024
2338
  }
2025
2339
 
2026
2340
  // 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";
2341
+ import { jsx as jsx22, jsxs as jsxs20 } from "react/jsx-runtime";
2342
+ 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";
2343
+ var ITEM_ACTIVE = "border-brand-600 text-brand-700 dark:text-brand-300";
2030
2344
  var ITEM_IDLE = "border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100";
2031
2345
  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
2346
+ [SCHEDULING_WORKSPACE_AREA.AGENDA]: CalendarDays2,
2347
+ [SCHEDULING_WORKSPACE_AREA.BOOKINGS]: ClipboardList2,
2348
+ [SCHEDULING_WORKSPACE_AREA.RESOURCES]: Users2,
2349
+ [SCHEDULING_WORKSPACE_AREA.SERVICES]: Wrench2,
2350
+ [SCHEDULING_WORKSPACE_AREA.AVAILABILITY]: Clock2
2037
2351
  };
2038
2352
  function WorkspaceAreaNav({ area, labels, onSelect }) {
2039
2353
  const items = [
@@ -2043,9 +2357,9 @@ function WorkspaceAreaNav({ area, labels, onSelect }) {
2043
2357
  [SCHEDULING_WORKSPACE_AREA.SERVICES, labels.servicesTab],
2044
2358
  [SCHEDULING_WORKSPACE_AREA.AVAILABILITY, labels.availabilityTab]
2045
2359
  ];
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]) => {
2360
+ return /* @__PURE__ */ jsx22("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
2361
  const Icon = AREA_ICON[value];
2048
- return /* @__PURE__ */ jsxs18(
2362
+ return /* @__PURE__ */ jsxs20(
2049
2363
  "button",
2050
2364
  {
2051
2365
  type: "button",
@@ -2053,7 +2367,7 @@ function WorkspaceAreaNav({ area, labels, onSelect }) {
2053
2367
  "aria-current": value === area ? "page" : void 0,
2054
2368
  className: `${ITEM_BASE} ${value === area ? ITEM_ACTIVE : ITEM_IDLE} whitespace-nowrap`,
2055
2369
  children: [
2056
- /* @__PURE__ */ jsx19(Icon, { "aria-hidden": "true", className: "w-4 h-4 shrink-0" }),
2370
+ /* @__PURE__ */ jsx22(Icon, { "aria-hidden": "true", className: "w-4 h-4 shrink-0" }),
2057
2371
  label
2058
2372
  ]
2059
2373
  },
@@ -2063,7 +2377,7 @@ function WorkspaceAreaNav({ area, labels, onSelect }) {
2063
2377
  }
2064
2378
 
2065
2379
  // src/workspace/SchedulingWorkspace.tsx
2066
- import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
2380
+ import { jsx as jsx23, jsxs as jsxs21 } from "react/jsx-runtime";
2067
2381
  var AREA_COMPONENT = {
2068
2382
  [SCHEDULING_WORKSPACE_AREA.AGENDA]: AgendaArea,
2069
2383
  [SCHEDULING_WORKSPACE_AREA.BOOKINGS]: BookingsArea,
@@ -2085,13 +2399,13 @@ function SchedulingWorkspace({
2085
2399
  setInternalArea(next);
2086
2400
  onAreaChange?.(next);
2087
2401
  }
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 }),
2402
+ return /* @__PURE__ */ jsxs21("div", { className: "flex h-full flex-col bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100", children: [
2403
+ /* @__PURE__ */ jsxs21("header", { className: "flex shrink-0 flex-wrap items-center gap-3 px-4 pb-2 pt-4", children: [
2404
+ /* @__PURE__ */ jsx23("h1", { className: "mr-auto text-xl font-semibold tracking-tight text-gray-900 dark:text-gray-100", children: labels.title }),
2091
2405
  renderHeaderActions?.()
2092
2406
  ] }),
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, {}) })
2407
+ /* @__PURE__ */ jsx23(WorkspaceAreaNav, { area, labels, onSelect: handleSelectArea }),
2408
+ /* @__PURE__ */ jsx23("div", { className: "relative flex flex-1 min-h-0 overflow-hidden", children: /* @__PURE__ */ jsx23(AreaComponent, {}) })
2095
2409
  ] });
2096
2410
  }
2097
2411
  export {