@spacesync/client 0.1.6 → 0.1.9

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.
@@ -1,5 +1,4 @@
1
- import { AnalogClockPicker } from './chunk-34332W2P.js';
2
- import { formatTimeForDisplay } from './chunk-H3CNZCYU.js';
1
+ import { CheckboxField, hourlyEndTimes, addMinutesToHm, formatTimeForDisplay, snapScheduleEndTime, TimeField } from './chunk-F7MU5T46.js';
3
2
  import './chunk-RV5PMZYZ.js';
4
3
  import './chunk-L65F4EMK.js';
5
4
  import './chunk-PBNBLCC2.js';
@@ -16,26 +15,44 @@ import { Info, ChevronLeft, ChevronRight } from 'lucide-react';
16
15
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
17
16
 
18
17
  // src/lib/booking-schedule.ts
19
- var PRICING_TODAY = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
20
- function computeMinMonth() {
21
- const d = /* @__PURE__ */ new Date();
22
- if (d.getDate() === 1) {
23
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
24
- }
25
- const n = new Date(d.getFullYear(), d.getMonth() + 1, 1);
26
- return `${n.getFullYear()}-${String(n.getMonth() + 1).padStart(2, "0")}`;
18
+ function localDateYmd(d) {
19
+ const y = d.getFullYear();
20
+ const m = String(d.getMonth() + 1).padStart(2, "0");
21
+ const day = String(d.getDate()).padStart(2, "0");
22
+ return `${y}-${m}-${day}`;
23
+ }
24
+ function todayLocalYmd(now = /* @__PURE__ */ new Date()) {
25
+ return localDateYmd(now);
27
26
  }
28
- function computeMinWeekStart() {
29
- const d = /* @__PURE__ */ new Date();
27
+ var PRICING_TODAY = todayLocalYmd();
28
+ function currentMonthYm(now = /* @__PURE__ */ new Date()) {
29
+ return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
30
+ }
31
+ function currentWeekStart(now = /* @__PURE__ */ new Date()) {
32
+ const d = new Date(now.getFullYear(), now.getMonth(), now.getDate());
30
33
  const day = d.getDay();
31
- if (day === 1) return d.toISOString().split("T")[0];
34
+ const offset = day === 0 ? -6 : 1 - day;
35
+ d.setDate(d.getDate() + offset);
36
+ return localDateYmd(d);
37
+ }
38
+ function computeMinMonth(now = /* @__PURE__ */ new Date()) {
39
+ if (now.getDate() === 1) {
40
+ return currentMonthYm(now);
41
+ }
42
+ const n = new Date(now.getFullYear(), now.getMonth() + 1, 1);
43
+ return currentMonthYm(n);
44
+ }
45
+ function computeMinWeekStart(now = /* @__PURE__ */ new Date()) {
46
+ const day = now.getDay();
47
+ if (day === 1) return todayLocalYmd(now);
48
+ const d = new Date(now.getFullYear(), now.getMonth(), now.getDate());
32
49
  const skip = day === 0 ? 1 : 8 - day;
33
50
  d.setDate(d.getDate() + skip);
34
- return d.toISOString().split("T")[0];
51
+ return localDateYmd(d);
35
52
  }
36
- var MIN_MONTH = computeMinMonth() || `${(/* @__PURE__ */ new Date()).getFullYear()}-${String((/* @__PURE__ */ new Date()).getMonth() + 2).padStart(2, "0")}`;
37
- var MIN_WEEK_START = computeMinWeekStart() || PRICING_TODAY;
38
- var IS_PARTIAL_MONTH = (/* @__PURE__ */ new Date()).getDate() !== 1;
53
+ computeMinMonth();
54
+ var MIN_WEEK_START = computeMinWeekStart();
55
+ (/* @__PURE__ */ new Date()).getDate() !== 1;
39
56
  function monthLabel(ym) {
40
57
  const [y, m] = ym.split("-").map(Number);
41
58
  return new Date(y, m - 1, 1).toLocaleDateString("en-US", { month: "long", year: "numeric" });
@@ -47,23 +64,27 @@ function monthCount(from, to) {
47
64
  return (ty - fy) * 12 + (tm - fm) + 1;
48
65
  }
49
66
  function weekRangeLabel(weekFrom, weekCount) {
50
- if (!weekFrom || isNaN(new Date(weekFrom).getTime())) return "\u2014";
51
- const end = new Date(weekFrom);
67
+ if (!weekFrom || isNaN((/* @__PURE__ */ new Date(weekFrom + "T00:00:00")).getTime())) return "\u2014";
68
+ const end = parseDateOnly(weekFrom);
52
69
  end.setDate(end.getDate() + weekCount * 7 - 1);
53
- const s = new Date(weekFrom).toLocaleDateString("en-US", { month: "short", day: "numeric" });
70
+ const s = parseDateOnly(weekFrom).toLocaleDateString("en-US", { month: "short", day: "numeric" });
54
71
  const e = end.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
55
72
  return `${s} \u2013 ${e}`;
56
73
  }
74
+ function weekCountBetween(weekFrom, weekToMon) {
75
+ if (!weekFrom || !weekToMon) return 0;
76
+ if (weekToMon < weekFrom) return 1;
77
+ return Math.floor(diffDays(weekFrom, weekToMon) / 7) + 1;
78
+ }
79
+ function addMonthsYm(ym, delta) {
80
+ const [y, m] = ym.split("-").map(Number);
81
+ const d = new Date(y, (m ?? 1) - 1 + delta, 1);
82
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
83
+ }
57
84
  function parseDateOnly(date) {
58
85
  const [y, m, d] = date.split("-").map(Number);
59
86
  return new Date(y, m - 1, d);
60
87
  }
61
- function formatDateOnly(d) {
62
- const y = d.getFullYear();
63
- const m = String(d.getMonth() + 1).padStart(2, "0");
64
- const day = String(d.getDate()).padStart(2, "0");
65
- return `${y}-${m}-${day}`;
66
- }
67
88
  function diffDays(a, b) {
68
89
  const da = parseDateOnly(a);
69
90
  const db = parseDateOnly(b);
@@ -72,7 +93,7 @@ function diffDays(a, b) {
72
93
  function addDays(date, days) {
73
94
  const d = parseDateOnly(date);
74
95
  d.setDate(d.getDate() + days);
75
- return formatDateOnly(d);
96
+ return localDateYmd(d);
76
97
  }
77
98
  var MONTH_NAMES = [
78
99
  "January",
@@ -89,7 +110,22 @@ var MONTH_NAMES = [
89
110
  "December"
90
111
  ];
91
112
  function dayStr(d) {
92
- return d.toISOString().split("T")[0];
113
+ return localDateYmd(d);
114
+ }
115
+ var calendarBlockClass = "flex flex-col overflow-hidden rounded-xl border border-border/60";
116
+ var calendarNavRowClass = "flex items-center justify-between border-b border-border/40 px-2 py-1.5";
117
+ var calendarWeekdayRowClass = "grid grid-cols-7 border-b border-border/40";
118
+ var calendarWeekdayCellClass = "flex h-9 items-center justify-center border-r border-border/40 text-[10px] font-medium text-muted-foreground last:border-r-0";
119
+ function cellGridLines(index, total, cols) {
120
+ return cn(
121
+ "border-border/40",
122
+ index % cols !== cols - 1 && "border-r",
123
+ index < total - cols && "border-b"
124
+ );
125
+ }
126
+ function weekEndMonday(weekFrom, weekCount) {
127
+ if (!weekFrom || weekCount < 1) return weekFrom;
128
+ return addDays(weekFrom, (weekCount - 1) * 7);
93
129
  }
94
130
  function getCalendarGrid(year, month) {
95
131
  const firstDay = new Date(year, month, 1);
@@ -145,6 +181,8 @@ function ScheduleStepper({
145
181
  }
146
182
  function WeekCalendarPicker({
147
183
  weekFrom,
184
+ weekCount,
185
+ rangeMode,
148
186
  onChange,
149
187
  flat = false
150
188
  }) {
@@ -152,10 +190,22 @@ function WeekCalendarPicker({
152
190
  const [calYear, setCalYear] = useState(initDate.getFullYear());
153
191
  const [calMonth, setCalMonth] = useState(initDate.getMonth());
154
192
  const [hoverMon, setHoverMon] = useState(null);
193
+ const [phase, setPhase] = useState("from");
194
+ useEffect(() => {
195
+ setPhase("from");
196
+ }, [rangeMode]);
155
197
  const days = getCalendarGrid(calYear, calMonth);
156
- const minDate = /* @__PURE__ */ new Date((MIN_WEEK_START || PRICING_TODAY) + "T00:00:00");
157
- const selectedMonStr = weekFrom || null;
158
- const hoverMonStr = hoverMon;
198
+ const minDate = /* @__PURE__ */ new Date((currentWeekStart() || PRICING_TODAY) + "T00:00:00");
199
+ const endMon = weekEndMonday(weekFrom, weekCount);
200
+ const [dispFrom, dispTo] = (() => {
201
+ if (!rangeMode || !weekFrom) return [weekFrom, endMon];
202
+ if (phase === "to" && hoverMon) {
203
+ if (hoverMon >= weekFrom) return [weekFrom, hoverMon];
204
+ return [hoverMon, hoverMon];
205
+ }
206
+ return [weekFrom, endMon];
207
+ })();
208
+ const previewCount = rangeMode && phase === "to" && hoverMon && hoverMon >= weekFrom ? weekCountBetween(weekFrom, hoverMon) : weekCount;
159
209
  const prevMonth = () => {
160
210
  if (calMonth === 0) {
161
211
  setCalYear((y) => y - 1);
@@ -170,6 +220,24 @@ function WeekCalendarPicker({
170
220
  };
171
221
  const weeks = [];
172
222
  for (let i = 0; i < days.length; i += 7) weeks.push(days.slice(i, i + 7));
223
+ const handleWeekClick = (monStr) => {
224
+ if (!rangeMode) {
225
+ onChange(monStr, 1);
226
+ return;
227
+ }
228
+ if (phase === "from") {
229
+ onChange(monStr, 1);
230
+ setPhase("to");
231
+ return;
232
+ }
233
+ if (monStr >= weekFrom) {
234
+ onChange(weekFrom, weekCountBetween(weekFrom, monStr));
235
+ setPhase("from");
236
+ return;
237
+ }
238
+ onChange(monStr, 1);
239
+ setPhase("to");
240
+ };
173
241
  return /* @__PURE__ */ jsxs(
174
242
  "div",
175
243
  {
@@ -178,70 +246,113 @@ function WeekCalendarPicker({
178
246
  flat ? "gap-3" : "gap-1 rounded-2xl border border-border bg-card p-3"
179
247
  ),
180
248
  children: [
181
- /* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center justify-between px-1", children: [
182
- /* @__PURE__ */ jsx(
183
- "button",
184
- {
185
- type: "button",
186
- onClick: prevMonth,
187
- className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent hover:text-muted-foreground",
188
- children: /* @__PURE__ */ jsx(ChevronLeft, { size: 12, strokeWidth: 2.25 })
189
- }
190
- ),
191
- /* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold text-muted-foreground", children: [
192
- MONTH_NAMES[calMonth],
193
- " ",
194
- calYear
249
+ rangeMode ? /* @__PURE__ */ jsx(
250
+ "div",
251
+ {
252
+ className: cn(
253
+ "flex items-center gap-2 px-1 text-[11px] font-medium",
254
+ phase === "to" ? "text-blue-600 dark:text-blue-400" : "text-muted-foreground"
255
+ ),
256
+ children: phase === "from" ? /* @__PURE__ */ jsxs(Fragment, { children: [
257
+ /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 shrink-0 rounded-full bg-muted-foreground/50" }),
258
+ "Click the first week"
259
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
260
+ /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 shrink-0 animate-pulse rounded-full bg-blue-500" }),
261
+ "Click the last week",
262
+ /* @__PURE__ */ jsx(
263
+ "button",
264
+ {
265
+ type: "button",
266
+ onClick: () => {
267
+ onChange(weekFrom, 1);
268
+ setPhase("to");
269
+ },
270
+ className: "ml-auto text-[10px] text-blue-400 underline underline-offset-2 hover:text-blue-600 dark:text-blue-400",
271
+ children: "Reset"
272
+ }
273
+ )
274
+ ] })
275
+ }
276
+ ) : null,
277
+ /* @__PURE__ */ jsxs("div", { className: calendarBlockClass, children: [
278
+ /* @__PURE__ */ jsxs("div", { className: calendarNavRowClass, children: [
279
+ /* @__PURE__ */ jsx(
280
+ "button",
281
+ {
282
+ type: "button",
283
+ onClick: prevMonth,
284
+ className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent hover:text-muted-foreground",
285
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 12, strokeWidth: 2.25 })
286
+ }
287
+ ),
288
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold text-muted-foreground", children: [
289
+ MONTH_NAMES[calMonth],
290
+ " ",
291
+ calYear
292
+ ] }),
293
+ /* @__PURE__ */ jsx(
294
+ "button",
295
+ {
296
+ type: "button",
297
+ onClick: nextMonth,
298
+ className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent hover:text-muted-foreground",
299
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 12, strokeWidth: 2.25 })
300
+ }
301
+ )
195
302
  ] }),
196
- /* @__PURE__ */ jsx(
197
- "button",
198
- {
199
- type: "button",
200
- onClick: nextMonth,
201
- className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent hover:text-muted-foreground",
202
- children: /* @__PURE__ */ jsx(ChevronRight, { size: 12, strokeWidth: 2.25 })
203
- }
204
- )
303
+ /* @__PURE__ */ jsx("div", { className: calendarWeekdayRowClass, children: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"].map((d) => /* @__PURE__ */ jsx("span", { className: calendarWeekdayCellClass, children: d }, d)) }),
304
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col divide-y divide-border/40", children: weeks.map((week, wi) => {
305
+ const mondayDate = week[0];
306
+ const monStr = dayStr(mondayDate);
307
+ const isSelectable = mondayDate >= minDate;
308
+ const isStartWeek = Boolean(weekFrom) && monStr === dispFrom;
309
+ const isEndWeek = Boolean(weekFrom) && monStr === dispTo;
310
+ const inRange = rangeMode && Boolean(weekFrom) && monStr >= dispFrom && monStr <= dispTo;
311
+ const isSelected = rangeMode ? inRange : monStr === weekFrom;
312
+ return /* @__PURE__ */ jsx(
313
+ "div",
314
+ {
315
+ className: cn(
316
+ "relative flex cursor-pointer transition-colors",
317
+ isSelected && (isStartWeek || isEndWeek) && "bg-blue-100/80",
318
+ isSelected && !isStartWeek && !isEndWeek && "bg-blue-50",
319
+ isSelectable ? "hover:bg-accent/60" : "cursor-not-allowed opacity-60"
320
+ ),
321
+ onMouseEnter: () => isSelectable && setHoverMon(monStr),
322
+ onMouseLeave: () => setHoverMon(null),
323
+ onClick: () => isSelectable && handleWeekClick(monStr),
324
+ children: week.map((d, di) => {
325
+ const isCurrentMonth = d.getMonth() === calMonth;
326
+ const isMon = di === 0;
327
+ const isSun = di === 6;
328
+ const filled = rangeMode ? isStartWeek && isMon || isEndWeek && isSun : isSelected && isMon;
329
+ return /* @__PURE__ */ jsx(
330
+ "div",
331
+ {
332
+ className: "relative z-10 flex h-9 flex-1 items-center justify-center border-r border-border/40 last:border-r-0",
333
+ children: /* @__PURE__ */ jsx(
334
+ "span",
335
+ {
336
+ className: cn(
337
+ "flex h-7 w-7 items-center justify-center rounded-full text-[11px] transition-colors",
338
+ isCurrentMonth ? "text-muted-foreground" : "text-muted-foreground/50",
339
+ !isSelectable && "text-muted-foreground/40",
340
+ isSelected && "font-medium text-blue-700 dark:text-blue-300",
341
+ filled && "bg-blue-500 font-semibold text-white shadow-sm"
342
+ ),
343
+ children: d.getDate()
344
+ }
345
+ )
346
+ },
347
+ di
348
+ );
349
+ })
350
+ },
351
+ wi
352
+ );
353
+ }) })
205
354
  ] }),
206
- /* @__PURE__ */ jsx("div", { className: "mb-0.5 grid grid-cols-7", children: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"].map((d) => /* @__PURE__ */ jsx("span", { className: "py-0.5 text-center text-[10px] font-medium text-muted-foreground", children: d }, d)) }),
207
- weeks.map((week, wi) => {
208
- const mondayDate = week[0];
209
- const monStr = dayStr(mondayDate);
210
- const isSelectable = mondayDate >= minDate;
211
- const isSelected = monStr === selectedMonStr;
212
- const isHovered = monStr === hoverMonStr && isSelectable && !isSelected;
213
- return /* @__PURE__ */ jsx(
214
- "div",
215
- {
216
- className: `flex cursor-pointer overflow-hidden rounded-xl transition-colors
217
- ${isSelected ? "bg-blue-100" : isHovered ? "bg-blue-50" : isSelectable ? "hover:bg-accent" : "cursor-not-allowed"}`,
218
- onMouseEnter: () => isSelectable && setHoverMon(monStr),
219
- onMouseLeave: () => setHoverMon(null),
220
- onClick: () => isSelectable && onChange(monStr),
221
- children: week.map((d, di) => {
222
- const isCurrentMonth = d.getMonth() === calMonth;
223
- const isMon = di === 0;
224
- const isSelectedMon = isSelected && isMon;
225
- return /* @__PURE__ */ jsx("div", { className: "flex h-8 flex-1 items-center justify-center", children: /* @__PURE__ */ jsx(
226
- "span",
227
- {
228
- className: `
229
- flex h-7 w-7 items-center justify-center rounded-full text-[11px] transition-colors
230
- ${isSelectedMon ? "bg-blue-500 font-semibold text-white" : ""}
231
- ${!isSelectedMon && isSelected ? "text-blue-700 dark:text-blue-400" : ""}
232
- ${!isSelected && isSelectable && isCurrentMonth ? "text-muted-foreground" : ""}
233
- ${!isSelectable && isCurrentMonth ? "text-muted-foreground/60" : ""}
234
- ${!isCurrentMonth ? "text-muted-foreground/60" : ""}
235
- `,
236
- children: d.getDate()
237
- }
238
- ) }, di);
239
- })
240
- },
241
- wi
242
- );
243
- }),
244
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-center text-[10px] text-muted-foreground", children: "Click any row to select that week" })
355
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-center text-[10px] text-muted-foreground", children: rangeMode ? phase === "from" ? "Click a week to start the range" : previewCount > 1 ? `${previewCount} weeks \xB7 ${weekRangeLabel(dispFrom, previewCount)}` : "Click a week to finish the range" : weekFrom ? `Selected week of ${formatDateLabel(weekFrom)}` : "Click any row to select that week" })
245
356
  ]
246
357
  }
247
358
  );
@@ -250,16 +361,21 @@ function MonthRangeCalendar({
250
361
  monthFrom,
251
362
  monthTo,
252
363
  onChange,
364
+ rangeMode,
253
365
  flat = false
254
366
  }) {
255
367
  const initYear = monthFrom ? parseInt(monthFrom.split("-")[0], 10) : (/* @__PURE__ */ new Date()).getFullYear();
256
368
  const [calYear, setCalYear] = useState(initYear);
257
369
  const [phase, setPhase] = useState("from");
258
370
  const [hoverYm, setHoverYm] = useState(null);
371
+ useEffect(() => {
372
+ setPhase("from");
373
+ }, [rangeMode]);
259
374
  const ABBR = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
260
375
  const toYm = (y, m) => `${y}-${String(m + 1).padStart(2, "0")}`;
261
- const minYm = MIN_MONTH || `${(/* @__PURE__ */ new Date()).getFullYear()}-01`;
376
+ const minYm = currentMonthYm() || `${(/* @__PURE__ */ new Date()).getFullYear()}-01`;
262
377
  const [dispFrom, dispTo] = (() => {
378
+ if (!rangeMode) return [monthFrom, monthFrom || monthTo];
263
379
  if (phase === "to" && hoverYm) {
264
380
  if (hoverYm >= monthFrom) return [monthFrom, hoverYm];
265
381
  return [hoverYm, hoverYm];
@@ -269,6 +385,10 @@ function MonthRangeCalendar({
269
385
  const isSingle = dispFrom === dispTo;
270
386
  const handleClick = (ymStr) => {
271
387
  if (ymStr < minYm) return;
388
+ if (!rangeMode) {
389
+ onChange(ymStr, ymStr);
390
+ return;
391
+ }
272
392
  if (phase === "from") {
273
393
  onChange(ymStr, ymStr);
274
394
  setPhase("to");
@@ -277,6 +397,7 @@ function MonthRangeCalendar({
277
397
  setPhase("from");
278
398
  } else {
279
399
  onChange(ymStr, ymStr);
400
+ setPhase("to");
280
401
  }
281
402
  };
282
403
  const actualCount = monthCount(monthFrom, monthTo);
@@ -289,28 +410,7 @@ function MonthRangeCalendar({
289
410
  flat ? "flex flex-col gap-5" : "overflow-hidden rounded-2xl border border-border bg-card"
290
411
  ),
291
412
  children: [
292
- /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between", flat ? "px-1" : "px-4 pb-2 pt-4"), children: [
293
- /* @__PURE__ */ jsx(
294
- "button",
295
- {
296
- type: "button",
297
- onClick: () => setCalYear((y) => y - 1),
298
- className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent hover:text-muted-foreground",
299
- children: /* @__PURE__ */ jsx(ChevronLeft, { size: 12, strokeWidth: 2.25 })
300
- }
301
- ),
302
- /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-muted-foreground", children: calYear }),
303
- /* @__PURE__ */ jsx(
304
- "button",
305
- {
306
- type: "button",
307
- onClick: () => setCalYear((y) => y + 1),
308
- className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent hover:text-muted-foreground",
309
- children: /* @__PURE__ */ jsx(ChevronRight, { size: 12, strokeWidth: 2.25 })
310
- }
311
- )
312
- ] }),
313
- /* @__PURE__ */ jsx(
413
+ rangeMode ? /* @__PURE__ */ jsx(
314
414
  "div",
315
415
  {
316
416
  className: cn(
@@ -322,10 +422,10 @@ function MonthRangeCalendar({
322
422
  ),
323
423
  children: phase === "from" ? /* @__PURE__ */ jsxs(Fragment, { children: [
324
424
  /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 shrink-0 rounded-full bg-muted-foreground/50" }),
325
- "Click a month to set the start"
425
+ "Click the first month"
326
426
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
327
427
  /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 shrink-0 animate-pulse rounded-full bg-blue-500" }),
328
- "Now click to set the end month",
428
+ "Click the last month",
329
429
  /* @__PURE__ */ jsx(
330
430
  "button",
331
431
  {
@@ -340,59 +440,76 @@ function MonthRangeCalendar({
340
440
  )
341
441
  ] })
342
442
  }
343
- ),
344
- /* @__PURE__ */ jsx(
345
- "div",
346
- {
347
- className: cn("grid grid-cols-4", flat ? "gap-2 py-1" : "px-2 pb-3 pt-1"),
348
- style: flat ? void 0 : { gap: 0 },
349
- children: ABBR.map((label, i) => {
350
- const ymStr = toYm(calYear, i);
351
- const disabled = ymStr < minYm;
352
- const inRange = !disabled && ymStr >= dispFrom && ymStr <= dispTo;
353
- const isFrom = ymStr === dispFrom;
354
- const isTo = ymStr === dispTo;
355
- const showStrip = inRange && !isSingle;
356
- const sLeft = isFrom ? "left-1/2" : "left-0";
357
- const sRight = isTo ? "right-1/2" : "right-0";
358
- const filled = (isFrom || isTo) && !disabled;
359
- return /* @__PURE__ */ jsxs(
360
- "div",
361
- {
362
- className: cn(
363
- "relative",
364
- flat ? "h-14" : "h-12",
365
- disabled ? "cursor-not-allowed" : "cursor-pointer"
366
- ),
367
- onMouseEnter: () => !disabled && setHoverYm(ymStr),
368
- onMouseLeave: () => setHoverYm(null),
369
- onClick: () => handleClick(ymStr),
370
- children: [
371
- showStrip ? /* @__PURE__ */ jsx("div", { className: `absolute bottom-1.5 top-1.5 bg-blue-100 ${sLeft} ${sRight}` }) : null,
372
- /* @__PURE__ */ jsx(
373
- "div",
374
- {
375
- className: cn(
376
- "relative z-10 flex w-full items-center justify-center",
377
- flat ? "h-14" : "h-12"
378
- ),
379
- children: /* @__PURE__ */ jsx(
380
- "span",
381
- {
382
- className: `flex h-9 w-9 items-center justify-center rounded-full text-xs font-medium transition-all duration-100
383
- ${filled ? "bg-blue-500 text-white shadow-md shadow-blue-200" : inRange ? "text-blue-700 dark:text-blue-400" : !disabled ? "text-muted-foreground hover:bg-accent" : "text-muted-foreground/60"}`,
384
- children: label
385
- }
386
- )
387
- }
388
- )
389
- ]
390
- },
391
- i
392
- );
393
- })
394
- }
395
- ),
443
+ ) : null,
444
+ /* @__PURE__ */ jsxs("div", { className: cn(calendarBlockClass, !flat && "mx-3 mb-3"), children: [
445
+ /* @__PURE__ */ jsxs("div", { className: calendarNavRowClass, children: [
446
+ /* @__PURE__ */ jsx(
447
+ "button",
448
+ {
449
+ type: "button",
450
+ onClick: () => setCalYear((y) => y - 1),
451
+ className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent hover:text-muted-foreground",
452
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 12, strokeWidth: 2.25 })
453
+ }
454
+ ),
455
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-muted-foreground", children: calYear }),
456
+ /* @__PURE__ */ jsx(
457
+ "button",
458
+ {
459
+ type: "button",
460
+ onClick: () => setCalYear((y) => y + 1),
461
+ className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent hover:text-muted-foreground",
462
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 12, strokeWidth: 2.25 })
463
+ }
464
+ )
465
+ ] }),
466
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-4", children: ABBR.map((label, i) => {
467
+ const ymStr = toYm(calYear, i);
468
+ const disabled = ymStr < minYm;
469
+ const inRange = !disabled && ymStr >= dispFrom && ymStr <= dispTo;
470
+ const isFrom = ymStr === dispFrom;
471
+ const isTo = ymStr === dispTo;
472
+ const showStrip = rangeMode && inRange && !isSingle;
473
+ const sLeft = isFrom ? "left-1/2" : "left-0";
474
+ const sRight = isTo ? "right-1/2" : "right-0";
475
+ const filled = (isFrom || isTo || !rangeMode && ymStr === monthFrom) && !disabled;
476
+ return /* @__PURE__ */ jsxs(
477
+ "div",
478
+ {
479
+ className: cn(
480
+ "relative",
481
+ cellGridLines(i, 12, 4),
482
+ flat ? "h-14" : "h-12",
483
+ disabled ? "cursor-not-allowed" : "cursor-pointer"
484
+ ),
485
+ onMouseEnter: () => !disabled && setHoverYm(ymStr),
486
+ onMouseLeave: () => setHoverYm(null),
487
+ onClick: () => handleClick(ymStr),
488
+ children: [
489
+ showStrip ? /* @__PURE__ */ jsx("div", { className: `absolute bottom-1.5 top-1.5 bg-blue-100 ${sLeft} ${sRight}` }) : null,
490
+ /* @__PURE__ */ jsx(
491
+ "div",
492
+ {
493
+ className: cn(
494
+ "relative z-10 flex w-full items-center justify-center",
495
+ flat ? "h-14" : "h-12"
496
+ ),
497
+ children: /* @__PURE__ */ jsx(
498
+ "span",
499
+ {
500
+ className: `flex h-9 w-9 items-center justify-center rounded-full text-xs font-medium transition-all duration-100
501
+ ${filled ? "bg-blue-500 text-white shadow-md shadow-blue-200" : inRange ? "text-blue-700 dark:text-blue-400" : !disabled ? "text-muted-foreground hover:bg-accent" : "text-muted-foreground/60"}`,
502
+ children: label
503
+ }
504
+ )
505
+ }
506
+ )
507
+ ]
508
+ },
509
+ i
510
+ );
511
+ }) })
512
+ ] }),
396
513
  /* @__PURE__ */ jsx(
397
514
  "div",
398
515
  {
@@ -400,10 +517,13 @@ function MonthRangeCalendar({
400
517
  "flex items-center justify-between transition-colors",
401
518
  flat ? "border-t border-border/70 pt-4 text-xs" : cn(
402
519
  "border-t px-4 py-2.5",
403
- phase === "from" && actualCount > 0 ? "border-border bg-muted" : "border-transparent"
520
+ rangeMode && phase === "from" && actualCount > 0 ? "border-border bg-muted" : "border-transparent"
404
521
  )
405
522
  ),
406
- children: phase === "from" && actualCount > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
523
+ children: !rangeMode && monthFrom ? /* @__PURE__ */ jsxs(Fragment, { children: [
524
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: monthLabel(monthFrom) }),
525
+ /* @__PURE__ */ jsx("span", { className: "rounded-full bg-blue-50 px-2 py-0.5 text-[10px] font-semibold text-blue-600 dark:bg-blue-950/40 dark:text-blue-400", children: "1 month" })
526
+ ] }) : rangeMode && phase === "from" && actualCount > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
407
527
  /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
408
528
  monthLabel(monthFrom),
409
529
  " \u2192 ",
@@ -414,14 +534,14 @@ function MonthRangeCalendar({
414
534
  " month",
415
535
  actualCount !== 1 ? "s" : ""
416
536
  ] })
417
- ] }) : phase === "to" ? /* @__PURE__ */ jsxs(Fragment, { children: [
537
+ ] }) : rangeMode && phase === "to" ? /* @__PURE__ */ jsxs(Fragment, { children: [
418
538
  /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: hoverYm && hoverYm >= monthFrom ? `${monthLabel(monthFrom)} \u2192 ${monthLabel(hoverYm)}` : `Start: ${monthLabel(monthFrom)}` }),
419
539
  hoverYm && hoverYm >= monthFrom && previewCount > 0 ? /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-medium text-blue-400", children: [
420
540
  previewCount,
421
541
  " month",
422
542
  previewCount !== 1 ? "s" : ""
423
543
  ] }) : null
424
- ] }) : /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/60", children: "No months selected" })
544
+ ] }) : /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/60", children: rangeMode ? "Select a month range" : "Click a month to book" })
425
545
  }
