@spacesync/client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/dist/analog-clock-picker.d.ts +10 -0
- package/dist/analog-clock-picker.js +408 -0
- package/dist/analog-clock-picker.js.map +1 -0
- package/dist/api.d.ts +222 -0
- package/dist/api.js +167 -0
- package/dist/api.js.map +1 -0
- package/dist/app-modal.d.ts +25 -0
- package/dist/app-modal.js +145 -0
- package/dist/app-modal.js.map +1 -0
- package/dist/app-shell-mobile-nav.d.ts +29 -0
- package/dist/app-shell-mobile-nav.js +122 -0
- package/dist/app-shell-mobile-nav.js.map +1 -0
- package/dist/app-web-shell.d.ts +20 -0
- package/dist/app-web-shell.js +45 -0
- package/dist/app-web-shell.js.map +1 -0
- package/dist/booking-schedule-calendars.d.ts +95 -0
- package/dist/booking-schedule-calendars.js +1343 -0
- package/dist/booking-schedule-calendars.js.map +1 -0
- package/dist/chat-stick-to-bottom.d.ts +22 -0
- package/dist/chat-stick-to-bottom.js +190 -0
- package/dist/chat-stick-to-bottom.js.map +1 -0
- package/dist/detail-page-header.d.ts +16 -0
- package/dist/detail-page-header.js +220 -0
- package/dist/detail-page-header.js.map +1 -0
- package/dist/dialog-CIdvylPK.d.ts +16 -0
- package/dist/errors-D1niseaa.d.ts +34 -0
- package/dist/file-picker-field.d.ts +23 -0
- package/dist/file-picker-field.js +165 -0
- package/dist/file-picker-field.js.map +1 -0
- package/dist/file-viewer-dialog.d.ts +15 -0
- package/dist/file-viewer-dialog.js +387 -0
- package/dist/file-viewer-dialog.js.map +1 -0
- package/dist/floor-switcher.d.ts +14 -0
- package/dist/floor-switcher.js +416 -0
- package/dist/floor-switcher.js.map +1 -0
- package/dist/forms.d.ts +52 -0
- package/dist/forms.js +66 -0
- package/dist/forms.js.map +1 -0
- package/dist/image-picker-field.d.ts +11 -0
- package/dist/image-picker-field.js +135 -0
- package/dist/image-picker-field.js.map +1 -0
- package/dist/layout-canvas-view.d.ts +57 -0
- package/dist/layout-canvas-view.js +974 -0
- package/dist/layout-canvas-view.js.map +1 -0
- package/dist/lib/dateDisplay.d.ts +24 -0
- package/dist/lib/dateDisplay.js +84 -0
- package/dist/lib/dateDisplay.js.map +1 -0
- package/dist/live-resource-meta.d.ts +11 -0
- package/dist/live-resource-meta.js +27 -0
- package/dist/live-resource-meta.js.map +1 -0
- package/dist/live-space-pulse-dot.d.ts +8 -0
- package/dist/live-space-pulse-dot.js +18 -0
- package/dist/live-space-pulse-dot.js.map +1 -0
- package/dist/notification-sidebar-preview.d.ts +37 -0
- package/dist/notification-sidebar-preview.js +121 -0
- package/dist/notification-sidebar-preview.js.map +1 -0
- package/dist/page-list-chrome.d.ts +124 -0
- package/dist/page-list-chrome.js +970 -0
- package/dist/page-list-chrome.js.map +1 -0
- package/dist/platform-support-message-composer.d.ts +38 -0
- package/dist/platform-support-message-composer.js +446 -0
- package/dist/platform-support-message-composer.js.map +1 -0
- package/dist/shell-sidebar-nav.d.ts +16 -0
- package/dist/shell-sidebar-nav.js +69 -0
- package/dist/shell-sidebar-nav.js.map +1 -0
- package/dist/shell-theme.d.ts +6 -0
- package/dist/shell-theme.js +9 -0
- package/dist/shell-theme.js.map +1 -0
- package/dist/shell-top-bar-chrome.d.ts +39 -0
- package/dist/shell-top-bar-chrome.js +105 -0
- package/dist/shell-top-bar-chrome.js.map +1 -0
- package/dist/shell-top-bar.d.ts +16 -0
- package/dist/shell-top-bar.js +71 -0
- package/dist/shell-top-bar.js.map +1 -0
- package/dist/styles/theme.css +223 -0
- package/dist/styles/theme.css.map +1 -0
- package/dist/styles/theme.d.ts +2 -0
- package/dist/support-presence-status.d.ts +11 -0
- package/dist/support-presence-status.js +92 -0
- package/dist/support-presence-status.js.map +1 -0
- package/dist/theme-appearance-control.d.ts +5 -0
- package/dist/theme-appearance-control.js +114 -0
- package/dist/theme-appearance-control.js.map +1 -0
- package/dist/theme-boot-script.d.ts +4 -0
- package/dist/theme-boot-script.js +9 -0
- package/dist/theme-boot-script.js.map +1 -0
- package/dist/theme-provider.d.ts +9 -0
- package/dist/theme-provider.js +25 -0
- package/dist/theme-provider.js.map +1 -0
- package/dist/ui.d.ts +765 -0
- package/dist/ui.js +5637 -0
- package/dist/ui.js.map +1 -0
- package/dist/use-client-pagination.d.ts +22 -0
- package/dist/use-client-pagination.js +51 -0
- package/dist/use-client-pagination.js.map +1 -0
- package/dist/use-client-sort.d.ts +20 -0
- package/dist/use-client-sort.js +41 -0
- package/dist/use-client-sort.js.map +1 -0
- package/package.json +208 -0
|
@@ -0,0 +1,1343 @@
|
|
|
1
|
+
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
|
2
|
+
import { Info, ChevronLeft, ChevronRight } from 'lucide-react';
|
|
3
|
+
import { clsx } from 'clsx';
|
|
4
|
+
import { twMerge } from 'tailwind-merge';
|
|
5
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
// src/booking/booking-schedule-calendars.tsx
|
|
8
|
+
|
|
9
|
+
// src/lib/booking-schedule.ts
|
|
10
|
+
var PRICING_TODAY = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
11
|
+
function computeMinMonth() {
|
|
12
|
+
const d = /* @__PURE__ */ new Date();
|
|
13
|
+
if (d.getDate() === 1) {
|
|
14
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
|
|
15
|
+
}
|
|
16
|
+
const n = new Date(d.getFullYear(), d.getMonth() + 1, 1);
|
|
17
|
+
return `${n.getFullYear()}-${String(n.getMonth() + 1).padStart(2, "0")}`;
|
|
18
|
+
}
|
|
19
|
+
function computeMinWeekStart() {
|
|
20
|
+
const d = /* @__PURE__ */ new Date();
|
|
21
|
+
const day = d.getDay();
|
|
22
|
+
if (day === 1) return d.toISOString().split("T")[0];
|
|
23
|
+
const skip = day === 0 ? 1 : 8 - day;
|
|
24
|
+
d.setDate(d.getDate() + skip);
|
|
25
|
+
return d.toISOString().split("T")[0];
|
|
26
|
+
}
|
|
27
|
+
var MIN_MONTH = computeMinMonth() || `${(/* @__PURE__ */ new Date()).getFullYear()}-${String((/* @__PURE__ */ new Date()).getMonth() + 2).padStart(2, "0")}`;
|
|
28
|
+
var MIN_WEEK_START = computeMinWeekStart() || PRICING_TODAY;
|
|
29
|
+
var IS_PARTIAL_MONTH = (/* @__PURE__ */ new Date()).getDate() !== 1;
|
|
30
|
+
function monthLabel(ym) {
|
|
31
|
+
const [y, m] = ym.split("-").map(Number);
|
|
32
|
+
return new Date(y, m - 1, 1).toLocaleDateString("en-US", { month: "long", year: "numeric" });
|
|
33
|
+
}
|
|
34
|
+
function monthCount(from, to) {
|
|
35
|
+
if (!from || !to || !from.includes("-") || !to.includes("-")) return 0;
|
|
36
|
+
const [fy, fm] = from.split("-").map(Number);
|
|
37
|
+
const [ty, tm] = to.split("-").map(Number);
|
|
38
|
+
return (ty - fy) * 12 + (tm - fm) + 1;
|
|
39
|
+
}
|
|
40
|
+
function weekRangeLabel(weekFrom, weekCount) {
|
|
41
|
+
if (!weekFrom || isNaN(new Date(weekFrom).getTime())) return "\u2014";
|
|
42
|
+
const end = new Date(weekFrom);
|
|
43
|
+
end.setDate(end.getDate() + weekCount * 7 - 1);
|
|
44
|
+
const s = new Date(weekFrom).toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
45
|
+
const e = end.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
|
|
46
|
+
return `${s} \u2013 ${e}`;
|
|
47
|
+
}
|
|
48
|
+
function parseDateOnly(date) {
|
|
49
|
+
const [y, m, d] = date.split("-").map(Number);
|
|
50
|
+
return new Date(y, m - 1, d);
|
|
51
|
+
}
|
|
52
|
+
function formatDateOnly(d) {
|
|
53
|
+
const y = d.getFullYear();
|
|
54
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
55
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
56
|
+
return `${y}-${m}-${day}`;
|
|
57
|
+
}
|
|
58
|
+
function diffDays(a, b) {
|
|
59
|
+
const da = parseDateOnly(a);
|
|
60
|
+
const db = parseDateOnly(b);
|
|
61
|
+
return Math.max(0, Math.round((db.getTime() - da.getTime()) / 864e5));
|
|
62
|
+
}
|
|
63
|
+
function addDays(date, days) {
|
|
64
|
+
const d = parseDateOnly(date);
|
|
65
|
+
d.setDate(d.getDate() + days);
|
|
66
|
+
return formatDateOnly(d);
|
|
67
|
+
}
|
|
68
|
+
function cn(...inputs) {
|
|
69
|
+
return twMerge(clsx(inputs));
|
|
70
|
+
}
|
|
71
|
+
function parseHm(value) {
|
|
72
|
+
const m = /^([01]\d|2[0-3]):([0-5]\d)$/.exec(value.trim());
|
|
73
|
+
if (!m) return void 0;
|
|
74
|
+
const h = Number(m[1]);
|
|
75
|
+
const min = Number(m[2]);
|
|
76
|
+
if (h > 23 || min > 59) return void 0;
|
|
77
|
+
return { h, m: min };
|
|
78
|
+
}
|
|
79
|
+
function toTimeStr(h, m) {
|
|
80
|
+
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
|
|
81
|
+
}
|
|
82
|
+
function formatTimeForDisplay(value, timeFormat) {
|
|
83
|
+
const p = parseHm(value);
|
|
84
|
+
if (!p) return value.trim() || "\u2014";
|
|
85
|
+
if (timeFormat === "24h") return toTimeStr(p.h, p.m);
|
|
86
|
+
const ap = p.h >= 12 ? "pm" : "am";
|
|
87
|
+
const h12 = p.h % 12 || 12;
|
|
88
|
+
return `${String(h12).padStart(2, "0")}:${String(p.m).padStart(2, "0")} ${ap}`;
|
|
89
|
+
}
|
|
90
|
+
Array.from({ length: 24 }, (_, i) => String(i).padStart(2, "0"));
|
|
91
|
+
Array.from({ length: 60 }, (_, i) => String(i).padStart(2, "0"));
|
|
92
|
+
Array.from({ length: 12 }, (_, i) => String(i + 1));
|
|
93
|
+
var SIZE = 248;
|
|
94
|
+
var CX = SIZE / 2;
|
|
95
|
+
var CY = SIZE / 2;
|
|
96
|
+
var FACE_R = 98;
|
|
97
|
+
var HOUR_LABEL_R = 72;
|
|
98
|
+
var MINUTE_TICK_R = 84;
|
|
99
|
+
function pointToAngleDeg(x, y) {
|
|
100
|
+
const dx = x - CX;
|
|
101
|
+
const dy = y - CY;
|
|
102
|
+
let deg = Math.atan2(dy, dx) * 180 / Math.PI + 90;
|
|
103
|
+
if (deg < 0) deg += 360;
|
|
104
|
+
return deg;
|
|
105
|
+
}
|
|
106
|
+
function angleToHour12(deg) {
|
|
107
|
+
const h = Math.round(deg / 30) % 12;
|
|
108
|
+
return h === 0 ? 12 : h;
|
|
109
|
+
}
|
|
110
|
+
function angleToHour24(deg) {
|
|
111
|
+
return Math.round(deg / 15) % 24;
|
|
112
|
+
}
|
|
113
|
+
function angleToMinute(deg) {
|
|
114
|
+
return Math.round(deg / 6) % 60;
|
|
115
|
+
}
|
|
116
|
+
function hour12To24(h12, ap) {
|
|
117
|
+
if (ap === "am") return h12 === 12 ? 0 : h12;
|
|
118
|
+
return h12 === 12 ? 12 : h12 + 12;
|
|
119
|
+
}
|
|
120
|
+
function hour24To12(h24) {
|
|
121
|
+
return { h12: h24 % 12 || 12, ap: h24 >= 12 ? "pm" : "am" };
|
|
122
|
+
}
|
|
123
|
+
function handEnd(deg, length) {
|
|
124
|
+
const rad = (deg - 90) * Math.PI / 180;
|
|
125
|
+
return { x: CX + Math.cos(rad) * length, y: CY + Math.sin(rad) * length };
|
|
126
|
+
}
|
|
127
|
+
function hourCenterDeg(h, timeFormat) {
|
|
128
|
+
if (timeFormat === "24h") return h * 15;
|
|
129
|
+
return h % 12 * 30;
|
|
130
|
+
}
|
|
131
|
+
function sectorPath(centerDeg, spread, radius) {
|
|
132
|
+
const start = handEnd(centerDeg - spread, radius);
|
|
133
|
+
const end = handEnd(centerDeg + spread, radius);
|
|
134
|
+
return `M ${CX} ${CY} L ${start.x} ${start.y} A ${radius} ${radius} 0 0 1 ${end.x} ${end.y} Z`;
|
|
135
|
+
}
|
|
136
|
+
function pointerTarget(clientX, clientY, svg, phase, timeFormat) {
|
|
137
|
+
const rect = svg.getBoundingClientRect();
|
|
138
|
+
const scale = SIZE / rect.width;
|
|
139
|
+
const x = (clientX - rect.left) * scale;
|
|
140
|
+
const y = (clientY - rect.top) * scale;
|
|
141
|
+
const dist = Math.hypot(x - CX, y - CY);
|
|
142
|
+
if (dist < 24 || dist > FACE_R + 8) return null;
|
|
143
|
+
const deg = pointToAngleDeg(x, y);
|
|
144
|
+
if (phase === "hour") {
|
|
145
|
+
return timeFormat === "24h" ? angleToHour24(deg) : angleToHour12(deg);
|
|
146
|
+
}
|
|
147
|
+
return angleToMinute(deg);
|
|
148
|
+
}
|
|
149
|
+
function AnalogClockPicker({
|
|
150
|
+
value,
|
|
151
|
+
onChange,
|
|
152
|
+
timeFormat = "24h",
|
|
153
|
+
className
|
|
154
|
+
}) {
|
|
155
|
+
const svgRef = useRef(null);
|
|
156
|
+
const parsed = parseHm(value);
|
|
157
|
+
const editor = parsed ?? { h: 9, m: 0 };
|
|
158
|
+
const { h12, ap } = hour24To12(editor.h);
|
|
159
|
+
const [phase, setPhase] = useState("hour");
|
|
160
|
+
const [hoverTarget, setHoverTarget] = useState(null);
|
|
161
|
+
const setTime = useCallback(
|
|
162
|
+
(h, m) => {
|
|
163
|
+
onChange(toTimeStr(h, m));
|
|
164
|
+
},
|
|
165
|
+
[onChange]
|
|
166
|
+
);
|
|
167
|
+
const handlePointer = useCallback(
|
|
168
|
+
(clientX, clientY) => {
|
|
169
|
+
const svg = svgRef.current;
|
|
170
|
+
if (!svg) return;
|
|
171
|
+
const rect = svg.getBoundingClientRect();
|
|
172
|
+
const scale = SIZE / rect.width;
|
|
173
|
+
const x = (clientX - rect.left) * scale;
|
|
174
|
+
const y = (clientY - rect.top) * scale;
|
|
175
|
+
const deg = pointToAngleDeg(x, y);
|
|
176
|
+
if (phase === "hour") {
|
|
177
|
+
if (timeFormat === "24h") {
|
|
178
|
+
const h24 = angleToHour24(deg);
|
|
179
|
+
setTime(h24, editor.m);
|
|
180
|
+
} else {
|
|
181
|
+
const h = angleToHour12(deg);
|
|
182
|
+
setTime(hour12To24(h, ap), editor.m);
|
|
183
|
+
}
|
|
184
|
+
setPhase("minute");
|
|
185
|
+
} else {
|
|
186
|
+
const m = angleToMinute(deg);
|
|
187
|
+
setTime(editor.h, m);
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
[phase, timeFormat, ap, editor.h, editor.m, setTime]
|
|
191
|
+
);
|
|
192
|
+
const updateHover = useCallback(
|
|
193
|
+
(clientX, clientY) => {
|
|
194
|
+
const svg = svgRef.current;
|
|
195
|
+
if (!svg) return;
|
|
196
|
+
setHoverTarget(pointerTarget(clientX, clientY, svg, phase, timeFormat));
|
|
197
|
+
},
|
|
198
|
+
[phase, timeFormat]
|
|
199
|
+
);
|
|
200
|
+
const previewHour = phase === "hour" && hoverTarget != null ? timeFormat === "24h" ? hoverTarget : hour12To24(hoverTarget, ap) : editor.h;
|
|
201
|
+
const previewMinute = phase === "minute" && hoverTarget != null ? hoverTarget : editor.m;
|
|
202
|
+
const hourHandDeg = previewHour % 12 * 30 + previewMinute * 0.5;
|
|
203
|
+
const minuteHandDeg = previewMinute * 6;
|
|
204
|
+
const hourEnd = handEnd(hourHandDeg, 46);
|
|
205
|
+
const minuteEnd = handEnd(minuteHandDeg, 64);
|
|
206
|
+
const selectedHourKey = timeFormat === "24h" ? editor.h : h12;
|
|
207
|
+
const hoveredHourKey = phase === "hour" ? hoverTarget : null;
|
|
208
|
+
const hourMarkers = useMemo(() => {
|
|
209
|
+
if (timeFormat === "24h") {
|
|
210
|
+
return Array.from({ length: 24 }, (_, h) => {
|
|
211
|
+
const deg = h * 15;
|
|
212
|
+
const rad = (deg - 90) * Math.PI / 180;
|
|
213
|
+
const showLabel = h % 3 === 0;
|
|
214
|
+
const r = showLabel ? HOUR_LABEL_R - 6 : HOUR_LABEL_R + 4;
|
|
215
|
+
return {
|
|
216
|
+
key: h,
|
|
217
|
+
label: showLabel ? String(h).padStart(2, "0") : null,
|
|
218
|
+
x: CX + Math.cos(rad) * r,
|
|
219
|
+
y: CY + Math.sin(rad) * r,
|
|
220
|
+
deg,
|
|
221
|
+
h
|
|
222
|
+
};
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return Array.from({ length: 12 }, (_, i) => {
|
|
226
|
+
const h = i === 0 ? 12 : i;
|
|
227
|
+
const deg = h * 30;
|
|
228
|
+
const rad = (deg - 90) * Math.PI / 180;
|
|
229
|
+
return {
|
|
230
|
+
key: h,
|
|
231
|
+
label: String(h),
|
|
232
|
+
x: CX + Math.cos(rad) * HOUR_LABEL_R,
|
|
233
|
+
y: CY + Math.sin(rad) * HOUR_LABEL_R,
|
|
234
|
+
deg,
|
|
235
|
+
h
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
}, [timeFormat]);
|
|
239
|
+
const minuteMarkers = useMemo(
|
|
240
|
+
() => Array.from({ length: 12 }, (_, i) => {
|
|
241
|
+
const m = i * 5;
|
|
242
|
+
const deg = m * 6;
|
|
243
|
+
const rad = (deg - 90) * Math.PI / 180;
|
|
244
|
+
const selected = editor.m === m;
|
|
245
|
+
const hovered = hoverTarget != null && Math.round(hoverTarget / 5) * 5 === m;
|
|
246
|
+
return {
|
|
247
|
+
m,
|
|
248
|
+
x: CX + Math.cos(rad) * MINUTE_TICK_R,
|
|
249
|
+
y: CY + Math.sin(rad) * MINUTE_TICK_R,
|
|
250
|
+
selected,
|
|
251
|
+
hovered
|
|
252
|
+
};
|
|
253
|
+
}),
|
|
254
|
+
[editor.m, hoverTarget]
|
|
255
|
+
);
|
|
256
|
+
const displayTime = formatTimeForDisplay(value || toTimeStr(editor.h, editor.m), timeFormat);
|
|
257
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center gap-4", className), children: [
|
|
258
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 rounded-2xl border border-border bg-muted/60 px-4 py-2.5", children: [
|
|
259
|
+
/* @__PURE__ */ jsx(
|
|
260
|
+
"button",
|
|
261
|
+
{
|
|
262
|
+
type: "button",
|
|
263
|
+
onClick: () => setPhase("hour"),
|
|
264
|
+
className: cn(
|
|
265
|
+
"rounded-lg px-3 py-1 text-2xl font-semibold tabular-nums transition-colors",
|
|
266
|
+
phase === "hour" ? "bg-blue-500 text-white shadow-sm" : "text-foreground hover:bg-accent"
|
|
267
|
+
),
|
|
268
|
+
children: timeFormat === "24h" ? String(editor.h).padStart(2, "0") : String(h12).padStart(2, "0")
|
|
269
|
+
}
|
|
270
|
+
),
|
|
271
|
+
/* @__PURE__ */ jsx("span", { className: "text-2xl font-semibold text-muted-foreground", children: ":" }),
|
|
272
|
+
/* @__PURE__ */ jsx(
|
|
273
|
+
"button",
|
|
274
|
+
{
|
|
275
|
+
type: "button",
|
|
276
|
+
onClick: () => setPhase("minute"),
|
|
277
|
+
className: cn(
|
|
278
|
+
"rounded-lg px-3 py-1 text-2xl font-semibold tabular-nums transition-colors",
|
|
279
|
+
phase === "minute" ? "bg-blue-500 text-white shadow-sm" : "text-foreground hover:bg-accent"
|
|
280
|
+
),
|
|
281
|
+
children: String(editor.m).padStart(2, "0")
|
|
282
|
+
}
|
|
283
|
+
),
|
|
284
|
+
timeFormat === "12h" ? /* @__PURE__ */ jsx("div", { className: "ml-2 flex flex-col gap-0.5", children: ["am", "pm"].map((p) => /* @__PURE__ */ jsx(
|
|
285
|
+
"button",
|
|
286
|
+
{
|
|
287
|
+
type: "button",
|
|
288
|
+
onClick: () => setTime(hour12To24(h12, p), editor.m),
|
|
289
|
+
className: cn(
|
|
290
|
+
"rounded-md px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide transition-colors",
|
|
291
|
+
ap === p ? "bg-blue-100 text-blue-700 dark:bg-blue-950/50 dark:text-blue-300" : "text-muted-foreground hover:bg-accent"
|
|
292
|
+
),
|
|
293
|
+
children: p
|
|
294
|
+
},
|
|
295
|
+
p
|
|
296
|
+
)) }) : null
|
|
297
|
+
] }),
|
|
298
|
+
/* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */ jsxs(
|
|
299
|
+
"svg",
|
|
300
|
+
{
|
|
301
|
+
ref: svgRef,
|
|
302
|
+
width: SIZE,
|
|
303
|
+
height: SIZE,
|
|
304
|
+
viewBox: `0 0 ${SIZE} ${SIZE}`,
|
|
305
|
+
className: "cursor-pointer touch-none select-none",
|
|
306
|
+
onClick: (e) => handlePointer(e.clientX, e.clientY),
|
|
307
|
+
onPointerMove: (e) => updateHover(e.clientX, e.clientY),
|
|
308
|
+
onPointerLeave: () => setHoverTarget(null),
|
|
309
|
+
onPointerDown: (e) => {
|
|
310
|
+
e.preventDefault();
|
|
311
|
+
handlePointer(e.clientX, e.clientY);
|
|
312
|
+
},
|
|
313
|
+
children: [
|
|
314
|
+
/* @__PURE__ */ jsx("circle", { cx: CX, cy: CY, r: FACE_R, className: "fill-blue-50/80 dark:fill-blue-950/30" }),
|
|
315
|
+
/* @__PURE__ */ jsx(
|
|
316
|
+
"circle",
|
|
317
|
+
{
|
|
318
|
+
cx: CX,
|
|
319
|
+
cy: CY,
|
|
320
|
+
r: FACE_R,
|
|
321
|
+
className: "fill-none stroke-border",
|
|
322
|
+
strokeWidth: 1.5
|
|
323
|
+
}
|
|
324
|
+
),
|
|
325
|
+
phase === "hour" && hoverTarget != null ? /* @__PURE__ */ jsx(
|
|
326
|
+
"path",
|
|
327
|
+
{
|
|
328
|
+
d: sectorPath(
|
|
329
|
+
hourCenterDeg(hoverTarget, timeFormat),
|
|
330
|
+
timeFormat === "24h" ? 7.5 : 15,
|
|
331
|
+
FACE_R - 2
|
|
332
|
+
),
|
|
333
|
+
className: "fill-blue-200/70 transition-opacity dark:fill-blue-500/25"
|
|
334
|
+
}
|
|
335
|
+
) : null,
|
|
336
|
+
phase === "minute" && hoverTarget != null ? /* @__PURE__ */ jsx(
|
|
337
|
+
"path",
|
|
338
|
+
{
|
|
339
|
+
d: sectorPath(hoverTarget * 6, 3, FACE_R - 2),
|
|
340
|
+
className: "fill-blue-200/70 transition-opacity dark:fill-blue-500/25"
|
|
341
|
+
}
|
|
342
|
+
) : null,
|
|
343
|
+
phase === "hour" ? hourMarkers.map((m) => {
|
|
344
|
+
const isSelected = m.h === selectedHourKey || timeFormat === "12h" && m.h === h12;
|
|
345
|
+
const isHovered = m.h === hoveredHourKey;
|
|
346
|
+
const active = isHovered || isSelected && hoveredHourKey == null;
|
|
347
|
+
return /* @__PURE__ */ jsxs("g", { children: [
|
|
348
|
+
active ? /* @__PURE__ */ jsx(
|
|
349
|
+
"circle",
|
|
350
|
+
{
|
|
351
|
+
cx: m.x,
|
|
352
|
+
cy: m.y,
|
|
353
|
+
r: isHovered ? 18 : 16,
|
|
354
|
+
className: cn(
|
|
355
|
+
"transition-all duration-100",
|
|
356
|
+
isHovered ? "fill-blue-500 shadow-sm" : "fill-blue-400/90"
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
) : null,
|
|
360
|
+
m.label ? /* @__PURE__ */ jsx(
|
|
361
|
+
"text",
|
|
362
|
+
{
|
|
363
|
+
x: m.x,
|
|
364
|
+
y: m.y,
|
|
365
|
+
textAnchor: "middle",
|
|
366
|
+
dominantBaseline: "central",
|
|
367
|
+
className: cn(
|
|
368
|
+
"pointer-events-none text-[11px] font-semibold transition-colors duration-100",
|
|
369
|
+
active ? "fill-white" : "fill-muted-foreground",
|
|
370
|
+
isHovered && "text-[12px] font-bold"
|
|
371
|
+
),
|
|
372
|
+
children: m.label
|
|
373
|
+
}
|
|
374
|
+
) : /* @__PURE__ */ jsx(
|
|
375
|
+
"circle",
|
|
376
|
+
{
|
|
377
|
+
cx: m.x,
|
|
378
|
+
cy: m.y,
|
|
379
|
+
r: isHovered ? 5 : 2,
|
|
380
|
+
className: cn(
|
|
381
|
+
"pointer-events-none transition-all duration-100",
|
|
382
|
+
isHovered ? "fill-white" : "fill-muted-foreground/40"
|
|
383
|
+
)
|
|
384
|
+
}
|
|
385
|
+
)
|
|
386
|
+
] }, m.key);
|
|
387
|
+
}) : minuteMarkers.map((m) => {
|
|
388
|
+
const active = m.selected || m.hovered;
|
|
389
|
+
return /* @__PURE__ */ jsxs("g", { children: [
|
|
390
|
+
/* @__PURE__ */ jsx(
|
|
391
|
+
"circle",
|
|
392
|
+
{
|
|
393
|
+
cx: m.x,
|
|
394
|
+
cy: m.y,
|
|
395
|
+
r: m.hovered ? 16 : m.selected ? 14 : 4,
|
|
396
|
+
className: cn(
|
|
397
|
+
"transition-all duration-100",
|
|
398
|
+
m.hovered ? "fill-blue-400" : m.selected ? "fill-blue-500" : "fill-blue-200/80 dark:fill-blue-800/60"
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
),
|
|
402
|
+
active || m.m % 15 === 0 ? /* @__PURE__ */ jsx(
|
|
403
|
+
"text",
|
|
404
|
+
{
|
|
405
|
+
x: m.x,
|
|
406
|
+
y: m.y,
|
|
407
|
+
textAnchor: "middle",
|
|
408
|
+
dominantBaseline: "central",
|
|
409
|
+
className: cn(
|
|
410
|
+
"pointer-events-none text-[10px] font-semibold transition-colors duration-100",
|
|
411
|
+
active ? "fill-white" : "fill-blue-700 dark:fill-blue-300"
|
|
412
|
+
),
|
|
413
|
+
children: String(m.m).padStart(2, "0")
|
|
414
|
+
}
|
|
415
|
+
) : null
|
|
416
|
+
] }, m.m);
|
|
417
|
+
}),
|
|
418
|
+
/* @__PURE__ */ jsx(
|
|
419
|
+
"line",
|
|
420
|
+
{
|
|
421
|
+
x1: CX,
|
|
422
|
+
y1: CY,
|
|
423
|
+
x2: hourEnd.x,
|
|
424
|
+
y2: hourEnd.y,
|
|
425
|
+
className: cn(
|
|
426
|
+
"stroke-blue-600 transition-all duration-100 dark:stroke-blue-400",
|
|
427
|
+
phase === "hour" && hoverTarget != null && "stroke-blue-500 opacity-90"
|
|
428
|
+
),
|
|
429
|
+
strokeWidth: 3.5,
|
|
430
|
+
strokeLinecap: "round"
|
|
431
|
+
}
|
|
432
|
+
),
|
|
433
|
+
/* @__PURE__ */ jsx(
|
|
434
|
+
"line",
|
|
435
|
+
{
|
|
436
|
+
x1: CX,
|
|
437
|
+
y1: CY,
|
|
438
|
+
x2: minuteEnd.x,
|
|
439
|
+
y2: minuteEnd.y,
|
|
440
|
+
className: cn(
|
|
441
|
+
"stroke-blue-500 transition-all duration-100 dark:stroke-blue-300",
|
|
442
|
+
phase === "minute" && hoverTarget != null && "stroke-blue-400 opacity-90"
|
|
443
|
+
),
|
|
444
|
+
strokeWidth: 2.5,
|
|
445
|
+
strokeLinecap: "round"
|
|
446
|
+
}
|
|
447
|
+
),
|
|
448
|
+
/* @__PURE__ */ jsx("circle", { cx: CX, cy: CY, r: 5, className: "fill-blue-600 dark:fill-blue-400" }),
|
|
449
|
+
/* @__PURE__ */ jsx("circle", { cx: CX, cy: CY, r: 2.5, className: "fill-white" })
|
|
450
|
+
]
|
|
451
|
+
}
|
|
452
|
+
) }),
|
|
453
|
+
/* @__PURE__ */ jsxs("p", { className: "text-center text-[11px] text-muted-foreground", children: [
|
|
454
|
+
phase === "hour" ? "Hover or tap an hour" : "Hover or tap minutes \xB7 ",
|
|
455
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: hoverTarget != null && phase === "hour" ? formatTimeForDisplay(
|
|
456
|
+
toTimeStr(
|
|
457
|
+
timeFormat === "24h" ? hoverTarget : hour12To24(hoverTarget, ap),
|
|
458
|
+
editor.m
|
|
459
|
+
),
|
|
460
|
+
timeFormat
|
|
461
|
+
) : hoverTarget != null && phase === "minute" ? formatTimeForDisplay(toTimeStr(editor.h, hoverTarget), timeFormat) : displayTime })
|
|
462
|
+
] })
|
|
463
|
+
] });
|
|
464
|
+
}
|
|
465
|
+
var MONTH_NAMES = [
|
|
466
|
+
"January",
|
|
467
|
+
"February",
|
|
468
|
+
"March",
|
|
469
|
+
"April",
|
|
470
|
+
"May",
|
|
471
|
+
"June",
|
|
472
|
+
"July",
|
|
473
|
+
"August",
|
|
474
|
+
"September",
|
|
475
|
+
"October",
|
|
476
|
+
"November",
|
|
477
|
+
"December"
|
|
478
|
+
];
|
|
479
|
+
function dayStr(d) {
|
|
480
|
+
return d.toISOString().split("T")[0];
|
|
481
|
+
}
|
|
482
|
+
function getCalendarGrid(year, month) {
|
|
483
|
+
const firstDay = new Date(year, month, 1);
|
|
484
|
+
const dow = firstDay.getDay();
|
|
485
|
+
const startOffset = dow === 0 ? -6 : 1 - dow;
|
|
486
|
+
const days = [];
|
|
487
|
+
for (let i = 0; i < 35; i++) {
|
|
488
|
+
days.push(new Date(year, month, 1 + startOffset + i));
|
|
489
|
+
}
|
|
490
|
+
if (days[days.length - 1] < new Date(year, month + 1, 0)) {
|
|
491
|
+
for (let i = 35; i < 42; i++) {
|
|
492
|
+
days.push(new Date(year, month, 1 + startOffset + i));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return days;
|
|
496
|
+
}
|
|
497
|
+
function ScheduleFieldLabel({ children }) {
|
|
498
|
+
return /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children });
|
|
499
|
+
}
|
|
500
|
+
function ScheduleInfoBanner({ children }) {
|
|
501
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 rounded-xl border border-blue-100 bg-blue-50 px-3 py-2.5 dark:border-blue-900/50 dark:bg-blue-950/40", children: [
|
|
502
|
+
/* @__PURE__ */ jsx(Info, { size: 13, className: "mt-0.5 shrink-0 text-blue-400" }),
|
|
503
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs leading-relaxed text-blue-700 dark:text-blue-400", children })
|
|
504
|
+
] });
|
|
505
|
+
}
|
|
506
|
+
function ScheduleStepper({
|
|
507
|
+
value,
|
|
508
|
+
min = 1,
|
|
509
|
+
max = 100,
|
|
510
|
+
onChange
|
|
511
|
+
}) {
|
|
512
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex w-32 items-center overflow-hidden rounded-xl border border-border bg-card", children: [
|
|
513
|
+
/* @__PURE__ */ jsx(
|
|
514
|
+
"button",
|
|
515
|
+
{
|
|
516
|
+
type: "button",
|
|
517
|
+
onClick: () => onChange(Math.max(min, value - 1)),
|
|
518
|
+
className: "flex h-9 w-9 shrink-0 items-center justify-center border-r border-border text-sm text-muted-foreground hover:bg-accent",
|
|
519
|
+
children: "\u2212"
|
|
520
|
+
}
|
|
521
|
+
),
|
|
522
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 text-center text-sm font-medium text-foreground", children: value }),
|
|
523
|
+
/* @__PURE__ */ jsx(
|
|
524
|
+
"button",
|
|
525
|
+
{
|
|
526
|
+
type: "button",
|
|
527
|
+
onClick: () => onChange(Math.min(max, value + 1)),
|
|
528
|
+
className: "flex h-9 w-9 shrink-0 items-center justify-center border-l border-border text-sm text-muted-foreground hover:bg-accent",
|
|
529
|
+
children: "+"
|
|
530
|
+
}
|
|
531
|
+
)
|
|
532
|
+
] });
|
|
533
|
+
}
|
|
534
|
+
function WeekCalendarPicker({
|
|
535
|
+
weekFrom,
|
|
536
|
+
onChange,
|
|
537
|
+
flat = false
|
|
538
|
+
}) {
|
|
539
|
+
const initDate = /* @__PURE__ */ new Date((weekFrom || MIN_WEEK_START) + "T00:00:00");
|
|
540
|
+
const [calYear, setCalYear] = useState(initDate.getFullYear());
|
|
541
|
+
const [calMonth, setCalMonth] = useState(initDate.getMonth());
|
|
542
|
+
const [hoverMon, setHoverMon] = useState(null);
|
|
543
|
+
const days = getCalendarGrid(calYear, calMonth);
|
|
544
|
+
const minDate = /* @__PURE__ */ new Date((MIN_WEEK_START || PRICING_TODAY) + "T00:00:00");
|
|
545
|
+
const selectedMonStr = weekFrom || null;
|
|
546
|
+
const hoverMonStr = hoverMon;
|
|
547
|
+
const prevMonth = () => {
|
|
548
|
+
if (calMonth === 0) {
|
|
549
|
+
setCalYear((y) => y - 1);
|
|
550
|
+
setCalMonth(11);
|
|
551
|
+
} else setCalMonth((m) => m - 1);
|
|
552
|
+
};
|
|
553
|
+
const nextMonth = () => {
|
|
554
|
+
if (calMonth === 11) {
|
|
555
|
+
setCalYear((y) => y + 1);
|
|
556
|
+
setCalMonth(0);
|
|
557
|
+
} else setCalMonth((m) => m + 1);
|
|
558
|
+
};
|
|
559
|
+
const weeks = [];
|
|
560
|
+
for (let i = 0; i < days.length; i += 7) weeks.push(days.slice(i, i + 7));
|
|
561
|
+
return /* @__PURE__ */ jsxs(
|
|
562
|
+
"div",
|
|
563
|
+
{
|
|
564
|
+
className: cn(
|
|
565
|
+
"flex select-none flex-col",
|
|
566
|
+
flat ? "gap-3" : "gap-1 rounded-2xl border border-border bg-card p-3"
|
|
567
|
+
),
|
|
568
|
+
children: [
|
|
569
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center justify-between px-1", children: [
|
|
570
|
+
/* @__PURE__ */ jsx(
|
|
571
|
+
"button",
|
|
572
|
+
{
|
|
573
|
+
type: "button",
|
|
574
|
+
onClick: prevMonth,
|
|
575
|
+
className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent hover:text-muted-foreground",
|
|
576
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { size: 12, strokeWidth: 2.25 })
|
|
577
|
+
}
|
|
578
|
+
),
|
|
579
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold text-muted-foreground", children: [
|
|
580
|
+
MONTH_NAMES[calMonth],
|
|
581
|
+
" ",
|
|
582
|
+
calYear
|
|
583
|
+
] }),
|
|
584
|
+
/* @__PURE__ */ jsx(
|
|
585
|
+
"button",
|
|
586
|
+
{
|
|
587
|
+
type: "button",
|
|
588
|
+
onClick: nextMonth,
|
|
589
|
+
className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent hover:text-muted-foreground",
|
|
590
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { size: 12, strokeWidth: 2.25 })
|
|
591
|
+
}
|
|
592
|
+
)
|
|
593
|
+
] }),
|
|
594
|
+
/* @__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)) }),
|
|
595
|
+
weeks.map((week, wi) => {
|
|
596
|
+
const mondayDate = week[0];
|
|
597
|
+
const monStr = dayStr(mondayDate);
|
|
598
|
+
const isSelectable = mondayDate >= minDate;
|
|
599
|
+
const isSelected = monStr === selectedMonStr;
|
|
600
|
+
const isHovered = monStr === hoverMonStr && isSelectable && !isSelected;
|
|
601
|
+
return /* @__PURE__ */ jsx(
|
|
602
|
+
"div",
|
|
603
|
+
{
|
|
604
|
+
className: `flex cursor-pointer overflow-hidden rounded-xl transition-colors
|
|
605
|
+
${isSelected ? "bg-blue-100" : isHovered ? "bg-blue-50" : isSelectable ? "hover:bg-accent" : "cursor-not-allowed"}`,
|
|
606
|
+
onMouseEnter: () => isSelectable && setHoverMon(monStr),
|
|
607
|
+
onMouseLeave: () => setHoverMon(null),
|
|
608
|
+
onClick: () => isSelectable && onChange(monStr),
|
|
609
|
+
children: week.map((d, di) => {
|
|
610
|
+
const isCurrentMonth = d.getMonth() === calMonth;
|
|
611
|
+
const isMon = di === 0;
|
|
612
|
+
const isSelectedMon = isSelected && isMon;
|
|
613
|
+
return /* @__PURE__ */ jsx("div", { className: "flex h-8 flex-1 items-center justify-center", children: /* @__PURE__ */ jsx(
|
|
614
|
+
"span",
|
|
615
|
+
{
|
|
616
|
+
className: `
|
|
617
|
+
flex h-7 w-7 items-center justify-center rounded-full text-[11px] transition-colors
|
|
618
|
+
${isSelectedMon ? "bg-blue-500 font-semibold text-white" : ""}
|
|
619
|
+
${!isSelectedMon && isSelected ? "text-blue-700 dark:text-blue-400" : ""}
|
|
620
|
+
${!isSelected && isSelectable && isCurrentMonth ? "text-muted-foreground" : ""}
|
|
621
|
+
${!isSelectable && isCurrentMonth ? "text-muted-foreground/60" : ""}
|
|
622
|
+
${!isCurrentMonth ? "text-muted-foreground/60" : ""}
|
|
623
|
+
`,
|
|
624
|
+
children: d.getDate()
|
|
625
|
+
}
|
|
626
|
+
) }, di);
|
|
627
|
+
})
|
|
628
|
+
},
|
|
629
|
+
wi
|
|
630
|
+
);
|
|
631
|
+
}),
|
|
632
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-center text-[10px] text-muted-foreground", children: "Click any row to select that week" })
|
|
633
|
+
]
|
|
634
|
+
}
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
function MonthRangeCalendar({
|
|
638
|
+
monthFrom,
|
|
639
|
+
monthTo,
|
|
640
|
+
onChange,
|
|
641
|
+
flat = false
|
|
642
|
+
}) {
|
|
643
|
+
const initYear = monthFrom ? parseInt(monthFrom.split("-")[0], 10) : (/* @__PURE__ */ new Date()).getFullYear();
|
|
644
|
+
const [calYear, setCalYear] = useState(initYear);
|
|
645
|
+
const [phase, setPhase] = useState("from");
|
|
646
|
+
const [hoverYm, setHoverYm] = useState(null);
|
|
647
|
+
const ABBR = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
648
|
+
const toYm = (y, m) => `${y}-${String(m + 1).padStart(2, "0")}`;
|
|
649
|
+
const minYm = MIN_MONTH || `${(/* @__PURE__ */ new Date()).getFullYear()}-01`;
|
|
650
|
+
const [dispFrom, dispTo] = (() => {
|
|
651
|
+
if (phase === "to" && hoverYm) {
|
|
652
|
+
if (hoverYm >= monthFrom) return [monthFrom, hoverYm];
|
|
653
|
+
return [hoverYm, hoverYm];
|
|
654
|
+
}
|
|
655
|
+
return [monthFrom, monthTo];
|
|
656
|
+
})();
|
|
657
|
+
const isSingle = dispFrom === dispTo;
|
|
658
|
+
const handleClick = (ymStr) => {
|
|
659
|
+
if (ymStr < minYm) return;
|
|
660
|
+
if (phase === "from") {
|
|
661
|
+
onChange(ymStr, ymStr);
|
|
662
|
+
setPhase("to");
|
|
663
|
+
} else if (ymStr >= monthFrom) {
|
|
664
|
+
onChange(monthFrom, ymStr);
|
|
665
|
+
setPhase("from");
|
|
666
|
+
} else {
|
|
667
|
+
onChange(ymStr, ymStr);
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
const actualCount = monthCount(monthFrom, monthTo);
|
|
671
|
+
const previewCount = monthCount(dispFrom, dispTo);
|
|
672
|
+
return /* @__PURE__ */ jsxs(
|
|
673
|
+
"div",
|
|
674
|
+
{
|
|
675
|
+
className: cn(
|
|
676
|
+
"select-none",
|
|
677
|
+
flat ? "flex flex-col gap-5" : "overflow-hidden rounded-2xl border border-border bg-card"
|
|
678
|
+
),
|
|
679
|
+
children: [
|
|
680
|
+
/* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between", flat ? "px-1" : "px-4 pb-2 pt-4"), children: [
|
|
681
|
+
/* @__PURE__ */ jsx(
|
|
682
|
+
"button",
|
|
683
|
+
{
|
|
684
|
+
type: "button",
|
|
685
|
+
onClick: () => setCalYear((y) => y - 1),
|
|
686
|
+
className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent hover:text-muted-foreground",
|
|
687
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { size: 12, strokeWidth: 2.25 })
|
|
688
|
+
}
|
|
689
|
+
),
|
|
690
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-muted-foreground", children: calYear }),
|
|
691
|
+
/* @__PURE__ */ jsx(
|
|
692
|
+
"button",
|
|
693
|
+
{
|
|
694
|
+
type: "button",
|
|
695
|
+
onClick: () => setCalYear((y) => y + 1),
|
|
696
|
+
className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent hover:text-muted-foreground",
|
|
697
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { size: 12, strokeWidth: 2.25 })
|
|
698
|
+
}
|
|
699
|
+
)
|
|
700
|
+
] }),
|
|
701
|
+
/* @__PURE__ */ jsx(
|
|
702
|
+
"div",
|
|
703
|
+
{
|
|
704
|
+
className: cn(
|
|
705
|
+
"flex items-center gap-2 text-[11px] font-medium transition-colors",
|
|
706
|
+
flat ? phase === "to" ? "text-blue-600 dark:text-blue-400" : "text-muted-foreground" : cn(
|
|
707
|
+
"px-4 py-2 text-[10px]",
|
|
708
|
+
phase === "to" ? "bg-blue-50 text-blue-600 dark:bg-blue-950/40 dark:text-blue-400" : "bg-transparent text-muted-foreground"
|
|
709
|
+
)
|
|
710
|
+
),
|
|
711
|
+
children: phase === "from" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
712
|
+
/* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 shrink-0 rounded-full bg-muted-foreground/50" }),
|
|
713
|
+
"Click a month to set the start"
|
|
714
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
715
|
+
/* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 shrink-0 animate-pulse rounded-full bg-blue-500" }),
|
|
716
|
+
"Now click to set the end month",
|
|
717
|
+
/* @__PURE__ */ jsx(
|
|
718
|
+
"button",
|
|
719
|
+
{
|
|
720
|
+
type: "button",
|
|
721
|
+
onClick: () => {
|
|
722
|
+
onChange(monthFrom, monthFrom);
|
|
723
|
+
setPhase("to");
|
|
724
|
+
},
|
|
725
|
+
className: "ml-auto text-[10px] text-blue-400 underline underline-offset-2 hover:text-blue-600 dark:text-blue-400",
|
|
726
|
+
children: "Reset"
|
|
727
|
+
}
|
|
728
|
+
)
|
|
729
|
+
] })
|
|
730
|
+
}
|
|
731
|
+
),
|
|
732
|
+
/* @__PURE__ */ jsx(
|
|
733
|
+
"div",
|
|
734
|
+
{
|
|
735
|
+
className: cn("grid grid-cols-4", flat ? "gap-2 py-1" : "px-2 pb-3 pt-1"),
|
|
736
|
+
style: flat ? void 0 : { gap: 0 },
|
|
737
|
+
children: ABBR.map((label, i) => {
|
|
738
|
+
const ymStr = toYm(calYear, i);
|
|
739
|
+
const disabled = ymStr < minYm;
|
|
740
|
+
const inRange = !disabled && ymStr >= dispFrom && ymStr <= dispTo;
|
|
741
|
+
const isFrom = ymStr === dispFrom;
|
|
742
|
+
const isTo = ymStr === dispTo;
|
|
743
|
+
const showStrip = inRange && !isSingle;
|
|
744
|
+
const sLeft = isFrom ? "left-1/2" : "left-0";
|
|
745
|
+
const sRight = isTo ? "right-1/2" : "right-0";
|
|
746
|
+
const filled = (isFrom || isTo) && !disabled;
|
|
747
|
+
return /* @__PURE__ */ jsxs(
|
|
748
|
+
"div",
|
|
749
|
+
{
|
|
750
|
+
className: cn(
|
|
751
|
+
"relative",
|
|
752
|
+
flat ? "h-14" : "h-12",
|
|
753
|
+
disabled ? "cursor-not-allowed" : "cursor-pointer"
|
|
754
|
+
),
|
|
755
|
+
onMouseEnter: () => !disabled && setHoverYm(ymStr),
|
|
756
|
+
onMouseLeave: () => setHoverYm(null),
|
|
757
|
+
onClick: () => handleClick(ymStr),
|
|
758
|
+
children: [
|
|
759
|
+
showStrip ? /* @__PURE__ */ jsx("div", { className: `absolute bottom-1.5 top-1.5 bg-blue-100 ${sLeft} ${sRight}` }) : null,
|
|
760
|
+
/* @__PURE__ */ jsx(
|
|
761
|
+
"div",
|
|
762
|
+
{
|
|
763
|
+
className: cn(
|
|
764
|
+
"relative z-10 flex w-full items-center justify-center",
|
|
765
|
+
flat ? "h-14" : "h-12"
|
|
766
|
+
),
|
|
767
|
+
children: /* @__PURE__ */ jsx(
|
|
768
|
+
"span",
|
|
769
|
+
{
|
|
770
|
+
className: `flex h-9 w-9 items-center justify-center rounded-full text-xs font-medium transition-all duration-100
|
|
771
|
+
${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"}`,
|
|
772
|
+
children: label
|
|
773
|
+
}
|
|
774
|
+
)
|
|
775
|
+
}
|
|
776
|
+
)
|
|
777
|
+
]
|
|
778
|
+
},
|
|
779
|
+
i
|
|
780
|
+
);
|
|
781
|
+
})
|
|
782
|
+
}
|
|
783
|
+
),
|
|
784
|
+
/* @__PURE__ */ jsx(
|
|
785
|
+
"div",
|
|
786
|
+
{
|
|
787
|
+
className: cn(
|
|
788
|
+
"flex items-center justify-between transition-colors",
|
|
789
|
+
flat ? "border-t border-border/70 pt-4 text-xs" : cn(
|
|
790
|
+
"border-t px-4 py-2.5",
|
|
791
|
+
phase === "from" && actualCount > 0 ? "border-border bg-muted" : "border-transparent"
|
|
792
|
+
)
|
|
793
|
+
),
|
|
794
|
+
children: phase === "from" && actualCount > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
795
|
+
/* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
|
|
796
|
+
monthLabel(monthFrom),
|
|
797
|
+
" \u2192 ",
|
|
798
|
+
monthLabel(monthTo)
|
|
799
|
+
] }),
|
|
800
|
+
/* @__PURE__ */ jsxs("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: [
|
|
801
|
+
actualCount,
|
|
802
|
+
" month",
|
|
803
|
+
actualCount !== 1 ? "s" : ""
|
|
804
|
+
] })
|
|
805
|
+
] }) : phase === "to" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
806
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: hoverYm && hoverYm >= monthFrom ? `${monthLabel(monthFrom)} \u2192 ${monthLabel(hoverYm)}` : `Start: ${monthLabel(monthFrom)}` }),
|
|
807
|
+
hoverYm && hoverYm >= monthFrom && previewCount > 0 ? /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-medium text-blue-400", children: [
|
|
808
|
+
previewCount,
|
|
809
|
+
" month",
|
|
810
|
+
previewCount !== 1 ? "s" : ""
|
|
811
|
+
] }) : null
|
|
812
|
+
] }) : /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/60", children: "No months selected" })
|
|
813
|
+
}
|
|
814
|
+
)
|
|
815
|
+
]
|
|
816
|
+
}
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
function MonthPeriodPicker({
|
|
820
|
+
booking,
|
|
821
|
+
onChange,
|
|
822
|
+
flat = true
|
|
823
|
+
}) {
|
|
824
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", flat ? "gap-5" : "gap-3"), children: [
|
|
825
|
+
IS_PARTIAL_MONTH ? /* @__PURE__ */ jsxs(ScheduleInfoBanner, { children: [
|
|
826
|
+
"Today is ",
|
|
827
|
+
new Date(PRICING_TODAY).toLocaleDateString("en-US", { month: "long", day: "numeric" }),
|
|
828
|
+
". Current month is partially elapsed \u2014 earliest bookable month is",
|
|
829
|
+
" ",
|
|
830
|
+
/* @__PURE__ */ jsx("strong", { children: monthLabel(MIN_MONTH) }),
|
|
831
|
+
"."
|
|
832
|
+
] }) : null,
|
|
833
|
+
/* @__PURE__ */ jsx(ScheduleFieldLabel, { children: "Select month range" }),
|
|
834
|
+
/* @__PURE__ */ jsx(
|
|
835
|
+
MonthRangeCalendar,
|
|
836
|
+
{
|
|
837
|
+
flat,
|
|
838
|
+
monthFrom: booking.monthFrom,
|
|
839
|
+
monthTo: booking.monthTo,
|
|
840
|
+
onChange: (from, to) => onChange({ monthFrom: from, monthTo: to })
|
|
841
|
+
}
|
|
842
|
+
)
|
|
843
|
+
] });
|
|
844
|
+
}
|
|
845
|
+
function WeekPeriodPicker({
|
|
846
|
+
booking,
|
|
847
|
+
onChange,
|
|
848
|
+
flat = true
|
|
849
|
+
}) {
|
|
850
|
+
const rangeStr = weekRangeLabel(booking.weekFrom, booking.weekCount);
|
|
851
|
+
const isCurrentWeekPartial = new Date(PRICING_TODAY).getDay() !== 1;
|
|
852
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", flat ? "gap-5" : "gap-3"), children: [
|
|
853
|
+
isCurrentWeekPartial ? /* @__PURE__ */ jsxs(ScheduleInfoBanner, { children: [
|
|
854
|
+
"This week started on a ",
|
|
855
|
+
new Date(PRICING_TODAY).toLocaleDateString("en-US", { weekday: "long" }),
|
|
856
|
+
" \u2014 earliest full week begins",
|
|
857
|
+
" ",
|
|
858
|
+
/* @__PURE__ */ jsx("strong", { children: new Date(MIN_WEEK_START).toLocaleDateString("en-US", {
|
|
859
|
+
month: "long",
|
|
860
|
+
day: "numeric"
|
|
861
|
+
}) }),
|
|
862
|
+
"."
|
|
863
|
+
] }) : null,
|
|
864
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
865
|
+
/* @__PURE__ */ jsx(ScheduleFieldLabel, { children: "Starting week" }),
|
|
866
|
+
/* @__PURE__ */ jsx(
|
|
867
|
+
WeekCalendarPicker,
|
|
868
|
+
{
|
|
869
|
+
flat,
|
|
870
|
+
weekFrom: booking.weekFrom,
|
|
871
|
+
onChange: (v) => onChange({ weekFrom: v })
|
|
872
|
+
}
|
|
873
|
+
)
|
|
874
|
+
] }),
|
|
875
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
876
|
+
/* @__PURE__ */ jsx(ScheduleFieldLabel, { children: "Number of weeks" }),
|
|
877
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
878
|
+
/* @__PURE__ */ jsx(
|
|
879
|
+
ScheduleStepper,
|
|
880
|
+
{
|
|
881
|
+
value: booking.weekCount,
|
|
882
|
+
max: 52,
|
|
883
|
+
onChange: (v) => onChange({ weekCount: v })
|
|
884
|
+
}
|
|
885
|
+
),
|
|
886
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
887
|
+
booking.weekCount,
|
|
888
|
+
" week",
|
|
889
|
+
booking.weekCount !== 1 ? "s" : ""
|
|
890
|
+
] })
|
|
891
|
+
] })
|
|
892
|
+
] }),
|
|
893
|
+
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: [
|
|
894
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
|
|
895
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: "Period" }),
|
|
896
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: rangeStr })
|
|
897
|
+
] }),
|
|
898
|
+
/* @__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: [
|
|
899
|
+
booking.weekCount,
|
|
900
|
+
" week",
|
|
901
|
+
booking.weekCount !== 1 ? "s" : ""
|
|
902
|
+
] })
|
|
903
|
+
] }) : null
|
|
904
|
+
] });
|
|
905
|
+
}
|
|
906
|
+
function formatDateLabel(iso) {
|
|
907
|
+
if (!iso) return "\u2014";
|
|
908
|
+
return (/* @__PURE__ */ new Date(iso + "T00:00:00")).toLocaleDateString("en-US", {
|
|
909
|
+
month: "short",
|
|
910
|
+
day: "numeric",
|
|
911
|
+
year: "numeric"
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
function isBillableDay(day, start, end) {
|
|
915
|
+
return Boolean(start && end && day >= start && day < end);
|
|
916
|
+
}
|
|
917
|
+
function DayCalendarPicker({
|
|
918
|
+
selectedDate,
|
|
919
|
+
onChange,
|
|
920
|
+
minDate = PRICING_TODAY,
|
|
921
|
+
flat = false
|
|
922
|
+
}) {
|
|
923
|
+
const initDate = /* @__PURE__ */ new Date((selectedDate || minDate) + "T00:00:00");
|
|
924
|
+
const [calYear, setCalYear] = useState(initDate.getFullYear());
|
|
925
|
+
const [calMonth, setCalMonth] = useState(initDate.getMonth());
|
|
926
|
+
const days = getCalendarGrid(calYear, calMonth);
|
|
927
|
+
const prevMonth = () => {
|
|
928
|
+
if (calMonth === 0) {
|
|
929
|
+
setCalYear((y) => y - 1);
|
|
930
|
+
setCalMonth(11);
|
|
931
|
+
} else setCalMonth((m) => m - 1);
|
|
932
|
+
};
|
|
933
|
+
const nextMonth = () => {
|
|
934
|
+
if (calMonth === 11) {
|
|
935
|
+
setCalYear((y) => y + 1);
|
|
936
|
+
setCalMonth(0);
|
|
937
|
+
} else setCalMonth((m) => m + 1);
|
|
938
|
+
};
|
|
939
|
+
return /* @__PURE__ */ jsxs(
|
|
940
|
+
"div",
|
|
941
|
+
{
|
|
942
|
+
className: cn(
|
|
943
|
+
"flex select-none flex-col",
|
|
944
|
+
flat ? "gap-3" : "gap-1 rounded-2xl border border-border bg-card p-3"
|
|
945
|
+
),
|
|
946
|
+
children: [
|
|
947
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center justify-between px-1", children: [
|
|
948
|
+
/* @__PURE__ */ jsx(
|
|
949
|
+
"button",
|
|
950
|
+
{
|
|
951
|
+
type: "button",
|
|
952
|
+
onClick: prevMonth,
|
|
953
|
+
className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent",
|
|
954
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { size: 12, strokeWidth: 2.25 })
|
|
955
|
+
}
|
|
956
|
+
),
|
|
957
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold text-muted-foreground", children: [
|
|
958
|
+
MONTH_NAMES[calMonth],
|
|
959
|
+
" ",
|
|
960
|
+
calYear
|
|
961
|
+
] }),
|
|
962
|
+
/* @__PURE__ */ jsx(
|
|
963
|
+
"button",
|
|
964
|
+
{
|
|
965
|
+
type: "button",
|
|
966
|
+
onClick: nextMonth,
|
|
967
|
+
className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent",
|
|
968
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { size: 12, strokeWidth: 2.25 })
|
|
969
|
+
}
|
|
970
|
+
)
|
|
971
|
+
] }),
|
|
972
|
+
/* @__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)) }),
|
|
973
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-7", children: days.map((d) => {
|
|
974
|
+
const ds = dayStr(d);
|
|
975
|
+
const isCurrentMonth = d.getMonth() === calMonth;
|
|
976
|
+
const isSelectable = ds >= minDate;
|
|
977
|
+
const isSelected = ds === selectedDate;
|
|
978
|
+
return /* @__PURE__ */ jsx(
|
|
979
|
+
"button",
|
|
980
|
+
{
|
|
981
|
+
type: "button",
|
|
982
|
+
disabled: !isSelectable,
|
|
983
|
+
onClick: () => isSelectable && onChange(ds),
|
|
984
|
+
className: cn(
|
|
985
|
+
"flex h-9 items-center justify-center",
|
|
986
|
+
!isSelectable && "cursor-not-allowed"
|
|
987
|
+
),
|
|
988
|
+
children: /* @__PURE__ */ jsx(
|
|
989
|
+
"span",
|
|
990
|
+
{
|
|
991
|
+
className: cn(
|
|
992
|
+
"flex h-8 w-8 items-center justify-center rounded-full text-[11px] transition-colors",
|
|
993
|
+
isSelected && "bg-blue-500 font-semibold text-white shadow-sm",
|
|
994
|
+
!isSelected && isSelectable && isCurrentMonth && "text-foreground hover:bg-accent",
|
|
995
|
+
!isSelected && isSelectable && !isCurrentMonth && "text-muted-foreground hover:bg-accent",
|
|
996
|
+
!isSelectable && isCurrentMonth && "text-muted-foreground/40",
|
|
997
|
+
!isSelectable && !isCurrentMonth && "text-muted-foreground/30"
|
|
998
|
+
),
|
|
999
|
+
children: d.getDate()
|
|
1000
|
+
}
|
|
1001
|
+
)
|
|
1002
|
+
},
|
|
1003
|
+
ds
|
|
1004
|
+
);
|
|
1005
|
+
}) }),
|
|
1006
|
+
selectedDate ? /* @__PURE__ */ jsxs("p", { className: "mt-1 text-center text-[10px] text-muted-foreground", children: [
|
|
1007
|
+
"Selected ",
|
|
1008
|
+
formatDateLabel(selectedDate)
|
|
1009
|
+
] }) : /* @__PURE__ */ jsx("p", { className: "mt-1 text-center text-[10px] text-muted-foreground", children: "Click a day to book" })
|
|
1010
|
+
]
|
|
1011
|
+
}
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
function DayRangeCalendar({
|
|
1015
|
+
startDate,
|
|
1016
|
+
endDate,
|
|
1017
|
+
onChange,
|
|
1018
|
+
minDate = PRICING_TODAY,
|
|
1019
|
+
flat = false
|
|
1020
|
+
}) {
|
|
1021
|
+
const initDate = /* @__PURE__ */ new Date((startDate || minDate) + "T00:00:00");
|
|
1022
|
+
const [calYear, setCalYear] = useState(initDate.getFullYear());
|
|
1023
|
+
const [calMonth, setCalMonth] = useState(initDate.getMonth());
|
|
1024
|
+
const [phase, setPhase] = useState("from");
|
|
1025
|
+
const [hoverDay, setHoverDay] = useState(null);
|
|
1026
|
+
const days = getCalendarGrid(calYear, calMonth);
|
|
1027
|
+
const lastBillableDay = endDate ? addDays(endDate, -1) : "";
|
|
1028
|
+
const [dispStart, dispEnd] = (() => {
|
|
1029
|
+
if (phase === "to" && hoverDay && startDate) {
|
|
1030
|
+
if (hoverDay >= startDate) return [startDate, addDays(hoverDay, 1)];
|
|
1031
|
+
return [hoverDay, addDays(hoverDay, 1)];
|
|
1032
|
+
}
|
|
1033
|
+
return [startDate, endDate];
|
|
1034
|
+
})();
|
|
1035
|
+
const dayCount = startDate && endDate ? diffDays(startDate, endDate) : 0;
|
|
1036
|
+
const handleClick = (ds) => {
|
|
1037
|
+
if (ds < minDate) return;
|
|
1038
|
+
if (phase === "from") {
|
|
1039
|
+
onChange(ds, addDays(ds, 1));
|
|
1040
|
+
setPhase("to");
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
if (ds < startDate) {
|
|
1044
|
+
onChange(ds, addDays(ds, 1));
|
|
1045
|
+
setPhase("to");
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
onChange(startDate, addDays(ds, 1));
|
|
1049
|
+
setPhase("from");
|
|
1050
|
+
};
|
|
1051
|
+
const prevMonth = () => {
|
|
1052
|
+
if (calMonth === 0) {
|
|
1053
|
+
setCalYear((y) => y - 1);
|
|
1054
|
+
setCalMonth(11);
|
|
1055
|
+
} else setCalMonth((m) => m - 1);
|
|
1056
|
+
};
|
|
1057
|
+
const nextMonth = () => {
|
|
1058
|
+
if (calMonth === 11) {
|
|
1059
|
+
setCalYear((y) => y + 1);
|
|
1060
|
+
setCalMonth(0);
|
|
1061
|
+
} else setCalMonth((m) => m + 1);
|
|
1062
|
+
};
|
|
1063
|
+
return /* @__PURE__ */ jsxs(
|
|
1064
|
+
"div",
|
|
1065
|
+
{
|
|
1066
|
+
className: cn(
|
|
1067
|
+
"flex select-none flex-col",
|
|
1068
|
+
flat ? "gap-3" : "gap-1 rounded-2xl border border-border bg-card p-3"
|
|
1069
|
+
),
|
|
1070
|
+
children: [
|
|
1071
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center justify-between px-1", children: [
|
|
1072
|
+
/* @__PURE__ */ jsx(
|
|
1073
|
+
"button",
|
|
1074
|
+
{
|
|
1075
|
+
type: "button",
|
|
1076
|
+
onClick: prevMonth,
|
|
1077
|
+
className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent",
|
|
1078
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { size: 12, strokeWidth: 2.25 })
|
|
1079
|
+
}
|
|
1080
|
+
),
|
|
1081
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold text-muted-foreground", children: [
|
|
1082
|
+
MONTH_NAMES[calMonth],
|
|
1083
|
+
" ",
|
|
1084
|
+
calYear
|
|
1085
|
+
] }),
|
|
1086
|
+
/* @__PURE__ */ jsx(
|
|
1087
|
+
"button",
|
|
1088
|
+
{
|
|
1089
|
+
type: "button",
|
|
1090
|
+
onClick: nextMonth,
|
|
1091
|
+
className: "flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent",
|
|
1092
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { size: 12, strokeWidth: 2.25 })
|
|
1093
|
+
}
|
|
1094
|
+
)
|
|
1095
|
+
] }),
|
|
1096
|
+
/* @__PURE__ */ jsx(
|
|
1097
|
+
"div",
|
|
1098
|
+
{
|
|
1099
|
+
className: cn(
|
|
1100
|
+
"flex items-center gap-2 px-1 text-[11px] font-medium",
|
|
1101
|
+
phase === "to" ? "text-blue-600 dark:text-blue-400" : "text-muted-foreground"
|
|
1102
|
+
),
|
|
1103
|
+
children: phase === "from" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1104
|
+
/* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 shrink-0 rounded-full bg-muted-foreground/50" }),
|
|
1105
|
+
"Click the first day"
|
|
1106
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1107
|
+
/* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 shrink-0 animate-pulse rounded-full bg-blue-500" }),
|
|
1108
|
+
"Click the last day",
|
|
1109
|
+
/* @__PURE__ */ jsx(
|
|
1110
|
+
"button",
|
|
1111
|
+
{
|
|
1112
|
+
type: "button",
|
|
1113
|
+
onClick: () => {
|
|
1114
|
+
onChange(startDate, addDays(startDate, 1));
|
|
1115
|
+
setPhase("to");
|
|
1116
|
+
},
|
|
1117
|
+
className: "ml-auto text-[10px] text-blue-400 underline underline-offset-2 hover:text-blue-600 dark:text-blue-400",
|
|
1118
|
+
children: "Reset"
|
|
1119
|
+
}
|
|
1120
|
+
)
|
|
1121
|
+
] })
|
|
1122
|
+
}
|
|
1123
|
+
),
|
|
1124
|
+
/* @__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)) }),
|
|
1125
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-7", children: days.map((d) => {
|
|
1126
|
+
const ds = dayStr(d);
|
|
1127
|
+
const isCurrentMonth = d.getMonth() === calMonth;
|
|
1128
|
+
const isSelectable = ds >= minDate;
|
|
1129
|
+
const inRange = isBillableDay(ds, dispStart, dispEnd);
|
|
1130
|
+
const isStart = ds === dispStart;
|
|
1131
|
+
const isEnd = ds === lastBillableDay && dispEnd && diffDays(dispStart, dispEnd) > 1;
|
|
1132
|
+
const isSingle = dispStart && dispEnd && diffDays(dispStart, dispEnd) === 1 && ds === dispStart;
|
|
1133
|
+
return /* @__PURE__ */ jsxs(
|
|
1134
|
+
"button",
|
|
1135
|
+
{
|
|
1136
|
+
type: "button",
|
|
1137
|
+
disabled: !isSelectable,
|
|
1138
|
+
onMouseEnter: () => isSelectable && setHoverDay(ds),
|
|
1139
|
+
onMouseLeave: () => setHoverDay(null),
|
|
1140
|
+
onClick: () => isSelectable && handleClick(ds),
|
|
1141
|
+
className: cn(
|
|
1142
|
+
"relative flex h-9 items-center justify-center",
|
|
1143
|
+
!isSelectable && "cursor-not-allowed"
|
|
1144
|
+
),
|
|
1145
|
+
children: [
|
|
1146
|
+
inRange && !isSingle ? /* @__PURE__ */ jsx(
|
|
1147
|
+
"span",
|
|
1148
|
+
{
|
|
1149
|
+
className: cn(
|
|
1150
|
+
"absolute inset-y-1.5 bg-blue-100",
|
|
1151
|
+
isStart ? "left-1/2 right-0 rounded-l-full" : isEnd ? "left-0 right-1/2 rounded-r-full" : "inset-x-0"
|
|
1152
|
+
)
|
|
1153
|
+
}
|
|
1154
|
+
) : null,
|
|
1155
|
+
/* @__PURE__ */ jsx(
|
|
1156
|
+
"span",
|
|
1157
|
+
{
|
|
1158
|
+
className: cn(
|
|
1159
|
+
"relative z-10 flex h-8 w-8 items-center justify-center rounded-full text-[11px] transition-colors",
|
|
1160
|
+
(isStart || isEnd || isSingle) && "bg-blue-500 font-semibold text-white shadow-sm",
|
|
1161
|
+
!isStart && !isEnd && !isSingle && inRange && "font-medium text-blue-700 dark:text-blue-400",
|
|
1162
|
+
!inRange && isSelectable && isCurrentMonth && "text-foreground hover:bg-accent",
|
|
1163
|
+
!inRange && isSelectable && !isCurrentMonth && "text-muted-foreground hover:bg-accent",
|
|
1164
|
+
!isSelectable && isCurrentMonth && "text-muted-foreground/40",
|
|
1165
|
+
!isSelectable && !isCurrentMonth && "text-muted-foreground/30"
|
|
1166
|
+
),
|
|
1167
|
+
children: d.getDate()
|
|
1168
|
+
}
|
|
1169
|
+
)
|
|
1170
|
+
]
|
|
1171
|
+
},
|
|
1172
|
+
ds
|
|
1173
|
+
);
|
|
1174
|
+
}) }),
|
|
1175
|
+
/* @__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: [
|
|
1176
|
+
/* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
|
|
1177
|
+
formatDateLabel(startDate),
|
|
1178
|
+
dayCount > 1 ? ` \u2192 ${formatDateLabel(lastBillableDay)}` : ""
|
|
1179
|
+
] }),
|
|
1180
|
+
/* @__PURE__ */ jsxs("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: [
|
|
1181
|
+
dayCount,
|
|
1182
|
+
" day",
|
|
1183
|
+
dayCount !== 1 ? "s" : ""
|
|
1184
|
+
] })
|
|
1185
|
+
] }) : /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/60", children: "Select a date range" }) })
|
|
1186
|
+
]
|
|
1187
|
+
}
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
function DayPeriodPicker({
|
|
1191
|
+
booking,
|
|
1192
|
+
onChange,
|
|
1193
|
+
flat = true
|
|
1194
|
+
}) {
|
|
1195
|
+
const oneDay = booking.oneDayBooking ?? true;
|
|
1196
|
+
const dayCount = oneDay ? 1 : diffDays(booking.startDate, booking.endDate);
|
|
1197
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", flat ? "gap-5" : "gap-3"), children: [
|
|
1198
|
+
/* @__PURE__ */ jsx("div", { className: "flex gap-2", children: [
|
|
1199
|
+
{ key: true, label: "Single day" },
|
|
1200
|
+
{ key: false, label: "Multiple days" }
|
|
1201
|
+
].map(({ key, label }) => /* @__PURE__ */ jsx(
|
|
1202
|
+
"button",
|
|
1203
|
+
{
|
|
1204
|
+
type: "button",
|
|
1205
|
+
onClick: () => {
|
|
1206
|
+
const start = booking.startDate || PRICING_TODAY;
|
|
1207
|
+
if (key) {
|
|
1208
|
+
onChange({ oneDayBooking: true, startDate: start, endDate: addDays(start, 1) });
|
|
1209
|
+
} else {
|
|
1210
|
+
const end = booking.endDate && booking.endDate > start && diffDays(start, booking.endDate) > 1 ? booking.endDate : addDays(start, 7);
|
|
1211
|
+
onChange({ oneDayBooking: false, startDate: start, endDate: end });
|
|
1212
|
+
}
|
|
1213
|
+
},
|
|
1214
|
+
className: cn(
|
|
1215
|
+
"flex h-9 flex-1 items-center justify-center rounded-xl border text-sm font-medium transition-colors",
|
|
1216
|
+
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"
|
|
1217
|
+
),
|
|
1218
|
+
children: label
|
|
1219
|
+
},
|
|
1220
|
+
String(key)
|
|
1221
|
+
)) }),
|
|
1222
|
+
oneDay ? /* @__PURE__ */ jsx(
|
|
1223
|
+
DayCalendarPicker,
|
|
1224
|
+
{
|
|
1225
|
+
flat,
|
|
1226
|
+
selectedDate: booking.startDate,
|
|
1227
|
+
onChange: (v) => onChange({ startDate: v, endDate: addDays(v, 1) })
|
|
1228
|
+
}
|
|
1229
|
+
) : /* @__PURE__ */ jsx(
|
|
1230
|
+
DayRangeCalendar,
|
|
1231
|
+
{
|
|
1232
|
+
flat,
|
|
1233
|
+
startDate: booking.startDate,
|
|
1234
|
+
endDate: booking.endDate,
|
|
1235
|
+
onChange: (start, end) => onChange({ startDate: start, endDate: end })
|
|
1236
|
+
}
|
|
1237
|
+
),
|
|
1238
|
+
dayCount > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded-xl border border-border bg-muted px-3 py-2.5", children: [
|
|
1239
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
|
|
1240
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: "Period" }),
|
|
1241
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: oneDay ? formatDateLabel(booking.startDate) : `${formatDateLabel(booking.startDate)} \u2192 ${formatDateLabel(addDays(booking.endDate, -1))}` })
|
|
1242
|
+
] }),
|
|
1243
|
+
/* @__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: [
|
|
1244
|
+
dayCount,
|
|
1245
|
+
" day",
|
|
1246
|
+
dayCount !== 1 ? "s" : ""
|
|
1247
|
+
] })
|
|
1248
|
+
] }) : null
|
|
1249
|
+
] });
|
|
1250
|
+
}
|
|
1251
|
+
function HourlySchedulePicker({
|
|
1252
|
+
scheduleDate,
|
|
1253
|
+
scheduleTime,
|
|
1254
|
+
onChange,
|
|
1255
|
+
timeFormat = "24h",
|
|
1256
|
+
flat = true
|
|
1257
|
+
}) {
|
|
1258
|
+
const [step, setStep] = useState(() => scheduleDate ? "time" : "date");
|
|
1259
|
+
useEffect(() => {
|
|
1260
|
+
if (!scheduleDate) setStep("date");
|
|
1261
|
+
}, [scheduleDate]);
|
|
1262
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-5", flat ? "" : "rounded-2xl border border-border bg-card p-4"), children: [
|
|
1263
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground", children: [
|
|
1264
|
+
/* @__PURE__ */ jsx(
|
|
1265
|
+
"span",
|
|
1266
|
+
{
|
|
1267
|
+
className: cn(
|
|
1268
|
+
step === "date" && "font-semibold text-blue-600 dark:text-blue-400",
|
|
1269
|
+
scheduleDate && step !== "date" && "text-foreground"
|
|
1270
|
+
),
|
|
1271
|
+
children: "Date"
|
|
1272
|
+
}
|
|
1273
|
+
),
|
|
1274
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\xB7" }),
|
|
1275
|
+
/* @__PURE__ */ jsx(
|
|
1276
|
+
"span",
|
|
1277
|
+
{
|
|
1278
|
+
className: cn(
|
|
1279
|
+
step === "time" && "font-semibold text-blue-600 dark:text-blue-400",
|
|
1280
|
+
!scheduleDate && "text-muted-foreground/60"
|
|
1281
|
+
),
|
|
1282
|
+
children: "Time"
|
|
1283
|
+
}
|
|
1284
|
+
)
|
|
1285
|
+
] }),
|
|
1286
|
+
step === "date" ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
1287
|
+
/* @__PURE__ */ jsx(ScheduleFieldLabel, { children: "Pick a day" }),
|
|
1288
|
+
/* @__PURE__ */ jsx(
|
|
1289
|
+
DayCalendarPicker,
|
|
1290
|
+
{
|
|
1291
|
+
flat,
|
|
1292
|
+
selectedDate: scheduleDate,
|
|
1293
|
+
minDate: PRICING_TODAY,
|
|
1294
|
+
onChange: (v) => {
|
|
1295
|
+
onChange({ scheduleDate: v });
|
|
1296
|
+
setStep("time");
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
),
|
|
1300
|
+
/* @__PURE__ */ jsx("p", { className: "text-center text-[11px] text-muted-foreground", children: "Select a day to set the time" })
|
|
1301
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-5", children: [
|
|
1302
|
+
/* @__PURE__ */ jsxs(
|
|
1303
|
+
"button",
|
|
1304
|
+
{
|
|
1305
|
+
type: "button",
|
|
1306
|
+
onClick: () => setStep("date"),
|
|
1307
|
+
className: "inline-flex w-fit items-center gap-2 rounded-xl border border-border bg-muted/60 px-3 py-2 text-left transition-colors hover:bg-muted",
|
|
1308
|
+
children: [
|
|
1309
|
+
/* @__PURE__ */ jsx(ChevronLeft, { size: 14, className: "shrink-0 text-muted-foreground" }),
|
|
1310
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1311
|
+
/* @__PURE__ */ jsx("p", { className: "text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: "Date" }),
|
|
1312
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: formatDateLabel(scheduleDate) })
|
|
1313
|
+
] }),
|
|
1314
|
+
/* @__PURE__ */ jsx("span", { className: "ml-2 text-[11px] font-medium text-blue-600 dark:text-blue-400", children: "Change" })
|
|
1315
|
+
]
|
|
1316
|
+
}
|
|
1317
|
+
),
|
|
1318
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
1319
|
+
/* @__PURE__ */ jsx(ScheduleFieldLabel, { children: "Pick a time" }),
|
|
1320
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-center rounded-2xl border border-border bg-card py-6", children: /* @__PURE__ */ jsx(
|
|
1321
|
+
AnalogClockPicker,
|
|
1322
|
+
{
|
|
1323
|
+
value: scheduleTime,
|
|
1324
|
+
onChange: (v) => onChange({ scheduleTime: v }),
|
|
1325
|
+
timeFormat
|
|
1326
|
+
}
|
|
1327
|
+
) })
|
|
1328
|
+
] }),
|
|
1329
|
+
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: [
|
|
1330
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: "Scheduled for" }),
|
|
1331
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-foreground", children: [
|
|
1332
|
+
formatDateLabel(scheduleDate),
|
|
1333
|
+
" \xB7 ",
|
|
1334
|
+
formatTimeForDisplay(scheduleTime, timeFormat)
|
|
1335
|
+
] })
|
|
1336
|
+
] }) }) : null
|
|
1337
|
+
] })
|
|
1338
|
+
] });
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
export { DayCalendarPicker, DayPeriodPicker, DayRangeCalendar, HourlySchedulePicker, MonthPeriodPicker, MonthRangeCalendar, ScheduleFieldLabel, ScheduleInfoBanner, ScheduleStepper, WeekCalendarPicker, WeekPeriodPicker };
|
|
1342
|
+
//# sourceMappingURL=booking-schedule-calendars.js.map
|
|
1343
|
+
//# sourceMappingURL=booking-schedule-calendars.js.map
|