@pisell/materials 1.8.34 → 1.8.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/build/lowcode/assets-daily.json +11 -11
  2. package/build/lowcode/assets-dev.json +2 -2
  3. package/build/lowcode/assets-prod.json +11 -11
  4. package/build/lowcode/index.js +1 -1
  5. package/build/lowcode/meta.js +2 -2
  6. package/build/lowcode/preview.js +1 -1
  7. package/build/lowcode/render/default/view.css +1 -1
  8. package/build/lowcode/render/default/view.js +35 -35
  9. package/build/lowcode/view.css +1 -1
  10. package/build/lowcode/view.js +35 -35
  11. package/es/components/pisellFloorMapLayout/PisellFloorMapLayout.js +25 -10
  12. package/es/components/pisellFloorMapLayout/appearance/floorMapAppearance.d.ts +4 -0
  13. package/es/components/pisellFloorMapLayout/appearance/floorMapAppearance.js +22 -0
  14. package/es/components/pisellFloorMapLayout/components/EditableItemLayer.js +3 -2
  15. package/es/components/pisellFloorMapLayout/components/ItemLayer.js +7 -4
  16. package/es/components/pisellFloorMapLayout/components/ItemLayer.less +10 -0
  17. package/es/components/pisellFloorMapLayout/components/ViewControls.js +6 -5
  18. package/es/components/pisellFloorMapLayout/components/ViewControls.less +29 -2
  19. package/es/components/pisellFloorMapLayout/components/ViewControlsWithZoom.js +1 -0
  20. package/es/components/pisellFloorMapLayout/context/mergeFloorMapLayoutContext.js +3 -1
  21. package/es/components/pisellFloorMapLayout/types.d.ts +21 -2
  22. package/es/components/pisellRecordBoard/index.d.ts +1 -1
  23. package/es/components/pisellRecordBoard/shellFrame/Calendar/BookingCalendar.js +280 -230
  24. package/es/components/pisellRecordBoard/shellFrame/Calendar/const/bookingCalendarConstants.js +18 -1
  25. package/es/components/pisellRecordBoard/shellFrame/Calendar/hooks/useBookingCalendarDayOverlayLayout.js +74 -0
  26. package/es/components/pisellRecordBoard/shellFrame/Calendar/modal/BookingCalendarBlockPanel.js +61 -0
  27. package/es/components/pisellRecordBoard/shellFrame/Calendar/modal/BookingCalendarOverflowListLayer.js +26 -0
  28. package/es/components/pisellRecordBoard/shellFrame/Calendar/utils/bookingCalendarDateUtils.js +11 -1
  29. package/es/components/pisellRecordBoard/shellFrame/Calendar/utils/bookingCalendarSelectionUtils.js +17 -0
  30. package/es/components/pisellRecordBoard/shellFrame/Calendar/view/BookingCalendarDaySelectionBar.js +12 -8
  31. package/es/components/pisellRecordBoard/shellFrame/Calendar/view/BookingCalendarDayView.js +3 -12
  32. package/es/components/pisellRecordBoard/shellFrame/Calendar/view/BookingCalendarResourceView.js +612 -0
  33. package/es/components/pisellRecordBoard/shellFrame/Calendar/view/day/useBookingCalendarDayHourRemaining.js +33 -0
  34. package/es/components/pisellRecordBoard/shellFrame/Calendar/view/day/useBookingCalendarDaySelection.js +37 -0
  35. package/es/components/pisellRecordBoard/shellFrame/Calendar/view/resource/useBookingCalendarResourceCellToggle.js +18 -0
  36. package/es/components/pisellRecordBoard/shellFrame/Calendar/view/resource/useBookingCalendarResourceDayHeaderRemaining.js +33 -0
  37. package/es/components/pisellRecordBoard/shellFrame/Calendar/view/useBookingCalendarGoToDayFromCell.js +29 -0
  38. package/es/components/pisellRecordBoard/types.d.ts +38 -6
  39. package/es/components/pisellReservationSchedule/PisellReservationSchedule.js +19 -3
  40. package/es/components/pisellReservationSchedule/PisellReservationSchedule.less +23 -0
  41. package/es/index.d.ts +2 -2
  42. package/es/locales/en-US.js +6 -5
  43. package/es/locales/ja.js +1 -0
  44. package/es/locales/pt.js +1 -0
  45. package/es/locales/zh-CN.js +1 -0
  46. package/es/locales/zh-TW.js +1 -0
  47. package/es/pisell-materials.tw.css +2413 -2
  48. package/lib/components/pisellFloorMapLayout/PisellFloorMapLayout.js +25 -10
  49. package/lib/components/pisellFloorMapLayout/appearance/floorMapAppearance.d.ts +4 -0
  50. package/lib/components/pisellFloorMapLayout/appearance/floorMapAppearance.js +22 -0
  51. package/lib/components/pisellFloorMapLayout/components/EditableItemLayer.js +3 -2
  52. package/lib/components/pisellFloorMapLayout/components/ItemLayer.js +7 -4
  53. package/lib/components/pisellFloorMapLayout/components/ItemLayer.less +10 -0
  54. package/lib/components/pisellFloorMapLayout/components/ViewControls.js +6 -5
  55. package/lib/components/pisellFloorMapLayout/components/ViewControls.less +29 -2
  56. package/lib/components/pisellFloorMapLayout/components/ViewControlsWithZoom.js +1 -0
  57. package/lib/components/pisellFloorMapLayout/context/mergeFloorMapLayoutContext.js +3 -1
  58. package/lib/components/pisellFloorMapLayout/types.d.ts +21 -2
  59. package/lib/components/pisellRecordBoard/index.d.ts +1 -1
  60. package/lib/components/pisellRecordBoard/shellFrame/Calendar/BookingCalendar.js +277 -227
  61. package/lib/components/pisellRecordBoard/shellFrame/Calendar/const/bookingCalendarConstants.js +18 -0
  62. package/lib/components/pisellRecordBoard/shellFrame/Calendar/hooks/useBookingCalendarDayOverlayLayout.js +75 -0
  63. package/lib/components/pisellRecordBoard/shellFrame/Calendar/modal/BookingCalendarBlockPanel.js +63 -0
  64. package/lib/components/pisellRecordBoard/shellFrame/Calendar/modal/BookingCalendarOverflowListLayer.js +28 -0
  65. package/lib/components/pisellRecordBoard/shellFrame/Calendar/utils/bookingCalendarDateUtils.js +11 -1
  66. package/lib/components/pisellRecordBoard/shellFrame/Calendar/utils/bookingCalendarSelectionUtils.js +17 -0
  67. package/lib/components/pisellRecordBoard/shellFrame/Calendar/view/BookingCalendarDaySelectionBar.js +12 -8
  68. package/lib/components/pisellRecordBoard/shellFrame/Calendar/view/BookingCalendarDayView.js +3 -12
  69. package/lib/components/pisellRecordBoard/shellFrame/Calendar/view/BookingCalendarResourceView.js +614 -0
  70. package/lib/components/pisellRecordBoard/shellFrame/Calendar/view/day/useBookingCalendarDayHourRemaining.js +34 -0
  71. package/lib/components/pisellRecordBoard/shellFrame/Calendar/view/day/useBookingCalendarDaySelection.js +38 -0
  72. package/lib/components/pisellRecordBoard/shellFrame/Calendar/view/resource/useBookingCalendarResourceCellToggle.js +19 -0
  73. package/lib/components/pisellRecordBoard/shellFrame/Calendar/view/resource/useBookingCalendarResourceDayHeaderRemaining.js +34 -0
  74. package/lib/components/pisellRecordBoard/shellFrame/Calendar/view/useBookingCalendarGoToDayFromCell.js +30 -0
  75. package/lib/components/pisellRecordBoard/types.d.ts +38 -6
  76. package/lib/components/pisellReservationSchedule/PisellReservationSchedule.js +19 -3
  77. package/lib/components/pisellReservationSchedule/PisellReservationSchedule.less +23 -0
  78. package/lib/index.d.ts +2 -2
  79. package/lib/locales/en-US.js +6 -5
  80. package/lib/locales/ja.js +1 -0
  81. package/lib/locales/pt.js +1 -0
  82. package/lib/locales/zh-CN.js +1 -0
  83. package/lib/locales/zh-TW.js +1 -0
  84. package/lib/pisell-materials.tw.css +2413 -2
  85. package/lowcode/pisell-record-board-calendar-view/meta.ts +8 -0
  86. package/package.json +1 -1