426
546
  )
427
547
  ]
@@ -433,25 +553,58 @@ function MonthPeriodPicker({
433
553
  onChange,
434
554
  flat = true
435
555
  }) {
556
+ const [rangeMode, setRangeMode] = useState(
557
+ () => Boolean(booking.monthFrom && booking.monthTo && booking.monthFrom !== booking.monthTo)
558
+ );
559
+ useEffect(() => {
560
+ if (booking.monthFrom && booking.monthTo && booking.monthFrom !== booking.monthTo) {
561
+ setRangeMode(true);
562
+ }
563
+ }, [booking.monthFrom, booking.monthTo]);
564
+ const monthCountSelected = monthCount(booking.monthFrom, booking.monthTo);
436
565
  return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", flat ? "gap-5" : "gap-3"), children: [
437
- IS_PARTIAL_MONTH ? /* @__PURE__ */ jsxs(ScheduleInfoBanner, { children: [
438
- "Today is ",
439
- new Date(PRICING_TODAY).toLocaleDateString("en-US", { month: "long", day: "numeric" }),
440
- ". Current month is partially elapsed \u2014 earliest bookable month is",
441
- " ",
442
- /* @__PURE__ */ jsx("strong", { children: monthLabel(MIN_MONTH) }),
443
- "."
444
- ] }) : null,
445
- /* @__PURE__ */ jsx(ScheduleFieldLabel, { children: "Select month range" }),
566
+ /* @__PURE__ */ jsx(ScheduleInfoBanner, { children: "Current month is available at full price, or choose a future month." }),
567
+ /* @__PURE__ */ jsx(
568
+ CheckboxField,
569
+ {
570
+ size: "sm",
571
+ label: "Select multiple months",
572
+ className: "w-fit items-center",
573
+ checked: rangeMode,
574
+ onCheckedChange: (checked) => {
575
+ setRangeMode(checked);
576
+ const from = booking.monthFrom || currentMonthYm();
577
+ if (!checked) {
578
+ onChange({ monthFrom: from, monthTo: from });
579
+ return;
580
+ }
581
+ const to = booking.monthTo && booking.monthTo > from ? booking.monthTo : addMonthsYm(from, 1);
582
+ onChange({ monthFrom: from, monthTo: to });
583
+ }
584
+ }
585
+ ),
586
+ /* @__PURE__ */ jsx(ScheduleFieldLabel, { children: rangeMode ? "Select month range" : "Select month" }),
446
587
  /* @__PURE__ */ jsx(
447
588
  MonthRangeCalendar,
448
589
  {
449
590
  flat,
591
+ rangeMode,
450
592
  monthFrom: booking.monthFrom,
451
593
  monthTo: booking.monthTo,
452
594
  onChange: (from, to) => onChange({ monthFrom: from, monthTo: to })
453
595
  }
454
- )
596
+ ),
597
+ monthCountSelected > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded-xl border border-border bg-muted px-3 py-2.5", children: [
598
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
599
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: "Period" }),
600
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: rangeMode && monthCountSelected > 1 ? `${monthLabel(booking.monthFrom)} \u2192 ${monthLabel(booking.monthTo)}` : monthLabel(booking.monthFrom) })
601
+ ] }),
602
+ /* @__PURE__ */ jsxs("span", { className: "rounded-lg bg-blue-50 px-2 py-1 text-xs font-semibold text-blue-600 dark:bg-blue-950/40 dark:text-blue-400", children: [
603
+ monthCountSelected,
604
+ " month",
605
+ monthCountSelected !== 1 ? "s" : ""
606
+ ] })
607
+ ] }) : null
455
608
  ] });
