@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
@@ -0,0 +1,614 @@
1
+ const require_runtime = require("../../../../../_virtual/_rolldown/runtime.js");
2
+ const require_bookingCalendarConstants = require("../const/bookingCalendarConstants.js");
3
+ const require_calendarI18n = require("../calendarI18n.js");
4
+ const require_bookingCalendarDateUtils = require("../utils/bookingCalendarDateUtils.js");
5
+ const require_BookingCalendarStyle = require("../utils/BookingCalendarStyle.js");
6
+ const require_bookingCalendarViewShared = require("../bookingCalendarViewShared.js");
7
+ const require_BookingCalendarGroupRowHeader = require("./BookingCalendarGroupRowHeader.js");
8
+ let react = require("react");
9
+ react = require_runtime.__toESM(react);
10
+ //#region src/components/pisellRecordBoard/shellFrame/Calendar/view/BookingCalendarResourceView.tsx
11
+ /**
12
+ * 资源视图(按月):与日视图同款骨架——冻结左列 + 横向滚动 + sticky 顶部表头。
13
+ *
14
+ * 与日视图的唯一差异:
15
+ * - 日视图:横轴 = 营业小时槽,cell = 一个小时;booking 通过 overlay 跨 cell 横铺。
16
+ * - 资源视图:横轴 = 当月每一天,cell = 一个 (court, date);booking 通过 overlay 在 cell 内
17
+ * 垂直 stack 渲染(最多 3 条 + `+N` 溢出 tile)。
18
+ *
19
+ * 性能策略(与日视图对齐):
20
+ * - cell 是「轻 placeholder」:只渲染背景色 + selected ring + 空 dashed slot/Selected 文本。
21
+ * - 所有 booking 卡片 / `+N` tile 由 `BookingOverlay` 单独 absolute 渲染,独立于 grid。
22
+ * - 这样 grid 内 DOM 极轻,浏览器每帧 paint/layout 与日视图同档(cell 内含复杂 booking
23
+ * 卡片时实测每列 ~7ms paint 成本,整月 30 列 → ~150ms;overlay 化后接近零)。
24
+ *
25
+ * 选中:与日视图 `toggleSlot(courtId, hour)` 等价的「`toggleResourceCell(courtId, dateKey)`」。
26
+ * 选中态合并到 cell 自身 className(与日视图一致),不插入额外 grid item,避免 CSS Grid 整表 reflow。
27
+ */
28
+ const PREVIEW_CAP = 3;
29
+ const BookingCalendarResourceView = react.default.memo(BookingCalendarResourceViewInner);
30
+ function BookingCalendarResourceViewInner({ calendarProps, isLight, anchorDate, monthDays, slotColWidth, bookings, visibleCourts, visibleCourtRows, hourSlots, slotStepHours, handleGridDropMove, openBookingDetail, setDraggingItem, setCollapsedGroups, overflowPopover, setOverflowPopover, setOverflowListModal, overflowPanelRef, resourceMonthScrollToTodayToken, resourceDayHeaderRemaining, toggleResourceCell, selectedResourceCells }) {
31
+ const props = calendarProps;
32
+ const todayKey = require_bookingCalendarDateUtils.fmtDate(/* @__PURE__ */ new Date());
33
+ const monthKeyPrefix = `${anchorDate.getFullYear()}-${anchorDate.getMonth()}`;
34
+ const bookingsStable = (0, react.useMemo)(() => bookings, [(0, react.useMemo)(() => {
35
+ if (!Array.isArray(bookings) || bookings.length === 0) return "[]";
36
+ return bookings.map((b) => {
37
+ var _b$id, _b$courtId, _b$date, _b$kind, _b$startHour, _b$endHour;
38
+ return `${(_b$id = b === null || b === void 0 ? void 0 : b.id) !== null && _b$id !== void 0 ? _b$id : ""}|${(_b$courtId = b === null || b === void 0 ? void 0 : b.courtId) !== null && _b$courtId !== void 0 ? _b$courtId : ""}|${(_b$date = b === null || b === void 0 ? void 0 : b.date) !== null && _b$date !== void 0 ? _b$date : ""}|${(_b$kind = b === null || b === void 0 ? void 0 : b.kind) !== null && _b$kind !== void 0 ? _b$kind : ""}|${(_b$startHour = b === null || b === void 0 ? void 0 : b.startHour) !== null && _b$startHour !== void 0 ? _b$startHour : ""}|${(_b$endHour = b === null || b === void 0 ? void 0 : b.endHour) !== null && _b$endHour !== void 0 ? _b$endHour : ""}`;
39
+ }).join(";");
40
+ }, [bookings])]);
41
+ const scrollRef = (0, react.useRef)(null);
42
+ const dragRef = (0, react.useRef)({
43
+ dragging: false,
44
+ startX: 0,
45
+ startY: 0,
46
+ startLeft: 0,
47
+ startTop: 0,
48
+ moved: false
49
+ });
50
+ const [dragging, setDragging] = (0, react.useState)(false);
51
+ const [virt, setVirt] = (0, react.useState)({
52
+ i0: 0,
53
+ i1: 0,
54
+ r0: 0,
55
+ r1: 0,
56
+ ready: false
57
+ });
58
+ const rafRef = (0, react.useRef)(null);
59
+ /** 行前缀和 ref(updateVirt 内通过 ref 读取最新值,避免每次行高变化都重建 callback) */
60
+ const rowPrefixRef = (0, react.useRef)([0]);
61
+ const nCols = monthDays.length;
62
+ const updateVirt = (0, react.useCallback)(() => {
63
+ const root = scrollRef.current;
64
+ if (!root || nCols <= 0) return;
65
+ const sl = root.scrollLeft;
66
+ const st = root.scrollTop;
67
+ const vw = root.clientWidth;
68
+ const vh = root.clientHeight;
69
+ let i0 = Math.floor((sl - 132) / slotColWidth) - 5;
70
+ let i1 = Math.ceil((sl + vw - 132) / slotColWidth) - 1 + 5;
71
+ i0 = Math.max(0, i0);
72
+ i1 = Math.min(nCols - 1, i1);
73
+ if (i0 > i1) {
74
+ i0 = 0;
75
+ i1 = nCols - 1;
76
+ }
77
+ const pfx = rowPrefixRef.current;
78
+ const nr = Math.max(0, pfx.length - 1);
79
+ let r0 = 0;
80
+ let r1 = nr > 0 ? nr - 1 : 0;
81
+ if (nr > 0) {
82
+ const bodyTop = 56;
83
+ const vTop = st;
84
+ const vBot = st + vh;
85
+ const buf = 240;
86
+ for (let r = 0; r < nr; r += 1) {
87
+ var _pfx;
88
+ if (bodyTop + ((_pfx = pfx[r + 1]) !== null && _pfx !== void 0 ? _pfx : 0) > vTop - buf) {
89
+ r0 = r;
90
+ break;
91
+ }
92
+ }
93
+ for (let r = nr - 1; r >= 0; r -= 1) {
94
+ var _pfx$r;
95
+ if (bodyTop + ((_pfx$r = pfx[r]) !== null && _pfx$r !== void 0 ? _pfx$r : 0) < vBot + buf) {
96
+ r1 = r;
97
+ break;
98
+ }
99
+ }
100
+ if (r0 > r1) {
101
+ r0 = 0;
102
+ r1 = nr - 1;
103
+ }
104
+ }
105
+ setVirt((prev) => prev.ready && prev.i0 === i0 && prev.i1 === i1 && prev.r0 === r0 && prev.r1 === r1 ? prev : {
106
+ i0,
107
+ i1,
108
+ r0,
109
+ r1,
110
+ ready: true
111
+ });
112
+ }, [nCols, slotColWidth]);
113
+ const scheduleVirt = (0, react.useCallback)(() => {
114
+ if (rafRef.current != null) return;
115
+ rafRef.current = requestAnimationFrame(() => {
116
+ rafRef.current = null;
117
+ updateVirt();
118
+ });
119
+ }, [updateVirt]);
120
+ (0, react.useLayoutEffect)(() => {
121
+ const root = scrollRef.current;
122
+ if (!root) return void 0;
123
+ scheduleVirt();
124
+ const onScroll = () => scheduleVirt();
125
+ root.addEventListener("scroll", onScroll, { passive: true });
126
+ let ro = null;
127
+ if (typeof ResizeObserver !== "undefined") {
128
+ ro = new ResizeObserver(() => scheduleVirt());
129
+ try {
130
+ ro.observe(root);
131
+ } catch (_unused) {}
132
+ }
133
+ return () => {
134
+ root.removeEventListener("scroll", onScroll);
135
+ ro === null || ro === void 0 || ro.disconnect();
136
+ if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
137
+ rafRef.current = null;
138
+ };
139
+ }, [
140
+ monthKeyPrefix,
141
+ scheduleVirt,
142
+ nCols
143
+ ]);
144
+ /** 切入资源视图时把今天列居中(仅当月含今天) */
145
+ (0, react.useLayoutEffect)(() => {
146
+ if (!resourceMonthScrollToTodayToken) return;
147
+ const root = scrollRef.current;
148
+ if (!root || monthDays.length === 0) return;
149
+ const idx = monthDays.findIndex((d) => require_bookingCalendarDateUtils.fmtDate(d) === require_bookingCalendarDateUtils.getViewNowReference().dateKey);
150
+ if (idx < 0) return;
151
+ const targetLeft = 132 + idx * slotColWidth - root.clientWidth / 2 + slotColWidth / 2;
152
+ root.scrollLeft = Math.max(0, Math.min(targetLeft, root.scrollWidth - root.clientWidth));
153
+ }, [
154
+ resourceMonthScrollToTodayToken,
155
+ monthKeyPrefix,
156
+ monthDays,
157
+ slotColWidth
158
+ ]);
159
+ const onMouseDown = (0, react.useCallback)((e) => {
160
+ if (e.button !== 0 || !scrollRef.current) return;
161
+ dragRef.current = {
162
+ dragging: true,
163
+ startX: e.clientX,
164
+ startY: e.clientY,
165
+ startLeft: scrollRef.current.scrollLeft,
166
+ startTop: scrollRef.current.scrollTop,
167
+ moved: false
168
+ };
169
+ setDragging(true);
170
+ }, []);
171
+ const onMouseMove = (0, react.useCallback)((e) => {
172
+ if (!dragRef.current.dragging || !scrollRef.current) return;
173
+ const dx = e.clientX - dragRef.current.startX;
174
+ const dy = e.clientY - dragRef.current.startY;
175
+ if (Math.abs(dx) > 3 || Math.abs(dy) > 3) dragRef.current.moved = true;
176
+ scrollRef.current.scrollLeft = dragRef.current.startLeft - dx;
177
+ scrollRef.current.scrollTop = dragRef.current.startTop - dy;
178
+ }, []);
179
+ const onMouseUp = (0, react.useCallback)(() => {
180
+ dragRef.current.dragging = false;
181
+ setDragging(false);
182
+ }, []);
183
+ const visibleColIndices = (0, react.useMemo)(() => {
184
+ if (!virt.ready) {
185
+ const out = [];
186
+ for (let i = 0; i < nCols; i += 1) out.push(i);
187
+ return out;
188
+ }
189
+ const out = [];
190
+ for (let i = virt.i0; i <= virt.i1; i += 1) out.push(i);
191
+ return out;
192
+ }, [virt, nCols]);
193
+ const summaryCache = (0, react.useMemo)(() => {
194
+ const m = /* @__PURE__ */ new Map();
195
+ const visibleCourtIds = [];
196
+ for (const r of visibleCourtRows) if ((r === null || r === void 0 ? void 0 : r.kind) === "court" && r.id) visibleCourtIds.push(String(r.id));
197
+ const visibleDateKeys = visibleColIndices.map((ci) => monthDays[ci]).filter(Boolean).map((d) => require_bookingCalendarDateUtils.fmtDate(d));
198
+ for (const cid of visibleCourtIds) for (const dk of visibleDateKeys) m.set(`${cid}\t${dk}`, require_bookingCalendarDateUtils.courtDaySummary(cid, dk, bookingsStable, hourSlots, slotStepHours));
199
+ return m;
200
+ }, [
201
+ visibleCourtRows,
202
+ visibleColIndices,
203
+ monthDays,
204
+ bookingsStable,
205
+ hourSlots,
206
+ slotStepHours
207
+ ]);
208
+ const getSummary = (0, react.useCallback)((courtId, dateKey) => {
209
+ var _summaryCache$get;
210
+ return (_summaryCache$get = summaryCache.get(`${courtId}\t${dateKey}`)) !== null && _summaryCache$get !== void 0 ? _summaryCache$get : require_bookingCalendarDateUtils.courtDaySummary(courtId, dateKey, bookingsStable, hourSlots, slotStepHours);
211
+ }, [
212
+ summaryCache,
213
+ bookingsStable,
214
+ hourSlots,
215
+ slotStepHours
216
+ ]);
217
+ const rowHeights = (0, react.useMemo)(() => {
218
+ return visibleCourtRows.map((row) => {
219
+ if (row.kind === "group") return 38;
220
+ let maxH = 44;
221
+ for (let di = 0; di < monthDays.length; di += 1) {
222
+ const day = monthDays[di];
223
+ if (!day) continue;
224
+ const summary = getSummary(row.id, require_bookingCalendarDateUtils.fmtDate(day));
225
+ maxH = Math.max(maxH, require_bookingCalendarConstants.estimateResourceViewCourtCellHeightPx(summary));
226
+ }
227
+ return maxH;
228
+ });
229
+ }, [
230
+ visibleCourtRows,
231
+ monthDays,
232
+ getSummary
233
+ ]);
234
+ const gridTemplateRows = (0, react.useMemo)(() => visibleCourtRows.map((row, i) => row.kind === "group" ? `38px` : `${rowHeights[i]}px`).join(" "), [visibleCourtRows, rowHeights]);
235
+ /** 行前缀和:rowPrefixTop[i] = 前 i 行总高度(i 从 0 到 N) */
236
+ const rowPrefixTop = (0, react.useMemo)(() => {
237
+ const arr = new Array(rowHeights.length + 1);
238
+ arr[0] = 0;
239
+ for (let i = 0; i < rowHeights.length; i += 1) {
240
+ var _rowHeights$i;
241
+ arr[i + 1] = arr[i] + ((_rowHeights$i = rowHeights[i]) !== null && _rowHeights$i !== void 0 ? _rowHeights$i : 0);
242
+ }
243
+ return arr;
244
+ }, [rowHeights]);
245
+ /** 同步到 ref,让 updateVirt 拿到最新前缀和;行高变化后立刻触发一次行窗口重算 */
246
+ (0, react.useLayoutEffect)(() => {
247
+ rowPrefixRef.current = rowPrefixTop;
248
+ scheduleVirt();
249
+ }, [rowPrefixTop, scheduleVirt]);
250
+ const contentHeight = 56 + rowPrefixTop[rowPrefixTop.length - 1];
251
+ /**
252
+ * 按 court 切分的「选中日期签名」:传给 ResourceCourtRow 触发 React.memo 浅比较。
253
+ * - 同一 court 的选中集没变 → sig 字符串相等 → memo 命中 → 整行跳过 reconcile。
254
+ * - 这样点击 / 取消 cell 时,只有「新选中 / 旧选中」对应的 1~2 行真正重渲染,其他全部 N 行被 memo 跳过。
255
+ */
256
+ const selectionSigByCourtId = (0, react.useMemo)(() => {
257
+ const grp = /* @__PURE__ */ new Map();
258
+ for (const s of selectedResourceCells) {
259
+ const cid = String(s.courtId);
260
+ let arr = grp.get(cid);
261
+ if (!arr) {
262
+ arr = [];
263
+ grp.set(cid, arr);
264
+ }
265
+ arr.push(s.dateKey);
266
+ }
267
+ const m = /* @__PURE__ */ new Map();
268
+ grp.forEach((arr, cid) => {
269
+ arr.sort();
270
+ m.set(cid, arr.join("|"));
271
+ });
272
+ return m;
273
+ }, [selectedResourceCells]);
274
+ const monthLabel = `${anchorDate.getFullYear()} / ${anchorDate.getMonth() + 1}`;
275
+ return /* @__PURE__ */ react.default.createElement("div", {
276
+ key: `resource-${monthKeyPrefix}`,
277
+ className: "flex min-h-0 flex-1 flex-col"
278
+ }, /* @__PURE__ */ react.default.createElement("div", { className: "relative flex min-h-0 flex-1 flex-col" }, /* @__PURE__ */ react.default.createElement("div", {
279
+ ref: scrollRef,
280
+ onMouseDown,
281
+ onMouseMove,
282
+ onMouseUp,
283
+ onMouseLeave: onMouseUp,
284
+ style: { overscrollBehavior: "none" },
285
+ className: `pisell-scroll-hidden relative border min-h-0 max-h-none flex-1 overflow-auto touch-pan-x touch-pan-y select-none ${isLight ? "theme-panel theme-border" : "border-zinc-800 bg-[#121217]"} ${dragging ? "cursor-grabbing" : "cursor-grab"}`
286
+ }, /* @__PURE__ */ react.default.createElement("div", {
287
+ className: "relative",
288
+ style: {
289
+ minWidth: `${132 + nCols * slotColWidth}px`,
290
+ minHeight: `${contentHeight}px`
291
+ }
292
+ }, /* @__PURE__ */ react.default.createElement("div", {
293
+ className: "grid sticky top-0 z-50",
294
+ style: { gridTemplateColumns: `132px repeat(${nCols}, ${slotColWidth}px)` }
295
+ }, /* @__PURE__ */ react.default.createElement("div", { className: `sticky left-0 top-0 z-[60] flex h-full min-h-0 w-[132px] min-w-[132px] max-w-[132px] flex-col justify-center px-2.5 font-semibold border-b shadow-[8px_0_16px_rgba(0,0,0,0.08)] ${isLight ? "surface-brand-side text-slate-900" : "bg-[#241d40] border-zinc-800 text-zinc-100"}` }, /* @__PURE__ */ react.default.createElement("span", { className: "text-lg font-bold leading-tight" }, monthLabel)), visibleColIndices.map((ci) => {
296
+ const day = monthDays[ci];
297
+ if (!day) return null;
298
+ const key = require_bookingCalendarDateUtils.fmtDate(day);
299
+ const isToday = key === todayKey;
300
+ const remaining = resourceDayHeaderRemaining[key];
301
+ return /* @__PURE__ */ react.default.createElement("div", {
302
+ key: `rh-${key}`,
303
+ className: `relative z-20 border-b border-l px-1 py-2 text-center shadow-[0_8px_16px_rgba(0,0,0,0.06)] ${isLight ? "theme-border theme-text" : "border-zinc-800"} ${isToday ? isLight ? "bg-gradient-to-b from-violet-50 to-white" : "bg-gradient-to-b from-violet-950 to-[#1a1a22]" : isLight ? "theme-panel-soft" : "bg-[#1a1a22]"}`,
304
+ style: { gridColumn: ci + 2 }
305
+ }, /* @__PURE__ */ react.default.createElement("div", { className: "flex justify-center" }, /* @__PURE__ */ react.default.createElement("span", {
306
+ className: "grid h-10 w-10 place-items-center text-3xl font-semibold leading-none",
307
+ "aria-label": isToday ? "Today" : void 0
308
+ }, day.getDate())), remaining && remaining.total > 0 && /* @__PURE__ */ react.default.createElement("div", { className: `mt-1 text-[11px] font-semibold leading-tight ${require_bookingCalendarViewShared.getRemainingToneClass(remaining.ratio, isLight)}` }, require_calendarI18n.tCal("pisell2.recordBoard.calendar.day.freeRatio", {
309
+ remaining: remaining.remaining,
310
+ total: remaining.total
311
+ })));
312
+ })), /* @__PURE__ */ react.default.createElement("div", {
313
+ className: "grid relative",
314
+ style: {
315
+ gridTemplateColumns: `132px repeat(${nCols}, ${slotColWidth}px)`,
316
+ gridTemplateRows
317
+ }
318
+ }, visibleCourtRows.map((row, rowIndex) => {
319
+ var _selectionSigByCourtI;
320
+ if (virt.ready && (rowIndex < virt.r0 || rowIndex > virt.r1)) return /* @__PURE__ */ react.default.createElement("div", {
321
+ key: `${row.id}-skip`,
322
+ className: `pointer-events-none border-b ${isLight ? "theme-border" : "border-zinc-800"}`,
323
+ style: {
324
+ gridColumn: "1 / -1",
325
+ gridRow: rowIndex + 1
326
+ },
327
+ "aria-hidden": true
328
+ });
329
+ return row.kind === "group" ? /* @__PURE__ */ react.default.createElement(react.default.Fragment, { key: row.id }, /* @__PURE__ */ react.default.createElement(require_BookingCalendarGroupRowHeader.BookingCalendarGroupRowHeader, {
330
+ isLight,
331
+ view: "resource",
332
+ row: {
333
+ id: row.id,
334
+ label: row.label,
335
+ count: row.count,
336
+ collapsed: row.collapsed
337
+ },
338
+ renderResourceCell: props.renderResourceCell,
339
+ setCollapsedGroups,
340
+ style: {
341
+ gridRow: rowIndex + 1,
342
+ gridColumn: 1
343
+ }
344
+ }), visibleColIndices.map((ci) => {
345
+ const day = monthDays[ci];
346
+ if (!day) return null;
347
+ const key = require_bookingCalendarDateUtils.fmtDate(day);
348
+ const groupCourts = visibleCourts.filter((c) => c.type === row.label);
349
+ const total = groupCourts.length;
350
+ const noRatioCell = total === 0;
351
+ let remaining = 0;
352
+ let ratio = 0;
353
+ if (!noRatioCell) {
354
+ remaining = total - groupCourts.filter((c) => getSummary(c.id, key).items.length > 0).length;
355
+ ratio = remaining / total;
356
+ }
357
+ return /* @__PURE__ */ react.default.createElement("div", {
358
+ key: `${row.id}-${key}`,
359
+ style: {
360
+ gridRow: rowIndex + 1,
361
+ gridColumn: ci + 2
362
+ },
363
+ className: `flex h-full items-center justify-center border-b border-l px-1.5 py-1 text-center text-sm font-medium ${isLight ? "theme-border" : "border-zinc-800"} ${noRatioCell ? isLight ? "bg-slate-100 theme-text-faint" : "bg-zinc-800/80 text-zinc-500" : isLight ? "theme-panel-soft" : "bg-[#151a2f] text-zinc-300"}`
364
+ }, noRatioCell ? /* @__PURE__ */ react.default.createElement("div", { className: "text-xs opacity-60" }, "—") : /* @__PURE__ */ react.default.createElement("div", { className: `text-xs font-semibold leading-none ${require_bookingCalendarViewShared.getRemainingToneClass(ratio, isLight)}` }, require_calendarI18n.tCal("pisell2.recordBoard.calendar.day.freeRatio", {
365
+ remaining,
366
+ total
367
+ })));
368
+ })) : /* @__PURE__ */ react.default.createElement(ResourceCourtRow, {
369
+ key: row.id,
370
+ courtId: String(row.id),
371
+ courtName: row.name,
372
+ courtType: row.type,
373
+ rowIndex,
374
+ visibleColIndices,
375
+ monthDays,
376
+ getSummary,
377
+ toggleResourceCell,
378
+ isLight,
379
+ renderResourceCell: props.renderResourceCell,
380
+ handleGridDropMove,
381
+ selectedSig: (_selectionSigByCourtI = selectionSigByCourtId.get(String(row.id))) !== null && _selectionSigByCourtI !== void 0 ? _selectionSigByCourtI : ""
382
+ });
383
+ }), /* @__PURE__ */ react.default.createElement(BookingOverlay, {
384
+ visibleCourtRows,
385
+ visibleColIndices,
386
+ monthDays,
387
+ rowPrefixTop,
388
+ rowHeights,
389
+ slotColWidth,
390
+ virt,
391
+ getSummary,
392
+ isLight,
393
+ renderMonthWeekBooking: props.renderMonthWeekBooking,
394
+ openBookingDetail,
395
+ setDraggingItem,
396
+ handleGridDropMove,
397
+ overflowPopover,
398
+ setOverflowPopover,
399
+ setOverflowListModal,
400
+ overflowPanelRef
401
+ }))))));
402
+ }
403
+ const ResourceCourtRow = react.default.memo(function ResourceCourtRow({ courtId, courtName, courtType, rowIndex, visibleColIndices, monthDays, getSummary, toggleResourceCell, isLight, renderResourceCell, handleGridDropMove, selectedSig }) {
404
+ /** 由签名字符串还原 Set,仅 selectedSig 变化时才重建 */
405
+ const selectedDateKeys = (0, react.useMemo)(() => selectedSig ? new Set(selectedSig.split("|")) : null, [selectedSig]);
406
+ return /* @__PURE__ */ react.default.createElement(react.default.Fragment, null, /* @__PURE__ */ react.default.createElement("div", {
407
+ className: `sticky left-0 z-30 flex h-full min-h-0 w-[132px] min-w-[132px] max-w-[132px] flex-col justify-center border-b px-2.5 py-1.5 shadow-[8px_0_16px_rgba(0,0,0,0.06)] ${isLight ? "surface-brand-side-muted" : "border-zinc-800 bg-[#1a1a22]"}`,
408
+ style: {
409
+ gridRow: rowIndex + 1,
410
+ gridColumn: 1
411
+ }
412
+ }, renderResourceCell ? renderResourceCell({
413
+ view: "resource",
414
+ row: {
415
+ kind: "court",
416
+ id: courtId,
417
+ name: courtName,
418
+ type: courtType
419
+ },
420
+ isLight
421
+ }) : /* @__PURE__ */ react.default.createElement("div", { className: "pisell-rb-cal-week-resource-name min-w-0 w-full font-semibold text-[16px] leading-tight" }, courtName)), visibleColIndices.map((ci) => {
422
+ var _selectedDateKeys$has;
423
+ const day = monthDays[ci];
424
+ if (!day) return null;
425
+ const key = require_bookingCalendarDateUtils.fmtDate(day);
426
+ const summary = getSummary(courtId, key);
427
+ return /* @__PURE__ */ react.default.createElement(ResourceCourtCell, {
428
+ key: `${courtId}-${key}`,
429
+ courtId,
430
+ dateKey: key,
431
+ rowIndex,
432
+ colIndex: ci,
433
+ summary,
434
+ selected: (_selectedDateKeys$has = selectedDateKeys === null || selectedDateKeys === void 0 ? void 0 : selectedDateKeys.has(key)) !== null && _selectedDateKeys$has !== void 0 ? _selectedDateKeys$has : false,
435
+ isLight,
436
+ toggleResourceCell,
437
+ handleGridDropMove
438
+ });
439
+ }));
440
+ });
441
+ const ResourceCourtCell = react.default.memo(function ResourceCourtCell({ courtId, dateKey, rowIndex, colIndex, summary, selected, isLight, toggleResourceCell, handleGridDropMove }) {
442
+ return /* @__PURE__ */ react.default.createElement("button", {
443
+ type: "button",
444
+ onClick: () => toggleResourceCell(courtId, dateKey),
445
+ onDragOver: (e) => e.preventDefault(),
446
+ onDrop: (e) => {
447
+ e.preventDefault();
448
+ handleGridDropMove(dateKey, courtId);
449
+ },
450
+ style: {
451
+ gridRow: rowIndex + 1,
452
+ gridColumn: colIndex + 2,
453
+ contain: "layout paint style"
454
+ },
455
+ className: `relative flex w-full min-h-0 flex-col justify-start border-b border-l p-1 text-left align-top outline-none ${isLight ? "theme-border" : "border-zinc-700"} ${summary.fullyBooked ? isLight ? "bg-slate-100 theme-text-faint" : "bg-zinc-800 text-zinc-500" : summary.hasBlock ? isLight ? "bg-red-50 border-red-200" : "bg-red-950/30 border-red-800/40" : summary.occupiedHours.length > 0 ? isLight ? "bg-violet-50" : "bg-[#1c1630]" : isLight ? "theme-grid-cell" : "bg-zinc-900"} ${selected ? isLight ? "ring-1 ring-inset ring-violet-400/75 bg-violet-400/10" : "ring-1 ring-inset ring-violet-400/70 bg-white/10" : ""}`
456
+ }, summary.items.length === 0 ? /* @__PURE__ */ react.default.createElement("div", { className: `${require_bookingCalendarConstants.CALENDAR_EMPTY_DASHED_SLOT_CLASSNAME} text-center ${isLight ? "border-[#d8d0eb] text-[#8f86a9]" : "border-zinc-700 text-zinc-500"}` }, selected ? require_calendarI18n.tCal("pisell2.recordBoard.calendar.day.selected") : null) : null);
457
+ });
458
+ const OVERLAY_CELL_PAD = 4;
459
+ const OVERLAY_ITEM_GAP = 4;
460
+ const BookingOverlay = react.default.memo(function BookingOverlay({ visibleCourtRows, visibleColIndices, monthDays, rowPrefixTop, rowHeights, slotColWidth, virt, getSummary, isLight, renderMonthWeekBooking, openBookingDetail, setDraggingItem, handleGridDropMove, overflowPopover, setOverflowPopover, setOverflowListModal, overflowPanelRef }) {
461
+ var _rowPrefixTop;
462
+ /** 一次遍历可见区域,计算出所有 overlay item 的 px 位置 */
463
+ const items = (0, react.useMemo)(() => {
464
+ const out = [];
465
+ for (let rowIdx = 0; rowIdx < visibleCourtRows.length; rowIdx += 1) {
466
+ var _rowPrefixTop$rowIdx;
467
+ if (virt.ready && (rowIdx < virt.r0 || rowIdx > virt.r1)) continue;
468
+ const row = visibleCourtRows[rowIdx];
469
+ if ((row === null || row === void 0 ? void 0 : row.kind) !== "court" || !row.id) continue;
470
+ const courtId = String(row.id);
471
+ const cellTop = (_rowPrefixTop$rowIdx = rowPrefixTop[rowIdx]) !== null && _rowPrefixTop$rowIdx !== void 0 ? _rowPrefixTop$rowIdx : 0;
472
+ for (const colIdx of visibleColIndices) {
473
+ const day = monthDays[colIdx];
474
+ if (!day) continue;
475
+ const dateKey = require_bookingCalendarDateUtils.fmtDate(day);
476
+ const summary = getSummary(courtId, dateKey);
477
+ if (summary.items.length === 0) continue;
478
+ const cellLeft = colIdx * slotColWidth + 132;
479
+ const previewItems = summary.items.slice(0, PREVIEW_CAP);
480
+ const moreItems = summary.items.slice(PREVIEW_CAP);
481
+ const moreCount = moreItems.length;
482
+ const showMore = moreCount > 0 && previewItems.length > 0;
483
+ const itemLeft = cellLeft + OVERLAY_CELL_PAD;
484
+ const itemWidth = slotColWidth - OVERLAY_CELL_PAD * 2;
485
+ for (let i = 0; i < previewItems.length; i += 1) {
486
+ var _previewItems$i$id;
487
+ const top = cellTop + OVERLAY_CELL_PAD + i * (48 + OVERLAY_ITEM_GAP);
488
+ out.push({
489
+ kind: "booking",
490
+ key: `${courtId}\t${dateKey}\t${(_previewItems$i$id = previewItems[i].id) !== null && _previewItems$i$id !== void 0 ? _previewItems$i$id : i}`,
491
+ item: previewItems[i],
492
+ courtId,
493
+ dateKey,
494
+ top,
495
+ left: itemLeft,
496
+ width: itemWidth,
497
+ height: 48
498
+ });
499
+ }
500
+ if (showMore) {
501
+ const top = cellTop + OVERLAY_CELL_PAD + previewItems.length * (48 + OVERLAY_ITEM_GAP);
502
+ out.push({
503
+ kind: "more",
504
+ key: `${courtId}\t${dateKey}\tmore`,
505
+ courtId,
506
+ dateKey,
507
+ top,
508
+ left: itemLeft,
509
+ width: itemWidth,
510
+ moreItems,
511
+ moreCount,
512
+ allItems: summary.items
513
+ });
514
+ }
515
+ }
516
+ }
517
+ return out;
518
+ }, [
519
+ visibleCourtRows,
520
+ visibleColIndices,
521
+ monthDays,
522
+ rowPrefixTop,
523
+ slotColWidth,
524
+ virt,
525
+ getSummary
526
+ ]);
527
+ const overlayHeight = (_rowPrefixTop = rowPrefixTop[rowPrefixTop.length - 1]) !== null && _rowPrefixTop !== void 0 ? _rowPrefixTop : rowHeights.reduce((a, b) => a + b, 0);
528
+ return /* @__PURE__ */ react.default.createElement("div", {
529
+ "aria-hidden": true,
530
+ style: {
531
+ position: "absolute",
532
+ top: 0,
533
+ left: 0,
534
+ width: "100%",
535
+ height: overlayHeight,
536
+ pointerEvents: "none",
537
+ zIndex: 5
538
+ }
539
+ }, items.map((it) => {
540
+ if (it.kind === "booking") {
541
+ const layoutClassName = `flex flex-col justify-center rounded-lg px-2.5 py-1.5 text-xs text-left w-full h-full ${require_BookingCalendarStyle.getGridBookingRowStateClasses(it.item)}`;
542
+ return /* @__PURE__ */ react.default.createElement("div", {
543
+ key: it.key,
544
+ style: {
545
+ position: "absolute",
546
+ top: it.top,
547
+ left: it.left,
548
+ width: it.width,
549
+ height: it.height,
550
+ pointerEvents: "auto"
551
+ },
552
+ onDragOver: (e) => e.preventDefault(),
553
+ onDrop: (e) => {
554
+ e.preventDefault();
555
+ handleGridDropMove(it.dateKey, it.courtId);
556
+ }
557
+ }, renderMonthWeekBooking ? renderMonthWeekBooking({
558
+ item: it.item,
559
+ dateKey: it.dateKey,
560
+ view: "resource",
561
+ isLight,
562
+ layoutClassName,
563
+ onOpenDetail: openBookingDetail,
564
+ onDragStart: (i) => setDraggingItem(i),
565
+ draggable: false
566
+ }) : /* @__PURE__ */ react.default.createElement("button", {
567
+ type: "button",
568
+ onClick: (e) => {
569
+ e.stopPropagation();
570
+ openBookingDetail(it.item);
571
+ },
572
+ className: layoutClassName
573
+ }, /* @__PURE__ */ react.default.createElement("div", { className: "truncate" }, it.item.name), /* @__PURE__ */ react.default.createElement("div", { className: "mt-1 opacity-80 truncate" }, require_bookingCalendarDateUtils.formatHourLabel(it.item.startHour), " -", " ", require_bookingCalendarDateUtils.formatHourLabel(it.item.endHour))));
574
+ }
575
+ const isThisPopover = (overflowPopover === null || overflowPopover === void 0 ? void 0 : overflowPopover.courtId) === it.courtId && (overflowPopover === null || overflowPopover === void 0 ? void 0 : overflowPopover.date) === it.dateKey;
576
+ return /* @__PURE__ */ react.default.createElement("div", {
577
+ key: it.key,
578
+ ref: isThisPopover ? overflowPanelRef : null,
579
+ style: {
580
+ position: "absolute",
581
+ top: it.top,
582
+ left: it.left,
583
+ width: it.width,
584
+ minHeight: 48,
585
+ pointerEvents: "auto"
586
+ }
587
+ }, /* @__PURE__ */ react.default.createElement("button", {
588
+ type: "button",
589
+ onMouseEnter: (e) => {
590
+ e.stopPropagation();
591
+ setOverflowPopover({
592
+ courtId: it.courtId,
593
+ date: it.dateKey,
594
+ items: it.moreItems,
595
+ pinned: false
596
+ });
597
+ },
598
+ onClick: (e) => {
599
+ e.stopPropagation();
600
+ setOverflowPopover(null);
601
+ setOverflowListModal((prev) => prev && prev.scope === it.courtId && prev.date === it.dateKey ? null : {
602
+ scope: it.courtId,
603
+ date: it.dateKey,
604
+ title: require_calendarI18n.tCal("pisell2.recordBoard.calendar.overflow.listTitleAllBookings", { date: it.dateKey }),
605
+ items: it.allItems.filter((x) => x.kind === "booking")
606
+ });
607
+ },
608
+ style: { minHeight: 48 },
609
+ className: "flex w-full items-center justify-center rounded-xl bg-violet-700/90 px-2.5 py-1 text-xs font-semibold text-white"
610
+ }, "+", it.moreCount));
611
+ }));
612
+ });
613
+ //#endregion
614
+ exports.BookingCalendarResourceView = BookingCalendarResourceView;
@@ -0,0 +1,34 @@
1
+ require("../../../../../../_virtual/_rolldown/runtime.js");
2
+ const require_bookingCalendarDateUtils = require("../../utils/bookingCalendarDateUtils.js");
3
+ let react = require("react");
4
+ //#region src/components/pisellRecordBoard/shellFrame/Calendar/view/day/useBookingCalendarDayHourRemaining.ts
5
+ /**
6
+ * 日视图表头:每列整点仍空闲资源占比。
7
+ */
8
+ function useBookingCalendarDayHourRemaining(visibleCourts, dayKey, bookings, hourSlots, slotStepHours, isBusinessHour) {
9
+ return (0, react.useMemo)(() => {
10
+ const total = visibleCourts.length;
11
+ return Object.fromEntries(hourSlots.map((hour) => {
12
+ if (!isBusinessHour(hour)) return [hour, {
13
+ remaining: 0,
14
+ total,
15
+ ratio: 0
16
+ }];
17
+ const remaining = visibleCourts.filter((court) => !require_bookingCalendarDateUtils.dayOccupancy(court.id, dayKey, bookings, hourSlots, slotStepHours)[hour]).length;
18
+ return [hour, {
19
+ remaining,
20
+ total,
21
+ ratio: total > 0 ? remaining / total : 0
22
+ }];
23
+ }));
24
+ }, [
25
+ visibleCourts,
26
+ dayKey,
27
+ bookings,
28
+ hourSlots,
29
+ slotStepHours,
30
+ isBusinessHour
31
+ ]);
32
+ }
33
+ //#endregion
34
+ exports.useBookingCalendarDayHourRemaining = useBookingCalendarDayHourRemaining;
@@ -0,0 +1,38 @@
1
+ require("../../../../../../_virtual/_rolldown/runtime.js");
2
+ const require_bookingCalendarDateUtils = require("../../utils/bookingCalendarDateUtils.js");
3
+ let react = require("react");
4
+ //#region src/components/pisellRecordBoard/shellFrame/Calendar/view/day/useBookingCalendarDaySelection.ts
5
+ /**
6
+ * 日视图:空闲 / 锁台槽多选逻辑(状态仍在 Shell,便于与底部 SelectionBar 共用)。
7
+ */
8
+ function useBookingCalendarDaySelection(dayKey, bookings, hourSlots, slotStepHours, courts, selectedFreeSlots, selectedBlockSlots, setSelectedFreeSlots, setSelectedBlockSlots) {
9
+ return {
10
+ isFreeSelected: (0, react.useCallback)((courtId, hour) => selectedFreeSlots.some((slot) => slot.courtId === courtId && slot.date === dayKey && slot.hour === hour), [selectedFreeSlots, dayKey]),
11
+ isBlockSelected: (0, react.useCallback)((courtId, hour) => selectedBlockSlots.some((slot) => slot.courtId === courtId && slot.date === dayKey && slot.hour === hour), [selectedBlockSlots, dayKey]),
12
+ toggleSlot: (0, react.useCallback)((courtId, hour) => {
13
+ const existing = require_bookingCalendarDateUtils.dayOccupancy(courtId, dayKey, bookings, hourSlots, slotStepHours)[hour];
14
+ const court = courts.find((c) => c.id === courtId);
15
+ const slot = {
16
+ courtId,
17
+ court: court === null || court === void 0 ? void 0 : court.name,
18
+ date: dayKey,
19
+ hour
20
+ };
21
+ if (!existing) {
22
+ setSelectedFreeSlots((prev) => prev.some((s) => require_bookingCalendarDateUtils.sameSlot(s, slot)) ? prev.filter((s) => !require_bookingCalendarDateUtils.sameSlot(s, slot)) : [...prev, slot]);
23
+ return;
24
+ }
25
+ if (existing.kind === "block") setSelectedBlockSlots((prev) => prev.some((s) => require_bookingCalendarDateUtils.sameSlot(s, slot)) ? prev.filter((s) => !require_bookingCalendarDateUtils.sameSlot(s, slot)) : [...prev, slot]);
26
+ }, [
27
+ bookings,
28
+ courts,
29
+ dayKey,
30
+ hourSlots,
31
+ setSelectedBlockSlots,
32
+ setSelectedFreeSlots,
33
+ slotStepHours
34
+ ])
35
+ };
36
+ }
37
+ //#endregion
38
+ exports.useBookingCalendarDaySelection = useBookingCalendarDaySelection;