@@ -2,25 +2,33 @@ import { _objectSpread2 } from "../../../../_virtual/_@oxc-project_runtime@0.122
2
2
  import { _asyncToGenerator } from "../../../../_virtual/_@oxc-project_runtime@0.122.0/helpers/asyncToGenerator.js";
3
3
  import { buildCalendarTimelineSlotStartsFromRange } from "../../../pisellTimeNavigator/utils/index.js";
4
4
  import { useCalendarPersist } from "./calendarPersistGuard.js";
5
- import { DEFAULT_CALENDAR_TIMELINE, dayCourtOverlayRowHeightPx } from "./const/bookingCalendarConstants.js";
5
+ import { DEFAULT_CALENDAR_TIMELINE } from "./const/bookingCalendarConstants.js";
6
6
  import { tCal } from "./calendarI18n.js";
7
- import { addDays, buildDayRowTopMap, buildOverlayBookings, clusterDayCourtOverlaysByOverlap, dayAndHourToDayjs, dayOccupancy, dayOverlayHiddenKey, effectiveDayOverlayDisplayLaneCount, fmtDate, getBookingSegmentForDate, groupSelectedSlots, isFreeSelectionContiguousSameResource, isHourInTimelineSlots, maxConcurrentBookingsOnCourt, monthGrid, rangeDays, sameSlot, startOfWeek } from "./utils/bookingCalendarDateUtils.js";
7
+ import { addDays, dayAndHourToDayjs, fmtDate, groupSelectedSlots, isFreeSelectionContiguousSameResource, isHourInTimelineSlots, monthCalendarDays, monthGrid, rangeDays, startOfWeek } from "./utils/bookingCalendarDateUtils.js";
8
8
  import { MoveConfirmModal } from "./modal/MoveConfirmModal.js";
9
+ import { BookingCalendarBlockPanel } from "./modal/BookingCalendarBlockPanel.js";
9
10
  import { buildGroupedCourtRows } from "./bookingCalendarViewShared.js";
10
- import { OverflowListModalVirtualList } from "./modal/OverflowListModal.js";
11
+ import { BookingCalendarOverflowListLayer } from "./modal/BookingCalendarOverflowListLayer.js";
11
12
  import { BookingCalendarMonthView } from "./view/BookingCalendarMonthView.js";
12
13
  import { BookingCalendarWeekView } from "./view/BookingCalendarWeekView.js";
13
14
  import { BookingCalendarDayView } from "./view/BookingCalendarDayView.js";
15
+ import { BookingCalendarResourceView } from "./view/BookingCalendarResourceView.js";
16
+ import { BookingCalendarDaySelectionBar } from "./view/BookingCalendarDaySelectionBar.js";
17
+ import { isResourceSelectionContiguousSameCourt } from "./utils/bookingCalendarSelectionUtils.js";
18
+ import { useBookingCalendarDayOverlayLayout } from "./hooks/useBookingCalendarDayOverlayLayout.js";
19
+ import { useBookingCalendarDayHourRemaining } from "./view/day/useBookingCalendarDayHourRemaining.js";
20
+ import { useBookingCalendarDaySelection } from "./view/day/useBookingCalendarDaySelection.js";
21
+ import { useBookingCalendarResourceDayHeaderRemaining } from "./view/resource/useBookingCalendarResourceDayHeaderRemaining.js";
22
+ import { useBookingCalendarResourceCellToggle } from "./view/resource/useBookingCalendarResourceCellToggle.js";
23
+ import { useBookingCalendarGoToDayFromCell } from "./view/useBookingCalendarGoToDayFromCell.js";
14
24
  import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
15
- import { DatePicker, Spin, message } from "antd";
16
- import { CloseOutlined } from "@ant-design/icons";
25
+ import { Spin, message } from "antd";
17
26
  import "./BookingCalendar.less";
18
27
  //#region src/components/pisellRecordBoard/shellFrame/Calendar/BookingCalendar.tsx
19
28
  /**
20
29
  * RecordBoard 预约日历:月 / 周 / 日三视图,资源分组与折叠、锁台、拖拽改期、日视图多 lane overlay。
21
30
  * 受控:`calendarView`、`anchorDate`、`bookings` 等见 {@link RecordBoardCalendarProps};未传时由内部 state 驱动(便于 Story)。
22
31
  */
23
- const { RangePicker } = DatePicker;
24
32
  /** 看板日历根组件:内含主题 CSS 与 Spin,按 `view` 挂载月 / 周 / 日子组件 */
25
33
  function BookingCalendar(props = {}) {
26
34
  var _hourSlots$;
@@ -55,8 +63,10 @@ function BookingCalendar(props = {}) {
55
63
  return Math.min(1440, n);
56
64
  }, [props.slotStepMinutes]);
57
65
  const slotStepHours = resolvedSlotMinutes / 60;
58
- /** 每槽一列固定为「原整点列」宽度,细粒度仅增加列数,由容器横向滚动承载 */
66
+ /** 每槽一列固定为「原整点列」宽度,细粒度仅增加列数,由容器横向滚动承载(日/周等时间轴) */
59
67
  const slotColWidth = 92;
68
+ /** 资源视图:按「日」分列,单列单独加宽 */
69
+ const resourceViewSlotColWidth = 102;
60
70
  const timelinePxPerHour = 92 / slotStepHours;
61
71
  /** 切换月/周/日:先 `onCalendarViewChange`,无受控回调时再 `setInternalView` */