456
609
  }
457
610
  function WeekPeriodPicker({
@@ -459,49 +612,47 @@ function WeekPeriodPicker({
459
612
  onChange,
460
613
  flat = true
461
614
  }) {
615
+ const [rangeMode, setRangeMode] = useState(() => booking.weekCount > 1);
616
+ useEffect(() => {
617
+ if (booking.weekCount > 1) setRangeMode(true);
618
+ }, [booking.weekCount]);
462
619
  const rangeStr = weekRangeLabel(booking.weekFrom, booking.weekCount);
463
- const isCurrentWeekPartial = new Date(PRICING_TODAY).getDay() !== 1;
464
620
  return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", flat ? "gap-5" : "gap-3"), children: [
465
- isCurrentWeekPartial ? /* @__PURE__ */ jsxs(ScheduleInfoBanner, { children: [
466
- "This week started on a ",
467
- new Date(PRICING_TODAY).toLocaleDateString("en-US", { weekday: "long" }),
468
- " \u2014 earliest full week begins",
469
- " ",
470
- /* @__PURE__ */ jsx("strong", { children: new Date(MIN_WEEK_START).toLocaleDateString("en-US", {
471
- month: "long",
472
- day: "numeric"
473
- }) }),
474
- "."
475
- ] }) : null,
621
+ /* @__PURE__ */ jsx(ScheduleInfoBanner, { children: "Current week is available at full price, or choose a future week." }),
622
+ /* @__PURE__ */ jsx(
623
+ CheckboxField,
624
+ {
625
+ size: "sm",
626
+ label: "Select multiple weeks",
627
+ className: "w-fit items-center",
628
+ checked: rangeMode,
629
+ onCheckedChange: (checked) => {
630
+ setRangeMode(checked);
631
+ const from = booking.weekFrom || currentWeekStart();
632
+ if (!checked) {
633
+ onChange({ weekFrom: from, weekCount: 1 });
634
+ return;
635
+ }
636
+ onChange({
637
+ weekFrom: from,
638
+ weekCount: Math.max(2, booking.weekCount)
639
+ });
640
+ }
641
+ }
642
+ ),
476
643
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
477
- /* @__PURE__ */ jsx(ScheduleFieldLabel, { children: "Starting week" }),
644
+ /* @__PURE__ */ jsx(ScheduleFieldLabel, { children: rangeMode ? "Select week range" : "Select week" }),
478
645
  /* @__PURE__ */ jsx(
479
646
  WeekCalendarPicker,
480
647
  {
481
648
  flat,
649
+ rangeMode,
482
650
  weekFrom: booking.weekFrom,
483
- onChange: (v) => onChange({ weekFrom: v })
651
+ weekCount: booking.weekCount,
652
+ onChange: (weekFrom, weekCount) => onChange({ weekFrom, weekCount })
484
653
  }
485
654
  )
486
655
  ] }),
487
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
488
- /* @__PURE__ */ jsx(ScheduleFieldLabel, { children: "Number of weeks" }),
489
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
490
- /* @__PURE__ */ jsx(
491
- ScheduleStepper,
492
- {
493
- value: booking.weekCount,
494
- max: 52,
495
- onChange: (v) => onChange({ weekCount: v })
496
- }
497
- ),
498
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
499
- booking.weekCount,
500
- " week",
501
- booking.weekCount !== 1 ? "s" : ""
502
- ] })
503
- ] })
504
- ] }),
505
656
  booking.weekFrom && booking.weekCount > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded-xl border border-border bg-muted px-3 py-2.5", children: [
506
657
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
507
658
  /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: "Period" }),
@@ -529,7 +680,7 @@ function isBillableDay(day, start, end) {
529
680
  function DayCalendarPicker({
530
681
  selectedDate,
531
682
  onChange,
532
- minDate = PRICING_TODAY,
683
+ minDate = todayLocalYmd(),
533
684
  flat = false
534
685
  }) {
535
686
  const initDate = /* @__PURE__ */ new Date((selectedDate || minDate) + "T00:00:00");
@@ -556,65 +707,68 @@ function DayCalendarPicker({
556
707
  flat ? "gap-3" : "gap-1 rounded-2xl border border-border bg-card p-3"
557
708
  ),
558
709
  children: [
559
- /* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center justify-between px-1", children: [
560
- /* @__PURE__ */ jsx(
561
- "button",
562
- {
563
- type: "button",
564
- onClick: prevMonth,
565
- className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent",
566
- children: /* @__PURE__ */ jsx(ChevronLeft, { size: 12, strokeWidth: 2.25 })
567
- }
568
- ),
569
- /* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold text-muted-foreground", children: [
570
- MONTH_NAMES[calMonth],
571
- " ",
572
- calYear
710
+ /* @__PURE__ */ jsxs("div", { className: calendarBlockClass, children: [
711
+ /* @__PURE__ */ jsxs("div", { className: calendarNavRowClass, children: [
712
+ /* @__PURE__ */ jsx(
713
+ "button",
714
+ {
715
+ type: "button",
716
+ onClick: prevMonth,
717
+ className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent",
718
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 12, strokeWidth: 2.25 })
719
+ }
720
+ ),
721
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold text-muted-foreground", children: [
722
+ MONTH_NAMES[calMonth],
723
+ " ",
724
+ calYear
725
+ ] }),
726
+ /* @__PURE__ */ jsx(
727
+ "button",
728
+ {
729
+ type: "button",
730
+ onClick: nextMonth,
731
+ className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent",
732
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 12, strokeWidth: 2.25 })
733
+ }
734
+ )
573
735
  ] }),