62
72
  const commitView = (v) => {
@@ -82,6 +92,9 @@ function BookingCalendar(props = {}) {
82
92
  if (view === "month") {
83
93
  rangeStart = new Date(a.getFullYear(), a.getMonth(), 1);
84
94
  rangeEnd = new Date(a.getFullYear(), a.getMonth() + 1, 0);
95
+ } else if (view === "resource") {
96
+ rangeStart = new Date(a.getFullYear(), a.getMonth(), 1);
97
+ rangeEnd = new Date(a.getFullYear(), a.getMonth() + 1, 0);
85
98
  } else if (view === "week") {
86
99
  rangeStart = startOfWeek(a);
87
100
  rangeEnd = addDays(rangeStart, 6);
@@ -102,6 +115,7 @@ function BookingCalendar(props = {}) {
102
115
  ]);
103
116
  const [selectedFreeSlots, setSelectedFreeSlots] = useState([]);
104
117
  const [selectedBlockSlots, setSelectedBlockSlots] = useState([]);
118
+ const [selectedResourceCells, setSelectedResourceCells] = useState([]);
105
119
  const [focusedCourtId, setFocusedCourtId] = useState(null);
106
120
  const [showBlockPanel, setShowBlockPanel] = useState(false);
107
121
  const [blockFormName, setBlockFormName] = useState(() => tCal("pisell2.recordBoard.calendar.block.defaultTitle"));
@@ -125,11 +139,14 @@ function BookingCalendar(props = {}) {
125
139
  * 供日视图将横向 scroll 定位到「当前时间」列。
126
140
  */
127
141
  const [dayTimelineScrollToNowToken, setDayTimelineScrollToNowToken] = useState(0);
142
+ const [resourceMonthScrollToTodayToken, setResourceMonthScrollToTodayToken] = useState(0);
128
143
  const prevCalendarViewRef = useRef(null);
144
+ const prevViewForSlotIsolationRef = useRef(view);
129
145
  /** 任意入口切入 `day` 时递增一次(含首屏即为日视图:`prev === null`) */
130
146
  useLayoutEffect(() => {
131
147
  const prev = prevCalendarViewRef.current;
132
148
  if (view === "day" && (prev === null || prev !== "day")) setDayTimelineScrollToNowToken((t) => t + 1);
149
+ if (view === "resource" && (prev === null || prev !== "resource")) setResourceMonthScrollToTodayToken((t) => t + 1);
133
150
  prevCalendarViewRef.current = view;
134
151
  }, [view]);
135
152
  const blockPanelRef = useRef(null);
@@ -154,79 +171,45 @@ function BookingCalendar(props = {}) {
154
171
  resolvedSlotMinutes
155
172
  ]);
156
173
  /**
157
- * 日视图专用:按资源行上「同时段最大并行条数」算行高(`dayCourtOverlayRowHeightPx`);无预约行仍用 `dayRowHeight`;有 overlay 时不再做不小于 `dayRowHeight` 的额外下限。
158
- * 周/月不读其中大部分字段,但为单 hook 依赖仍随 `visibleCourtRows` 重算。
174
+ * 日视图 overlay 行高 / 并行 lane / +N 溢出(详见 hook 内注释)。
159
175
  */
160
- const { dayRowHeights, dayRowTopMap, overlayBookings, courtDayDisplayLaneCount, hiddenDayOverlayIdSet, dayOverlayOverflowPluses } = useMemo(() => {
161
- const defaultHeights = visibleCourtRows.map((row) => row.kind === "group" ? 38 : 44);
162
- let overlays = buildOverlayBookings(visibleCourtRows, dayKey, bookings, buildDayRowTopMap(visibleCourtRows, defaultHeights), hourSlots, defaultHeights, slotStepHours);
163
- const expandedHeights = visibleCourtRows.map((row, i) => {
164
- if (row.kind !== "court") return defaultHeights[i];
165
- if (!overlays.some((o) => o.courtId === row.id)) return defaultHeights[i];
166
- return dayCourtOverlayRowHeightPx(effectiveDayOverlayDisplayLaneCount(maxConcurrentBookingsOnCourt(overlays, row.id)));
167
- });
168
- const tops2 = buildDayRowTopMap(visibleCourtRows, expandedHeights);
169
- overlays = buildOverlayBookings(visibleCourtRows, dayKey, bookings, tops2, hourSlots, expandedHeights, slotStepHours);
170
- const courtDayDisplayLaneCount = {};
171
- const hiddenDayOverlayIdSet = /* @__PURE__ */ new Set();
172
- const dayOverlayOverflowPluses = [];
173
- for (const row of visibleCourtRows) {
174
- if (row.kind !== "court") continue;
175
- const cid = row.id;
176
- courtDayDisplayLaneCount[cid] = effectiveDayOverlayDisplayLaneCount(maxConcurrentBookingsOnCourt(overlays, cid));
177
- const clusters = clusterDayCourtOverlaysByOverlap(overlays, cid);
178
- for (const cluster of clusters) {
179
- if (cluster.length <= 3) continue;
180
- const sortedByLane = [...cluster].sort((a, b) => {
181
- var _a$lane, _b$lane;
182
- return ((_a$lane = a.lane) !== null && _a$lane !== void 0 ? _a$lane : 0) - ((_b$lane = b.lane) !== null && _b$lane !== void 0 ? _b$lane : 0) || a.start - b.start;
183
- });
184
- for (let i = 3; i < sortedByLane.length; i++) {
185
- const o = sortedByLane[i];
186
- hiddenDayOverlayIdSet.add(dayOverlayHiddenKey(o.item, dayKey, o.courtId));
187
- }
188
- const hidden = sortedByLane.slice(3);
189
- let minS = Infinity;
190
- let maxE = -Infinity;
191
- for (const o of cluster) {
192
- minS = Math.min(minS, o.start);
193
- maxE = Math.max(maxE, o.start + o.span);
194
- }
195
- const anchor = hidden[0];
196
- dayOverlayOverflowPluses.push({
197
- key: `${cid}-${minS}-${maxE}-${dayKey}`,
198
- courtId: cid,
199
- rowTop: anchor.rowTop,
200
- rowHeight: anchor.rowHeight,
201
- start: minS,
202
- span: maxE - minS,
203
- plusCount: hidden.length,
204
- overflowItems: hidden.map((o) => o.item)
205
- });
206
- }
207
- }
208
- return {
209
- dayRowHeights: expandedHeights,
210
- dayRowTopMap: tops2,
211
- overlayBookings: overlays,
212
- courtDayDisplayLaneCount,
213
- hiddenDayOverlayIdSet,
214
- dayOverlayOverflowPluses
215
- };
216
- }, [
217
- visibleCourtRows,
218
- dayKey,
219
- bookings,
220
- hourSlots,
221
- slotStepHours
222
- ]);
176
+ const { dayRowHeights, overlayBookings, courtDayDisplayLaneCount, hiddenDayOverlayIdSet, dayOverlayOverflowPluses } = useBookingCalendarDayOverlayLayout(visibleCourtRows, dayKey, bookings, hourSlots, slotStepHours);
223
177
  /** 周视图表头对应的 7 个自然日 */
224
178
  const weekDays = useMemo(() => rangeDays(anchorDate), [anchorDate]);
179
+ /** 资源视图:当月每日一列 */
180
+ const resourceMonthDays = useMemo(() => monthCalendarDays(anchorDate), [anchorDate]);
225
181
  useMemo(() => new Set(weekDays.map((d) => fmtDate(d))), [weekDays]);
226
182
  const firstTimelineHour = (_hourSlots$ = hourSlots[0]) !== null && _hourSlots$ !== void 0 ? _hourSlots$ : 0;
227
183
  const endTimelineExclusive = hourSlots.length ? hourSlots[hourSlots.length - 1] + slotStepHours : 24;
228
184
  /** 槽位起始小时是否落在当前营业/时间轴配置内 */
229
- const isBusinessHour = (h) => isHourInTimelineSlots(h, hourSlots);
185
+ const isBusinessHour = useCallback((h) => isHourInTimelineSlots(h, hourSlots), [hourSlots]);
186
+ const dayHourRemaining = useBookingCalendarDayHourRemaining(visibleCourts, dayKey, bookings, hourSlots, slotStepHours, isBusinessHour);
187
+ const resourceDayHeaderRemaining = useBookingCalendarResourceDayHeaderRemaining(resourceMonthDays, visibleCourts, bookings, hourSlots, slotStepHours);
188
+ /**
189
+ * 资源视图:选区是否可触发 Block / Create Booking。
190
+ * - 任何选中 → SelectionBar 显示「Cancel」。
191
+ * - 同 court + 连续多天(含单日) → 才显示「Block / Create」按钮。
192
+ */
193
+ const resourceSelectionContiguous = useMemo(() => isResourceSelectionContiguousSameCourt(selectedResourceCells), [selectedResourceCells]);
194
+ const resourceSelectionBarMeta = useMemo(() => {
195
+ if (selectedResourceCells.length === 0) return null;
196
+ return {
197
+ freePickCount: selectedResourceCells.length,
198
+ blockPickCount: selectedResourceCells.length,
199
+ freeAllowsBlockPanel: resourceSelectionContiguous
200
+ };
201
+ }, [selectedResourceCells, resourceSelectionContiguous]);
202
+ /** 日视图与资源视图选中隔离:切换视图时清空另一侧 */
203
+ useEffect(() => {
204
+ if (prevViewForSlotIsolationRef.current !== view) {
205
+ if (view === "day") setSelectedResourceCells([]);
206
+ if (view === "resource") {
207
+ setSelectedFreeSlots([]);
208
+ setSelectedBlockSlots([]);
209
+ }
210
+ prevViewForSlotIsolationRef.current = view;
211
+ }
212
+ }, [view]);
230
213
  /** 月历每格摘要(占用点、锁台等),供月视图渲染 */
231
214
  const monthDays = useMemo(() => monthGrid(anchorDate, bookings, courts, hourSlots, slotStepHours), [
232
215
  anchorDate,
@@ -235,29 +218,6 @@ function BookingCalendar(props = {}) {
235
218
  hourSlots,
236
219
  slotStepHours
237
220
  ]);
238
- /** 日视图表头:每个整点列上仍有空的资源数占比(按全部可见资源计,与分组折叠无关) */
239
- const dayHourRemaining = useMemo(() => {
240
- const total = visibleCourts.length;
241
- return Object.fromEntries(hourSlots.map((hour) => {
242
- if (!isBusinessHour(hour)) return [hour, {
243
- remaining: 0,
244
- total,
245
- ratio: 0
246
- }];
247
- const remaining = visibleCourts.filter((court) => !dayOccupancy(court.id, dayKey, bookings, hourSlots, slotStepHours)[hour]).length;
248
- return [hour, {
249
- remaining,
250
- total,
251
- ratio: total > 0 ? remaining / total : 0
252
- }];
253
- }));
254
- }, [
255
- visibleCourts,
256
- dayKey,
257
- bookings,
258
- hourSlots,
259
- slotStepHours
260
- ]);
261
221
  /** 日视图内容区总高度(表头 + 各资源行叠加以 overlay 抬高后的高度) */
262
222
  const dayContentHeight = 56 + dayRowHeights.reduce((sum, h) => sum + h, 0);
263
223
  /** 点击文档空白处关闭锁台面版、溢出浮层、改期确认等面板 */
@@ -281,16 +241,39 @@ function BookingCalendar(props = {}) {
281
241
  /** 锁台面版打开时若选区不再连续同资源则自动收起(避免与表单状态不一致) */
282
242
  useEffect(() => {
283
243
  if (!showBlockPanel) return;
244
+ if (view === "resource") {
245
+ if (!resourceSelectionContiguous) setShowBlockPanel(false);
246
+ return;
247
+ }
284
248
  if (!isFreeSelectionContiguousSameResource(selectedFreeSlots, slotStepHours)) setShowBlockPanel(false);
285
249
  }, [
286
250
  showBlockPanel,
251
+ view,
287
252
  selectedFreeSlots,
253
+ resourceSelectionContiguous,
288
254
  slotStepHours
289
255
  ]);
290
256
  /** 打开锁台面版时用当前选中的空闲连续段预填资源与时间范围(仅内置锁台面版) */
291
257
  useEffect(() => {
292
258
  if (props.onBlockedTimePanelRequest) return;
293
- if (!showBlockPanel || selectedFreeSlots.length === 0) return;
259
+ if (!showBlockPanel) return;
260
+ if (view === "resource") {
261
+ var _bizHours$, _bizHours;
262
+ if (!resourceSelectionContiguous) return;
263
+ const sortedDates = [...selectedResourceCells].map((s) => s.dateKey).sort();
264
+ const firstDate = sortedDates[0];
265
+ const lastDate = sortedDates[sortedDates.length - 1];
266
+ const bizHours = hourSlots.filter((h) => isHourInTimelineSlots(h, hourSlots));
267
+ const h0 = (_bizHours$ = bizHours[0]) !== null && _bizHours$ !== void 0 ? _bizHours$ : 0;
268
+ const hLast = (_bizHours = bizHours[bizHours.length - 1]) !== null && _bizHours !== void 0 ? _bizHours : h0;
269
+ const slotStep = slotStepHours > 0 ? slotStepHours : 1;
270
+ setBlockResourceId(String(selectedResourceCells[0].courtId));
271
+ setBlockTimeRange([dayAndHourToDayjs(firstDate, h0), dayAndHourToDayjs(lastDate, hLast + slotStep)]);
272
+ setBlockFormName(tCal("pisell2.recordBoard.calendar.block.defaultTitle"));
273
+ setBlockModalNote("");
274
+ return;
275
+ }
276
+ if (selectedFreeSlots.length === 0) return;
294
277
  const g = groupSelectedSlots(selectedFreeSlots, slotStepHours)[0];
295
278
  if (!g) return;
296
279
  setBlockResourceId(String(g.courtId));
@@ -299,8 +282,13 @@ function BookingCalendar(props = {}) {
299
282
  setBlockModalNote("");
300
283
  }, [
301
284
  showBlockPanel,
285
+ view,
302
286
  selectedFreeSlots,
303
- slotStepHours
287
+ selectedResourceCells,
288
+ resourceSelectionContiguous,
289
+ hourSlots,
290
+ slotStepHours,
291
+ props.onBlockedTimePanelRequest
304
292
  ]);
305
293
  /** 周/月跳入日视图时的短动画标记,超时清除 */
306
294
  useEffect(() => {
@@ -308,47 +296,15 @@ function BookingCalendar(props = {}) {
308
296
  const timer = setTimeout(() => setTransitioningToDay(null), 280);
309
297
  return () => clearTimeout(timer);
310
298
  }, [transitioningToDay]);
311
- /** 当前日锚定下,该资源该槽是否在「空闲多选」集合中 */
312
- function isFreeSelected(courtId, hour) {
313
- return selectedFreeSlots.some((slot) => slot.courtId === courtId && slot.date === dayKey && slot.hour === hour);
314
- }
315
- /** 当前日锚定下,该资源该槽是否在「锁台多选」集合中 */
316
- function isBlockSelected(courtId, hour) {
317
- return selectedBlockSlots.some((slot) => slot.courtId === courtId && slot.date === dayKey && slot.hour === hour);
318
- }
319
- /** 从周/月单元格调起日视图:更新锚定日、可选聚焦资源、记录过渡动画方向 */
320
- function goToDayFromCell(day, courtId, source) {
321
- setNavDirection(day > anchorDate ? 1 : -1);
322
- setTransitioningToDay({ source });
323
- /** 周/月只是“跳转查看”,不应把日视图多选状态带过去造成误解(像是系统替用户选了格子) */
324
- setSelectedFreeSlots([]);
325
- setSelectedBlockSlots([]);
326
- commitAnchor(day);
327
- setFocusedCourtId(courtId || null);
328
- commitView("day");
329
- }
299
+ const goToDayFromCell = useBookingCalendarGoToDayFromCell(anchorDate, commitAnchor, commitView, setNavDirection, setTransitioningToDay, setSelectedFreeSlots, setSelectedBlockSlots, setSelectedResourceCells, setFocusedCourtId);
300
+ const toggleResourceCell = useBookingCalendarResourceCellToggle(setSelectedResourceCells);
301
+ const { isFreeSelected, isBlockSelected, toggleSlot } = useBookingCalendarDaySelection(dayKey, bookings, hourSlots, slotStepHours, courts, selectedFreeSlots, selectedBlockSlots, setSelectedFreeSlots, setSelectedBlockSlots);
330
302
  /** 卡片详情:业务若 `onBookingClick` 返回 true 则视为已消费;否则关闭溢出气泡 */
331
- function openBookingDetail(item) {
303
+ const openBookingDetail = useCallback((item) => {
332
304
  var _props$onBookingClick;
333
305
  if (((_props$onBookingClick = props.onBookingClick) === null || _props$onBookingClick === void 0 ? void 0 : _props$onBookingClick.call(props, item)) === true) return;
334
306
  setOverflowPopover(null);
335
- }
336
- /** 日视图格子点击:空闲槽进多选;锁台槽进锁台多选;已约不动 */
337
- function toggleSlot(courtId, hour) {
338
- const existing = dayOccupancy(courtId, dayKey, bookings, hourSlots, slotStepHours)[hour];
339
- const court = courts.find((c) => c.id === courtId);
340
- const slot = {
341
- courtId,
342
- court: court === null || court === void 0 ? void 0 : court.name,
343
- date: dayKey,
344
- hour
345
- };
346
- if (!existing) {
347
- setSelectedFreeSlots((prev) => prev.some((s) => sameSlot(s, slot)) ? prev.filter((s) => !sameSlot(s, slot)) : [...prev, slot]);
348
- return;
349
- }
350
- if (existing.kind === "block") setSelectedBlockSlots((prev) => prev.some((s) => sameSlot(s, slot)) ? prev.filter((s) => !sameSlot(s, slot)) : [...prev, slot]);
351
- }
307
+ }, [props.onBookingClick]);
352
308
  /** 清空空闲与锁台多选 */
353
309
  function deselectAll() {
354
310
  setSelectedFreeSlots([]);
@@ -356,9 +312,27 @@ function BookingCalendar(props = {}) {
356
312
  }
357
313
  /** 将连续空闲选区合并为一条或多条本地预约(演示/未接写接口时) */
358
314
  function createBookingFromSelection() {
359
- if (!allowPersist("createBooking")) return;
360
315
  if (selectedFreeSlots.length === 0) return;
361
- const newBookings = groupSelectedSlots(selectedFreeSlots, slotStepHours).map((slot, idx) => ({
316
+ const groups = groupSelectedSlots(selectedFreeSlots, slotStepHours);
317
+ const host = props.onCreateBookingFromSelection;
318
+ if (host) {
319
+ _asyncToGenerator(function* () {
320
+ try {
321
+ if ((yield Promise.resolve(host({
322
+ mode: "day",
323
+ groups: groups.map((g) => ({
324
+ courtId: g.courtId,
325
+ date: g.date,
326
+ startHour: g.startHour,
327
+ endHour: g.endHour
328
+ }))
329
+ }))) !== false) setSelectedFreeSlots([]);
330
+ } catch (_unused) {}
331
+ })();
332
+ return;
333
+ }
334
+ if (!allowPersist("createBooking")) return;
335
+ const newBookings = groups.map((slot, idx) => ({
362
336
  id: `new-${slot.courtId}-${slot.date}-${slot.startHour}-${idx}`,
363
337
  courtId: slot.courtId,
364
338
  date: slot.date,
@@ -370,17 +344,48 @@ function BookingCalendar(props = {}) {
370
344
  setBookings((prev) => [...newBookings, ...prev]);
371
345
  setSelectedFreeSlots([]);
372
346
  }
373
- /** 按多选锁台槽从本地 bookings 中移除对应 block 片段 */
374
- function openSelectedBlocks() {
375
- if (!allowPersist("removeBlocks")) return;
376
- if (selectedBlockSlots.length === 0) return;
377
- const selectedSet = new Set(selectedBlockSlots.map((s) => `${s.courtId}-${s.date}-${s.hour}`));
378
- setBookings((prev) => prev.filter((item) => {
379
- if (item.kind !== "block") return true;
380
- getBookingSegmentForDate(item, item.date);
381
- return !hourSlots.filter((slotStart) => slotStart >= item.startHour - 1e-9 && slotStart < item.endHour - 1e-9).some((h) => selectedSet.has(`${item.courtId}-${item.date}-${h}`));
382
- }));
383
- setSelectedBlockSlots([]);
347
+ /**
348
+ * 资源视图:连续多天 → 一条跨日整天预约,时间 = 营业开始 → 营业结束。
349
+ * 单日 → 同样按整天写入。仅在 `resourceSelectionContiguous` 为 true 时执行。
350
+ */
351
+ function createBookingFromResourceSelection() {
352
+ var _bizHours$2, _bizHours2;
353
+ if (!resourceSelectionContiguous) return;
354
+ const sortedDates = [...selectedResourceCells].map((s) => s.dateKey).sort();
355
+ const firstDate = sortedDates[0];
356
+ const lastDate = sortedDates[sortedDates.length - 1];
357
+ const courtId = selectedResourceCells[0].courtId;
358
+ const bizHours = hourSlots.filter((h) => isHourInTimelineSlots(h, hourSlots));
359
+ const h0 = (_bizHours$2 = bizHours[0]) !== null && _bizHours$2 !== void 0 ? _bizHours$2 : 0;
360
+ const endHour = ((_bizHours2 = bizHours[bizHours.length - 1]) !== null && _bizHours2 !== void 0 ? _bizHours2 : h0) + (slotStepHours > 0 ? slotStepHours : 1);
361
+ const host = props.onCreateBookingFromSelection;
362
+ if (host) {
363
+ _asyncToGenerator(function* () {
364
+ try {
365
+ if ((yield Promise.resolve(host({
366
+ mode: "resource",
367
+ courtId,
368
+ dates: sortedDates,
369
+ startHour: h0,
370
+ endHour
371
+ }))) !== false) setSelectedResourceCells([]);
372
+ } catch (_unused2) {}
373
+ })();
374
+ return;
375
+ }
376
+ if (!allowPersist("createBooking")) return;
377
+ const newBooking = {
378
+ id: `new-${courtId}-${firstDate}-${Date.now()}`,
379
+ courtId,
380
+ date: firstDate,
381
+ endDate: lastDate !== firstDate ? lastDate : void 0,
382
+ startHour: h0,
383
+ endHour,
384
+ name: tCal("pisell2.recordBoard.calendar.common.newBookingName"),
385
+ kind: "booking"
386
+ };
387
+ setBookings((prev) => [newBooking, ...prev]);
388
+ setSelectedResourceCells([]);
384
389
  }
385
390
  /** 屏蔽:宿主 lock 弹窗,或内置锁台面版 */
386
391
  const handleOpenBlockPanelFromSelection = useCallback(_asyncToGenerator(function* () {
@@ -389,20 +394,21 @@ function BookingCalendar(props = {}) {
389
394
  if (!g) return;
390
395
  if (props.onBlockedTimePanelRequest) {
391
396
  try {
397
+ const slotsPayload = selectedFreeSlots.map((s) => {
398
+ const slot = {
399
+ courtId: s.courtId,
400
+ date: s.date,
401
+ hour: s.hour
402
+ };
403
+ const extra = s;
404
+ if (extra.court !== void 0 && extra.court !== "") slot.court = extra.court;
405
+ return slot;
406
+ });
392
407
  const payload = {
393
408
  slotStepHours,
394
409
  slotStepMinutes: resolvedSlotMinutes,
395
- dayKey,
396
- selectedFreeSlots: selectedFreeSlots.map((s) => {
397
- const slot = {
398
- courtId: s.courtId,
399
- date: s.date,
400
- hour: s.hour
401
- };
402
- const extra = s;
403
- if (extra.court !== void 0 && extra.court !== "") slot.court = extra.court;
404
- return slot;
405
- }),
410
+ dayKey: g.date,
411
+ selectedFreeSlots: slotsPayload,
406
412
  mergedRange: {
407
413
  courtId: g.courtId,
408
414
  date: g.date,
@@ -411,7 +417,7 @@ function BookingCalendar(props = {}) {
411
417
  }
412
418
  };
413
419
  if ((yield props.onBlockedTimePanelRequest(payload)) !== false) setSelectedFreeSlots([]);
414
- } catch (_unused) {}
420
+ } catch (_unused3) {}
415
421
  return;
416
422
  }
417
423
  setShowBlockPanel(true);
@@ -419,7 +425,56 @@ function BookingCalendar(props = {}) {
419
425
  selectedFreeSlots,
420
426
  slotStepHours,
421
427
  resolvedSlotMinutes,
422
- dayKey,
428
+ props.onBlockedTimePanelRequest
429
+ ]);
430
+ /**
431
+ * 资源视图:仅在「同 court + 连续多天」时打开 Block 面板,按整天预填资源 + 跨日时间范围。
432
+ */
433
+ const handleOpenBlockPanelFromResource = useCallback(_asyncToGenerator(function* () {
434
+ var _bizHours$3, _bizHours3;
435
+ if (!resourceSelectionContiguous) return;
436
+ const sortedDates = [...selectedResourceCells].map((s) => s.dateKey).sort();
437
+ const firstDate = sortedDates[0];
438
+ const lastDate = sortedDates[sortedDates.length - 1];
439
+ const courtId = selectedResourceCells[0].courtId;
440
+ const court = courts.find((c) => c.id === courtId);
441
+ const bizHours = hourSlots.filter((h) => isHourInTimelineSlots(h, hourSlots));
442
+ const h0 = (_bizHours$3 = bizHours[0]) !== null && _bizHours$3 !== void 0 ? _bizHours$3 : 0;
443
+ const hLast = (_bizHours3 = bizHours[bizHours.length - 1]) !== null && _bizHours3 !== void 0 ? _bizHours3 : h0;
444
+ const slotStep = slotStepHours > 0 ? slotStepHours : 1;
445
+ const startHour = h0;
446
+ const endHour = hLast + slotStep;
447
+ if (props.onBlockedTimePanelRequest) {
448
+ try {
449
+ const payload = {
450
+ slotStepHours,
451
+ slotStepMinutes: resolvedSlotMinutes,
452
+ dayKey: firstDate,
453
+ selectedFreeSlots: sortedDates.map((d) => _objectSpread2({
454
+ courtId,
455
+ date: d,
456
+ hour: startHour
457
+ }, (court === null || court === void 0 ? void 0 : court.name) ? { court: court.name } : {})),
458
+ mergedRange: {
459
+ courtId,
460
+ date: firstDate,
461
+ endDate: lastDate !== firstDate ? lastDate : void 0,
462
+ startHour,
463
+ endHour
464
+ }
465
+ };
466
+ if ((yield props.onBlockedTimePanelRequest(payload)) !== false) setSelectedResourceCells([]);
467
+ } catch (_unused4) {}
468
+ return;
469
+ }
470
+ setShowBlockPanel(true);
471
+ }), [
472
+ selectedResourceCells,
473
+ resourceSelectionContiguous,
474
+ hourSlots,
475
+ slotStepHours,
476
+ courts,
477
+ resolvedSlotMinutes,
423
478
  props.onBlockedTimePanelRequest
424
479
  ]);
425
480
  /** 提交锁台:优先走 `onBlockedTimeSave`;否则写入本地 block 片段 */
@@ -458,6 +513,7 @@ function BookingCalendar(props = {}) {
458
513
  if ((yield props.onBlockedTimeSave(payload)) === false) return;
459
514
  setShowBlockPanel(false);
460
515
  setSelectedFreeSlots([]);
516
+ setSelectedResourceCells([]);
461
517
  setBlockModalNote("");
462
518
  } finally {
463
519
  setBlockSaving(false);
@@ -484,18 +540,19 @@ function BookingCalendar(props = {}) {
484
540
  if (!allowPersist("createBlock")) return;
485
541
  setBookings((prev) => [nextBlock, ...prev]);
486
542
  setSelectedFreeSlots([]);
543
+ setSelectedResourceCells([]);
487
544
  setShowBlockPanel(false);
488
545
  setBlockModalNote("");
489
546
  });
490
547
  return _saveBlockedTime.apply(this, arguments);
491
548
  }
492
549
  /** 拖拽改期松手:宿主接管改约,或弹出内置确认框 */
493
- function handleDropMove(targetCourtId, targetDate, targetHour) {
550
+ const handleDropMove = useCallback((targetCourtId, targetDate, targetHour) => {
494
551
  if (!draggingItem) return;
495
552
  const duration = draggingItem.endHour - draggingItem.startHour;
496
553
  const nextStart = targetHour;
497
554
  const nextEnd = targetHour + duration;
498
- if (!isBusinessHour(nextStart) || nextEnd > endTimelineExclusive) return;
555
+ if (!isHourInTimelineSlots(nextStart, hourSlots) || nextEnd > endTimelineExclusive) return;
499
556
  const targetCourt = targetCourtId !== null && targetCourtId !== void 0 ? targetCourtId : draggingItem.courtId;
500
557
  const itemSnapshot = draggingItem;
501
558
  const draft = {
@@ -518,12 +575,17 @@ function BookingCalendar(props = {}) {
518
575
  targetHour
519
576
  });
520
577
  setMoveDraft(draft);
521
- }
578
+ }, [
579
+ draggingItem,
580
+ hourSlots,
581
+ endTimelineExclusive,
582
+ props.onBookingMoveRequest
583
+ ]);
522
584
  /** 周/月格 drop:保持原开始小时,仅换日或资源 */
523
- function handleGridDropMove(targetDate, targetCourtId = null) {
585
+ const handleGridDropMove = useCallback((targetDate, targetCourtId = null) => {
524
586
  if (!draggingItem || draggingItem.kind === "block") return;
525
587
  handleDropMove(targetCourtId !== null && targetCourtId !== void 0 ? targetCourtId : draggingItem.courtId, targetDate, draggingItem.startHour);
526
- }
588
+ }, [draggingItem, handleDropMove]);
527
589
  /** 确认改期:在 bookings 中平移该条的起止小时与 court/date */
528
590
  function confirmMove() {
529
591
  if (!moveConfirm || !moveDraft) return;
@@ -582,6 +644,29 @@ function BookingCalendar(props = {}) {
582
644
  overflowListModal,
583
645
  setOverflowListModal,
584
646
  overflowPanelRef
647
+ }), view === "resource" && /* @__PURE__ */ React.createElement(BookingCalendarResourceView, {
648
+ calendarProps: props,
649
+ isLight,
650
+ anchorDate,
651
+ monthDays: resourceMonthDays,
652
+ slotColWidth: resourceViewSlotColWidth,
653
+ bookings,
654
+ hourSlots,
655
+ slotStepHours,
656
+ visibleCourts,
657
+ visibleCourtRows,
658
+ handleGridDropMove,
659
+ openBookingDetail,
660
+ setDraggingItem,
661
+ setCollapsedGroups,
662
+ overflowPopover,
663
+ setOverflowPopover,
664
+ setOverflowListModal,
665
+ overflowPanelRef,
666
+ resourceMonthScrollToTodayToken,
667
+ resourceDayHeaderRemaining,
668
+ toggleResourceCell,
669
+ selectedResourceCells
585
670
  }), view === "day" && /* @__PURE__ */ React.createElement(BookingCalendarDayView, {
586
671
  calendarProps: props,
587
672
  isLight,
@@ -619,73 +704,38 @@ function BookingCalendar(props = {}) {
619
704
  setOverflowListModal,
620
705
  selectedFreeSlots,
621
706
  selectedBlockSlots,
622
- deselectAll,
623
- openSelectedBlocks,
624
- onOpenBlockPanel: handleOpenBlockPanelFromSelection,
625
- createBookingFromSelection,
626
707
  focusedCourtId,
627
708
  dayTimelineScrollToNowToken
628
- }))), showBlockPanel && !props.onBlockedTimePanelRequest && selectedFreeSlots.length > 0 && isFreeSelectionContiguousSameResource(selectedFreeSlots, slotStepHours) && /* @__PURE__ */ React.createElement("div", { className: "fixed inset-0 z-50 bg-black/55 backdrop-blur-sm flex items-end md:items-center justify-center p-2 md:p-4" }, /* @__PURE__ */ React.createElement("div", {
629
- ref: blockPanelRef,
630
- className: `w-full max-w-[720px] max-h-[92vh] overflow-y-auto rounded-t-3xl md:rounded-3xl border p-4 md:p-6 shadow-2xl ${isLight ? "theme-panel theme-border soft-shadow" : "border-zinc-700 bg-[#121217]"}`
631
- }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 flex items-center justify-between gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "text-xl font-semibold" }, tCal("pisell2.recordBoard.calendar.block.panelTitle")), /* @__PURE__ */ React.createElement("button", {
632
- type: "button",
633
- "aria-label": tCal("pisell2.recordBoard.calendar.block.closeAria"),
634
- onClick: () => setShowBlockPanel(false),
635
- className: `rounded-xl p-2 ${isLight ? "btn-neutral" : "border border-zinc-700 text-zinc-300"}`
636
- }, /* @__PURE__ */ React.createElement(CloseOutlined, { style: { fontSize: 20 } }))), /* @__PURE__ */ React.createElement("div", { className: `space-y-4 rounded-2xl border p-4 ${isLight ? "theme-panel-alt theme-border" : "border-zinc-700 bg-[#171820]"}` }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", { className: `mb-2 block text-sm ${isLight ? "theme-text-muted" : "text-zinc-400"}` }, tCal("pisell2.recordBoard.calendar.block.fieldTitle")), /* @__PURE__ */ React.createElement("input", {
637
- value: blockFormName,
638
- onChange: (e) => setBlockFormName(e.target.value),
639
- placeholder: tCal("pisell2.recordBoard.calendar.block.placeholderTitle"),
640
- className: `w-full rounded-xl px-3 py-2 outline-none ${isLight ? "theme-input theme-text" : "border border-zinc-700 bg-[#111218] text-white"}`
641
- })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", { className: `mb-2 block text-sm ${isLight ? "theme-text-muted" : "text-zinc-400"}` }, "备注"), /* @__PURE__ */ React.createElement("input", {
642
- value: blockModalNote,
643
- onChange: (e) => setBlockModalNote(e.target.value),
644
- placeholder: "选填",
645
- className: `w-full rounded-xl px-3 py-2 outline-none ${isLight ? "theme-input theme-text" : "border border-zinc-700 bg-[#111218] text-white"}`
646
- })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", { className: `mb-2 block text-sm ${isLight ? "theme-text-muted" : "text-zinc-400"}` }, tCal("pisell2.recordBoard.calendar.block.fieldResource")), /* @__PURE__ */ React.createElement("select", {
647
- value: blockResourceId,
648
- onChange: (e) => setBlockResourceId(e.target.value),
649
- className: `w-full rounded-xl px-3 py-2 outline-none ${isLight ? "theme-input theme-text" : "border border-zinc-700 bg-[#111218] text-white"}`
650
- }, /* @__PURE__ */ React.createElement("option", { value: "" }, tCal("pisell2.recordBoard.calendar.block.noResource")), visibleCourts.map((c) => /* @__PURE__ */ React.createElement("option", {
651
- key: c.id,
652
- value: String(c.id)
653
- }, c.name)))), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("label", { className: `mb-2 block text-sm ${isLight ? "theme-text-muted" : "text-zinc-400"}` }, tCal("pisell2.recordBoard.calendar.block.fieldTimeRange")), /* @__PURE__ */ React.createElement(RangePicker, {
654
- style: { width: "100%" },
655
- showTime: {
656
- format: "HH:mm",
657
- minuteStep: resolvedSlotMinutes >= 60 ? 60 : Math.max(1, resolvedSlotMinutes)
658
- },
659
- format: "YYYY-MM-DD HH:mm",
660
- value: blockTimeRange !== null && blockTimeRange !== void 0 ? blockTimeRange : void 0,
661
- onChange: (v) => {
662
- var _ref;
663
- return setBlockTimeRange((_ref = v) !== null && _ref !== void 0 ? _ref : null);
664
- }
665
- }))), /* @__PURE__ */ React.createElement("div", { className: "mt-6 flex flex-col-reverse sm:flex-row items-stretch sm:items-center justify-end gap-3" }, /* @__PURE__ */ React.createElement("button", {
666
- type: "button",
667
- disabled: blockSaving,
668
- onClick: () => setShowBlockPanel(false),
669
- className: `rounded-xl px-4 py-2 ${isLight ? "btn-neutral" : "border border-zinc-700 text-zinc-300"}`
670
- }, tCal("pisell2.recordBoard.calendar.common.cancel")), /* @__PURE__ */ React.createElement("button", {
671
- type: "button",
672
- disabled: blockSaving,
673
- onClick: () => void saveBlockedTime(),
674
- className: `rounded-xl px-5 py-3 text-sm font-semibold disabled:opacity-50 ${isLight ? "btn-brand" : "bg-violet-600 text-white"}`
675
- }, blockSaving ? tCal("pisell2.recordBoard.calendar.common.saving") : tCal("pisell2.recordBoard.calendar.common.save"))))), overflowListModal && /* @__PURE__ */ React.createElement("div", { className: "fixed inset-0 z-[69] bg-black/45 backdrop-blur-sm flex items-center justify-center p-4" }, /* @__PURE__ */ React.createElement("div", {
676
- ref: overflowPanelRef,
677
- className: `flex w-full max-w-[520px] max-h-[72vh] flex-col overflow-hidden rounded-3xl border p-5 shadow-2xl ${isLight ? "theme-panel theme-border soft-shadow" : "border-zinc-700 bg-[#121217]"}`
678
- }, /* @__PURE__ */ React.createElement("div", { className: "mb-4 flex shrink-0 items-start justify-between" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "text-xl font-semibold" }, tCal("pisell2.recordBoard.calendar.overflow.bookingsForDay")), /* @__PURE__ */ React.createElement("div", { className: "mt-1 text-sm text-zinc-400" }, overflowListModal.title)), /* @__PURE__ */ React.createElement("button", {
679
- onClick: () => setOverflowListModal(null),
680
- className: "rounded-xl border border-zinc-700 px-3 py-2 text-zinc-300"
681
- }, tCal("pisell2.recordBoard.calendar.common.close"))), /* @__PURE__ */ React.createElement("div", { className: "min-h-0 flex-1 overflow-hidden px-0.5" }, /* @__PURE__ */ React.createElement(OverflowListModalVirtualList, {
682
- items: overflowListModal.items,
683
- dateKey: overflowListModal.date,
684
- listWidth: 468,
685
- onPickItem: openBookingDetail,
709
+ }))), view === "day" && (selectedFreeSlots.length > 0 || selectedBlockSlots.length > 0) || view === "resource" && selectedResourceCells.length > 0 ? /* @__PURE__ */ React.createElement(BookingCalendarDaySelectionBar, {
710
+ slotStepHours,
711
+ selectedFreeSlots: view === "resource" ? [] : selectedFreeSlots,
712
+ resourceSelectionSummary: view === "resource" ? resourceSelectionBarMeta : void 0,
713
+ deselectAll: view === "resource" ? () => setSelectedResourceCells([]) : deselectAll,
714
+ onOpenBlockPanel: view === "resource" ? handleOpenBlockPanelFromResource : handleOpenBlockPanelFromSelection,
715
+ createBookingFromSelection: view === "resource" ? createBookingFromResourceSelection : createBookingFromSelection
716
+ }) : null, showBlockPanel && !props.onBlockedTimePanelRequest && (view === "resource" ? resourceSelectionContiguous : selectedFreeSlots.length > 0 && isFreeSelectionContiguousSameResource(selectedFreeSlots, slotStepHours)) && /* @__PURE__ */ React.createElement(BookingCalendarBlockPanel, {
717
+ blockPanelRef,
718
+ isLight,
719
+ blockFormName,
720
+ setBlockFormName,
721
+ blockModalNote,
722
+ setBlockModalNote,
723
+ blockResourceId,
724
+ setBlockResourceId,
725
+ blockTimeRange,
726
+ setBlockTimeRange,
727
+ resolvedSlotMinutes,
728
+ visibleCourts,
729
+ blockSaving,
730
+ onClose: () => setShowBlockPanel(false),
731
+ onSave: saveBlockedTime
732
+ }), overflowListModal && /* @__PURE__ */ React.createElement(BookingCalendarOverflowListLayer, {
733
+ overflowPanelRef,
734
+ isLight,
735
+ modal: overflowListModal,
686
736
  onClose: () => setOverflowListModal(null),
687
- showStatusUi: Boolean(overflowListModal.scope && String(overflowListModal.scope).startsWith("day-lane-overflow-"))
688
- })))), moveConfirm && /* @__PURE__ */ React.createElement(MoveConfirmModal, {
737
+ onPickItem: openBookingDetail
738
+ }), moveConfirm && /* @__PURE__ */ React.createElement(MoveConfirmModal, {
689
739
  moveConfirm,
690
740
  moveDraft,
691
741
  setMoveDraft,