574
- /* @__PURE__ */ jsx(
575
- "button",
576
- {
577
- type: "button",
578
- onClick: nextMonth,
579
- className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent",
580
- children: /* @__PURE__ */ jsx(ChevronRight, { size: 12, strokeWidth: 2.25 })
581
- }
582
- )
736
+ /* @__PURE__ */ jsx("div", { className: calendarWeekdayRowClass, children: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"].map((d) => /* @__PURE__ */ jsx("span", { className: calendarWeekdayCellClass, children: d }, d)) }),
737
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7", children: days.map((d, i) => {
738
+ const ds = dayStr(d);
739
+ const isCurrentMonth = d.getMonth() === calMonth;
740
+ const isSelectable = ds >= minDate;
741
+ const isSelected = ds === selectedDate;
742
+ return /* @__PURE__ */ jsx(
743
+ "button",
744
+ {
745
+ type: "button",
746
+ disabled: !isSelectable,
747
+ onClick: () => isSelectable && onChange(ds),
748
+ className: cn(
749
+ "flex h-9 items-center justify-center",
750
+ cellGridLines(i, days.length, 7),
751
+ !isSelectable && "cursor-not-allowed"
752
+ ),
753
+ children: /* @__PURE__ */ jsx(
754
+ "span",
755
+ {
756
+ className: cn(
757
+ "flex h-8 w-8 items-center justify-center rounded-full text-[11px] transition-colors",
758
+ isSelectable && isCurrentMonth && "text-foreground hover:bg-accent",
759
+ isSelectable && !isCurrentMonth && "text-muted-foreground hover:bg-accent",
760
+ !isSelectable && isCurrentMonth && "text-muted-foreground/40",
761
+ !isSelectable && !isCurrentMonth && "text-muted-foreground/30",
762
+ isSelected && "bg-blue-500 font-semibold text-white shadow-sm"
763
+ ),
764
+ children: d.getDate()
765
+ }
766
+ )
767
+ },
768
+ ds
769
+ );
770
+ }) })
583
771
  ] }),
584
- /* @__PURE__ */ jsx("div", { className: "mb-0.5 grid grid-cols-7", children: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"].map((d) => /* @__PURE__ */ jsx("span", { className: "py-0.5 text-center text-[10px] font-medium text-muted-foreground", children: d }, d)) }),
585
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7", children: days.map((d) => {
586
- const ds = dayStr(d);
587
- const isCurrentMonth = d.getMonth() === calMonth;
588
- const isSelectable = ds >= minDate;
589
- const isSelected = ds === selectedDate;
590
- return /* @__PURE__ */ jsx(
591
- "button",
592
- {
593
- type: "button",
594
- disabled: !isSelectable,
595
- onClick: () => isSelectable && onChange(ds),
596
- className: cn(
597
- "flex h-9 items-center justify-center",
598
- !isSelectable && "cursor-not-allowed"
599
- ),
600
- children: /* @__PURE__ */ jsx(
601
- "span",
602
- {
603
- className: cn(
604
- "flex h-8 w-8 items-center justify-center rounded-full text-[11px] transition-colors",
605
- isSelected && "bg-blue-500 font-semibold text-white shadow-sm",
606
- !isSelected && isSelectable && isCurrentMonth && "text-foreground hover:bg-accent",
607
- !isSelected && isSelectable && !isCurrentMonth && "text-muted-foreground hover:bg-accent",
608
- !isSelectable && isCurrentMonth && "text-muted-foreground/40",
609
- !isSelectable && !isCurrentMonth && "text-muted-foreground/30"
610
- ),
611
- children: d.getDate()
612
- }
613
- )
614
- },
615
- ds
616
- );
617
- }) }),
618
772
  selectedDate ? /* @__PURE__ */ jsxs("p", { className: "mt-1 text-center text-[10px] text-muted-foreground", children: [
619
773
  "Selected ",
620
774
  formatDateLabel(selectedDate)
@@ -627,7 +781,7 @@ function DayRangeCalendar({
627
781
  startDate,
628
782
  endDate,
629
783
  onChange,
630
- minDate = PRICING_TODAY,
784
+ minDate = currentWeekStart(),
631
785
  flat = false
632
786
  }) {
633
787
  const initDate = /* @__PURE__ */ new Date((startDate || minDate) + "T00:00:00");
@@ -680,31 +834,6 @@ function DayRangeCalendar({
680
834
  flat ? "gap-3" : "gap-1 rounded-2xl border border-border bg-card p-3"
681
835
  ),
682
836
  children: [
683
- /* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center justify-between px-1", children: [
684
- /* @__PURE__ */ jsx(
685
- "button",
686
- {
687
- type: "button",
688
- onClick: prevMonth,
689
- className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent",
690
- children: /* @__PURE__ */ jsx(ChevronLeft, { size: 12, strokeWidth: 2.25 })
691
- }
692
- ),
693
- /* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold text-muted-foreground", children: [
694
- MONTH_NAMES[calMonth],
695
- " ",
696
- calYear
697
- ] }),
698
- /* @__PURE__ */ jsx(
699
- "button",
700
- {
701
- type: "button",
702
- onClick: nextMonth,
703
- className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent",
704
- children: /* @__PURE__ */ jsx(ChevronRight, { size: 12, strokeWidth: 2.25 })
705
- }
706
- )
707
- ] }),
708
837
  /* @__PURE__ */ jsx(
709
838
  "div",
710
839
  {
@@ -733,57 +862,85 @@ function DayRangeCalendar({
733
862
  ] })
734
863
  }
735
864
  ),
736
- /* @__PURE__ */ jsx("div", { className: "mb-0.5 grid grid-cols-7", children: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"].map((d) => /* @__PURE__ */ jsx("span", { className: "py-0.5 text-center text-[10px] font-medium text-muted-foreground", children: d }, d)) }),
737
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7", children: days.map((d) => {
738
- const ds = dayStr(d);
739
- const isCurrentMonth = d.getMonth() === calMonth;
740
- const isSelectable = ds >= minDate;
741
- const inRange = isBillableDay(ds, dispStart, dispEnd);
742
- const isStart = ds === dispStart;
743
- const isEnd = ds === lastBillableDay && dispEnd && diffDays(dispStart, dispEnd) > 1;
744
- const isSingle = dispStart && dispEnd && diffDays(dispStart, dispEnd) === 1 && ds === dispStart;
745
- return /* @__PURE__ */ jsxs(
746
- "button",
747
- {
748
- type: "button",
749
- disabled: !isSelectable,
750
- onMouseEnter: () => isSelectable && setHoverDay(ds),
751
- onMouseLeave: () => setHoverDay(null),
752
- onClick: () => isSelectable && handleClick(ds),
753
- className: cn(
754
- "relative flex h-9 items-center justify-center",
755
- !isSelectable && "cursor-not-allowed"
756
- ),
757
- children: [
758
- inRange && !isSingle ? /* @__PURE__ */ jsx(
759
- "span",
760
- {
761
- className: cn(
762
- "absolute inset-y-1.5 bg-blue-100",
763
- isStart ? "left-1/2 right-0 rounded-l-full" : isEnd ? "left-0 right-1/2 rounded-r-full" : "inset-x-0"
764
- )
765
- }
766
- ) : null,
767
- /* @__PURE__ */ jsx(
768
- "span",
769
- {
770
- className: cn(
771
- "relative z-10 flex h-8 w-8 items-center justify-center rounded-full text-[11px] transition-colors",
772
- (isStart || isEnd || isSingle) && "bg-blue-500 font-semibold text-white shadow-sm",
773
- !isStart && !isEnd && !isSingle && inRange && "font-medium text-blue-700 dark:text-blue-400",
774
- !inRange && isSelectable && isCurrentMonth && "text-foreground hover:bg-accent",
775
- !inRange && isSelectable && !isCurrentMonth && "text-muted-foreground hover:bg-accent",
776
- !isSelectable && isCurrentMonth && "text-muted-foreground/40",
777
- !isSelectable && !isCurrentMonth && "text-muted-foreground/30"
778
- ),
779
- children: d.getDate()
780
- }
781
- )
782
- ]
783
- },
784
- ds
785
- );
786
- }) }),
865
+ /* @__PURE__ */ jsxs("div", { className: calendarBlockClass, children: [
866
+ /* @__PURE__ */ jsxs("div", { className: calendarNavRowClass, children: [
867
+ /* @__PURE__ */ jsx(
868
+ "button",
869
+ {
870
+ type: "button",
871
+ onClick: prevMonth,
872
+ className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent",
873
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 12, strokeWidth: 2.25 })
874
+ }
875
+ ),
876
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold text-muted-foreground", children: [
877
+ MONTH_NAMES[calMonth],
878
+ " ",
879
+ calYear
880
+ ] }),
881
+ /* @__PURE__ */ jsx(
882
+ "button",
883
+ {
884
+ type: "button",
885
+ onClick: nextMonth,
886
+ className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent",
887
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 12, strokeWidth: 2.25 })
888
+ }
889
+ )
890
+ ] }),
891
+ /* @__PURE__ */ jsx("div", { className: calendarWeekdayRowClass, children: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"].map((d) => /* @__PURE__ */ jsx("span", { className: calendarWeekdayCellClass, children: d }, d)) }),
892
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7", children: days.map((d, i) => {
893
+ const ds = dayStr(d);
894
+ const isCurrentMonth = d.getMonth() === calMonth;
895
+ const isSelectable = ds >= minDate;
896
+ const inRange = isBillableDay(ds, dispStart, dispEnd);
897
+ const isStart = ds === dispStart;
898
+ const isEnd = ds === lastBillableDay && dispEnd && diffDays(dispStart, dispEnd) > 1;
899
+ const isSingle = dispStart && dispEnd && diffDays(dispStart, dispEnd) === 1 && ds === dispStart;
900
+ return /* @__PURE__ */ jsxs(
901
+ "button",
902
+ {
903
+ type: "button",
904
+ disabled: !isSelectable,
905
+ onMouseEnter: () => isSelectable && setHoverDay(ds),
906
+ onMouseLeave: () => setHoverDay(null),
907
+ onClick: () => isSelectable && handleClick(ds),
908
+ className: cn(
909
+ "relative flex h-9 items-center justify-center",
910
+ cellGridLines(i, days.length, 7),
911
+ !isSelectable && "cursor-not-allowed"
912
+ ),
913
+ children: [
914
+ inRange && !isSingle ? /* @__PURE__ */ jsx(
915
+ "span",
916
+ {
917
+ className: cn(
918
+ "absolute inset-y-1.5 bg-blue-100",
919
+ isStart ? "left-1/2 right-0 rounded-l-full" : isEnd ? "left-0 right-1/2 rounded-r-full" : "inset-x-0"
920
+ )
921
+ }
922
+ ) : null,
923
+ /* @__PURE__ */ jsx(
924
+ "span",
925
+ {
926
+ className: cn(
927
+ "relative z-10 flex h-8 w-8 items-center justify-center rounded-full text-[11px] transition-colors",
928
+ !inRange && isSelectable && isCurrentMonth && "text-foreground hover:bg-accent",
929
+ !inRange && isSelectable && !isCurrentMonth && "text-muted-foreground hover:bg-accent",
930
+ !isSelectable && isCurrentMonth && "text-muted-foreground/40",
931
+ !isSelectable && !isCurrentMonth && "text-muted-foreground/30",
932
+ inRange && "font-medium text-blue-700 dark:text-blue-300",
933
+ (isStart || isEnd || isSingle) && "bg-blue-500 font-semibold text-white shadow-sm"
934
+ ),
935
+ children: d.getDate()
936
+ }
937
+ )
938
+ ]
939
+ },
940
+ ds
941
+ );
942
+ }) })
943
+ ] }),
787
944
  /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between border-t border-border/70 pt-3 text-xs", children: dayCount > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
788
945
  /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
789
946
  formatDateLabel(startDate),
@@ -807,30 +964,24 @@ function DayPeriodPicker({
807
964
  const oneDay = booking.oneDayBooking ?? true;
808
965
  const dayCount = oneDay ? 1 : diffDays(booking.startDate, booking.endDate);
809
966
  return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", flat ? "gap-5" : "gap-3"), children: [
810
- /* @__PURE__ */ jsx("div", { className: "flex gap-2", children: [
811
- { key: true, label: "Single day" },
812
- { key: false, label: "Multiple days" }
813
- ].map(({ key, label }) => /* @__PURE__ */ jsx(
814
- "button",
967
+ /* @__PURE__ */ jsx(
968
+ CheckboxField,
815
969
  {
816
- type: "button",
817
- onClick: () => {
818
- const start = booking.startDate || PRICING_TODAY;
819
- if (key) {
970
+ size: "sm",
971
+ label: "Select a date range",
972
+ className: "w-fit items-center",
973
+ checked: !oneDay,
974
+ onCheckedChange: (checked) => {
975
+ const start = booking.startDate || todayLocalYmd();
976
+ if (!checked) {
820
977
  onChange({ oneDayBooking: true, startDate: start, endDate: addDays(start, 1) });
821
- } else {
822
- const end = booking.endDate && booking.endDate > start && diffDays(start, booking.endDate) > 1 ? booking.endDate : addDays(start, 7);
823
- onChange({ oneDayBooking: false, startDate: start, endDate: end });
978
+ return;
824
979
  }
825
- },
826
- className: cn(
827
- "flex h-9 flex-1 items-center justify-center rounded-xl border text-sm font-medium transition-colors",
828
- oneDay === key ? "border-blue-400 bg-blue-50 text-blue-600 dark:bg-blue-950/40 dark:text-blue-400" : "border-border bg-card text-muted-foreground hover:bg-accent"
829
- ),
830
- children: label
831
- },
832
- String(key)
833
- )) }),
980
+ const end = booking.endDate && booking.endDate > start && diffDays(start, booking.endDate) > 1 ? booking.endDate : addDays(start, 7);
981
+ onChange({ oneDayBooking: false, startDate: start, endDate: end });
982
+ }
983
+ }
984
+ ),
834
985
  oneDay ? /* @__PURE__ */ jsx(
835
986
  DayCalendarPicker,
836
987
  {
@@ -860,17 +1011,149 @@ function DayPeriodPicker({
860
1011
  ] }) : null
861
1012
  ] });
862
1013
  }
1014
+ function HourlyTimeRangeFields({
1015
+ fromTime,
1016
+ scheduleEndTime,
1017
+ chargeBy,
1018
+ timeFormat,
1019
+ toOptions,
1020
+ minTo,
1021
+ onFromChange,
1022
+ onToChange,
1023
+ vertical = false
1024
+ }) {
1025
+ const fields = /* @__PURE__ */ jsxs(Fragment, { children: [
1026
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
1027
+ /* @__PURE__ */ jsx(ScheduleFieldLabel, { children: "From" }),
1028
+ /* @__PURE__ */ jsx(TimeField, { value: fromTime, onChange: onFromChange, timeFormat })
1029
+ ] }),
1030
+ vertical ? /* @__PURE__ */ jsx("div", { className: "h-px bg-border", "aria-hidden": true }) : null,
1031
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
1032
+ /* @__PURE__ */ jsx(ScheduleFieldLabel, { children: "To" }),
1033
+ chargeBy === "hours" ? /* @__PURE__ */ jsx(
1034
+ TimeField,
1035
+ {
1036
+ value: scheduleEndTime,
1037
+ onChange: onToChange,
1038
+ timeFormat,
1039
+ allowedTimes: toOptions,
1040
+ disabled: toOptions.length === 0,
1041
+ placeholder: toOptions.length === 0 ? "No later times" : "Select time"
1042
+ }
1043
+ ) : /* @__PURE__ */ jsx(
1044
+ TimeField,
1045
+ {
1046
+ value: scheduleEndTime,
1047
+ onChange: onToChange,
1048
+ timeFormat,
1049
+ minTime: minTo,
1050
+ disabled: !minTo,
1051
+ placeholder: minTo ? "Select time" : "No later times"
1052
+ }
1053
+ )
1054
+ ] })
1055
+ ] });
1056
+ if (vertical) {
1057
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4", children: fields });
1058
+ }
1059
+ return /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3", children: fields });
1060
+ }
863
1061
  function HourlySchedulePicker({
864
1062
  scheduleDate,
865
1063
  scheduleTime,
1064
+ scheduleEndTime = "",
866
1065
  onChange,
867
1066
  timeFormat = "24h",
868
- flat = true
1067
+ flat = true,
1068
+ chargeBy = "hours",
1069
+ splitLayout = false,
1070
+ stackLayout = false
869
1071
  }) {
870
- const [step, setStep] = useState(() => scheduleDate ? "time" : "date");
871
- useEffect(() => {
872
- if (!scheduleDate) setStep("date");
873
- }, [scheduleDate]);
1072
+ const [step, setStep] = useState("date");
1073
+ const fromTime = scheduleTime || "09:00";
1074
+ const toOptions = chargeBy === "hours" ? hourlyEndTimes(fromTime) : [];
1075
+ const minTo = chargeBy === "minutes" ? addMinutesToHm(fromTime, 1) ?? void 0 : void 0;
1076
+ const handleFromChange = (nextFrom) => {
1077
+ onChange({
1078
+ scheduleTime: nextFrom,
1079
+ scheduleEndTime: snapScheduleEndTime(nextFrom, scheduleEndTime, chargeBy)
1080
+ });
1081
+ };
1082
+ const handleDateChange = (v) => {
1083
+ const nextFrom = scheduleTime || "09:00";
1084
+ onChange({
1085
+ scheduleDate: v,
1086
+ scheduleTime: nextFrom,
1087
+ scheduleEndTime: scheduleEndTime || snapScheduleEndTime(nextFrom, "", chargeBy) || "10:00"
1088
+ });
1089
+ };
1090
+ const summary = scheduleDate && fromTime && scheduleEndTime ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between rounded-xl border border-border bg-muted px-3 py-2.5", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
1091
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: "Scheduled for" }),
1092
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-foreground", children: [
1093
+ formatDateLabel(scheduleDate),
1094
+ " \xB7 ",
1095
+ formatTimeForDisplay(fromTime, timeFormat),
1096
+ " \u2192",
1097
+ " ",
1098
+ formatTimeForDisplay(scheduleEndTime, timeFormat)
1099
+ ] })
1100
+ ] }) }) : null;
1101
+ const timeFields = /* @__PURE__ */ jsx(
1102
+ HourlyTimeRangeFields,
1103
+ {
1104
+ fromTime,
1105
+ scheduleEndTime,
1106
+ chargeBy,
1107
+ timeFormat,
1108
+ toOptions,
1109
+ minTo,
1110
+ onFromChange: handleFromChange,
1111
+ onToChange: (v) => onChange({ scheduleEndTime: v }),
1112
+ vertical: splitLayout || stackLayout
1113
+ }
1114
+ );
1115
+ if (stackLayout) {
1116
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-5", flat ? "" : "rounded-2xl border border-border bg-card p-4"), children: [
1117
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
1118
+ /* @__PURE__ */ jsx(ScheduleFieldLabel, { children: "Date" }),
1119
+ /* @__PURE__ */ jsx(
1120
+ DayCalendarPicker,
1121
+ {
1122
+ flat,
1123
+ selectedDate: scheduleDate,
1124
+ minDate: todayLocalYmd(),
1125
+ onChange: handleDateChange
1126
+ }
1127
+ )
1128
+ ] }),
1129
+ timeFields,
1130
+ summary
1131
+ ] });
1132
+ }
1133
+ if (splitLayout) {
1134
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-5", flat ? "" : "rounded-2xl border border-border bg-card p-4"), children: [
1135
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-5 sm:flex-row sm:items-start", children: [
1136
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1137
+ /* @__PURE__ */ jsx(ScheduleFieldLabel, { children: "Date" }),
1138
+ /* @__PURE__ */ jsx("div", { className: "mt-1.5", children: /* @__PURE__ */ jsx(
1139
+ DayCalendarPicker,
1140
+ {
1141
+ flat,
1142
+ selectedDate: scheduleDate,
1143
+ minDate: todayLocalYmd(),
1144
+ onChange: handleDateChange
1145
+ }
1146
+ ) })
1147
+ ] }),
1148
+ /* @__PURE__ */ jsx("div", { className: "hidden w-px shrink-0 self-stretch bg-border sm:block", "aria-hidden": true }),
1149
+ /* @__PURE__ */ jsxs("div", { className: "min-w-[9.5rem] shrink-0 sm:w-44", children: [
1150
+ /* @__PURE__ */ jsx(ScheduleFieldLabel, { children: "Time" }),
1151
+ /* @__PURE__ */ jsx("div", { className: "mt-1.5", children: timeFields })
1152
+ ] })
1153
+ ] }),
1154
+ summary
1155
+ ] });
1156
+ }
874
1157
  return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-5", flat ? "" : "rounded-2xl border border-border bg-card p-4"), children: [
875
1158
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground", children: [
876
1159
  /* @__PURE__ */ jsx(
@@ -889,7 +1172,7 @@ function HourlySchedulePicker({
889
1172
  {
890
1173
  className: cn(
891
1174
  step === "time" && "font-semibold text-blue-600 dark:text-blue-400",
892
- !scheduleDate && "text-muted-foreground/60"
1175
+ step !== "time" && "text-muted-foreground/60"
893
1176
  ),
894
1177
  children: "Time"
895
1178
  }
@@ -902,9 +1185,9 @@ function HourlySchedulePicker({
902
1185
  {
903
1186
  flat,
904
1187
  selectedDate: scheduleDate,
905
- minDate: PRICING_TODAY,
1188
+ minDate: todayLocalYmd(),
906
1189
  onChange: (v) => {
907
- onChange({ scheduleDate: v });
1190
+ handleDateChange(v);
908
1191
  setStep("time");
909
1192
  }
910
1193
  }
@@ -927,29 +1210,110 @@ function HourlySchedulePicker({
927
1210
  ]
928
1211
  }
929
1212
  ),
1213
+ timeFields,
1214
+ summary
1215
+ ] })
1216
+ ] });
1217
+ }
1218
+ function ServiceNeededAtPicker({
1219
+ scheduleDate,
1220
+ scheduleTime,
1221
+ onChange,
1222
+ timeFormat = "24h",
1223
+ flat = true,
1224
+ stackLayout = true,
1225
+ minDate = todayLocalYmd(),
1226
+ preparationMinutes = 0
1227
+ }) {
1228
+ const neededTime = scheduleTime || "09:00";
1229
+ const todayYmd = todayLocalYmd();
1230
+ const minTimeToday = preparationMinutes > 0 && scheduleDate === todayYmd ? minNeededTimeHm(preparationMinutes) : void 0;
1231
+ const syncNeeded = (date, time) => {
1232
+ let nextTime = time;
1233
+ if (preparationMinutes > 0 && date === todayYmd) {
1234
+ const floor = minNeededTimeHm(preparationMinutes);
1235
+ if (floor && nextTime < floor) nextTime = floor;
1236
+ }
1237
+ onChange({
1238
+ scheduleDate: date,
1239
+ scheduleTime: nextTime,
1240
+ scheduleEndTime: nextTime,
1241
+ startDate: date,
1242
+ endDate: date
1243
+ });
1244
+ };
1245
+ const handleDateChange = (v) => syncNeeded(v, neededTime);
1246
+ const handleTimeChange = (v) => {
1247
+ if (!scheduleDate) return;
1248
+ syncNeeded(scheduleDate, v);
1249
+ };
1250
+ const summary = scheduleDate && neededTime ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between rounded-xl border border-border bg-muted px-3 py-2.5", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
1251
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: "Needed at" }),
1252
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-foreground", children: [
1253
+ formatDateLabel(scheduleDate),
1254
+ " \xB7 ",
1255
+ formatTimeForDisplay(neededTime, timeFormat)
1256
+ ] })
1257
+ ] }) }) : null;
1258
+ const timeField = /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
1259
+ /* @__PURE__ */ jsx(ScheduleFieldLabel, { children: "Needed at" }),
1260
+ /* @__PURE__ */ jsx(
1261
+ TimeField,
1262
+ {
1263
+ value: neededTime,
1264
+ onChange: handleTimeChange,
1265
+ timeFormat,
1266
+ minTime: minTimeToday,
1267
+ disabled: Boolean(minTimeToday && !minTimeToday),
1268
+ placeholder: minTimeToday ? "Select time" : void 0
1269
+ }
1270
+ )
1271
+ ] });
1272
+ if (stackLayout) {
1273
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-5", flat ? "" : "rounded-2xl border border-border bg-card p-4"), children: [
930
1274
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
931
- /* @__PURE__ */ jsx(ScheduleFieldLabel, { children: "Pick a time" }),
932
- /* @__PURE__ */ jsx("div", { className: "flex justify-center rounded-2xl border border-border bg-card py-6", children: /* @__PURE__ */ jsx(
933
- AnalogClockPicker,
1275
+ /* @__PURE__ */ jsx(ScheduleFieldLabel, { children: "Date" }),
1276
+ /* @__PURE__ */ jsx(
1277
+ DayCalendarPicker,
934
1278
  {
935
- value: scheduleTime,
936
- onChange: (v) => onChange({ scheduleTime: v }),
937
- timeFormat
1279
+ flat,
1280
+ selectedDate: scheduleDate,
1281
+ minDate,
1282
+ onChange: handleDateChange
938
1283
  }
939
- ) })
1284
+ )
940
1285
  ] }),
941
- scheduleDate && scheduleTime ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between rounded-xl border border-border bg-muted px-3 py-2.5", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
942
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: "Scheduled for" }),
943
- /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-foreground", children: [
944
- formatDateLabel(scheduleDate),
945
- " \xB7 ",
946
- formatTimeForDisplay(scheduleTime, timeFormat)
947
- ] })
948
- ] }) }) : null
949
- ] })
1286
+ timeField,
1287
+ summary
1288
+ ] });
1289
+ }
1290
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-5 sm:flex-row sm:items-start", flat ? "" : "rounded-2xl border border-border bg-card p-4"), children: [
1291
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1292
+ /* @__PURE__ */ jsx(ScheduleFieldLabel, { children: "Date" }),
1293
+ /* @__PURE__ */ jsx("div", { className: "mt-1.5", children: /* @__PURE__ */ jsx(
1294
+ DayCalendarPicker,
1295
+ {
1296
+ flat,
1297
+ selectedDate: scheduleDate,
1298
+ minDate,
1299
+ onChange: handleDateChange
1300
+ }
1301
+ ) })
1302
+ ] }),
1303
+ /* @__PURE__ */ jsx("div", { className: "hidden w-px shrink-0 self-stretch bg-border sm:block", "aria-hidden": true }),
1304
+ /* @__PURE__ */ jsx("div", { className: "min-w-[9.5rem] shrink-0 sm:w-44", children: timeField }),
1305
+ summary ? /* @__PURE__ */ jsx("div", { className: "w-full", children: summary }) : null
950
1306
  ] });
951
1307
  }
1308
+ function minNeededTimeHm(prepMinutes, now = /* @__PURE__ */ new Date()) {
1309
+ const earliest = new Date(now.getTime() + prepMinutes * 6e4);
1310
+ if (earliest.getSeconds() > 0 || earliest.getMilliseconds() > 0) {
1311
+ earliest.setMinutes(earliest.getMinutes() + 1);
1312
+ }
1313
+ earliest.setSeconds(0, 0);
1314
+ return `${String(earliest.getHours()).padStart(2, "0")}:${String(earliest.getMinutes()).padStart(2, "0")}`;
1315
+ }
952
1316
 
953
- export { DayCalendarPicker, DayPeriodPicker, DayRangeCalendar, HourlySchedulePicker, MonthPeriodPicker, MonthRangeCalendar, ScheduleFieldLabel, ScheduleInfoBanner, ScheduleStepper, WeekCalendarPicker, WeekPeriodPicker };
1317
+ export { DayCalendarPicker, DayPeriodPicker, DayRangeCalendar, HourlySchedulePicker, MonthPeriodPicker, MonthRangeCalendar, ScheduleFieldLabel, ScheduleInfoBanner, ScheduleStepper, ServiceNeededAtPicker, WeekCalendarPicker, WeekPeriodPicker };
954
1318
  //# sourceMappingURL=booking-schedule-calendars.js.map
955
1319
  //# sourceMappingURL=booking-schedule-calendars.js.map