react-big-schedule 6.0.0 → 6.1.1-beta.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.
@@ -1,5 +1,11 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
+
4
+ /**
5
+ * Render the table body (<tbody>) containing resource rows and cells based on schedulerData.
6
+ * @param {object} schedulerData - Scheduler state and helpers. Expected properties used: `renderData` (array of row descriptors with `slotId`, `render`, `rowHeight`, `groupOnly`, `nonWorkingTime`), `headers` (array of header descriptors with `time` and `nonWorkingTime`), `config` (including `nonWorkingTimeBodyBgColor` and `groupOnlySlotColor`), `behaviors` (optional `getNonAgendaViewBodyCellBgColorFunc`), `getContentCellWidth()` and `isVerticalResourceView()`.
7
+ * @returns {JSX.Element} The rendered `<tbody>` element containing a `<tr>` per rendered row and `<td>` cells for each header with computed inline styles.
8
+ */
3
9
  import { jsx as _jsx } from "react/jsx-runtime";
4
10
  function BodyView({
5
11
  schedulerData
@@ -11,17 +17,25 @@ function BodyView({
11
17
  behaviors
12
18
  } = schedulerData;
13
19
  const width = schedulerData.getContentCellWidth();
14
- const tableRows = renderData.filter(o => o.render).map(({
15
- slotId,
16
- groupOnly,
17
- rowHeight
18
- }) => {
19
- const rowCells = headers.map((header, index) => {
20
- const key = `${slotId}_${header.time}`;
21
- const style = index === headers.length - 1 ? {} : {
22
- width
20
+ const tableRows = renderData.filter(o => o.render).map(row => {
21
+ const {
22
+ slotId,
23
+ groupOnly,
24
+ rowHeight
25
+ } = row;
26
+ const rowCells = headers.map(header => {
27
+ // vertical view: every header shares one time, the resource id tells the columns apart
28
+ const key = `${slotId}_${header.id ?? header.time}`;
29
+ const style = {
30
+ width,
31
+ minWidth: width
23
32
  };
24
- if (header.nonWorkingTime) {
33
+ const isVertical = schedulerData.isVerticalResourceView();
34
+ if (isVertical) {
35
+ if (row.nonWorkingTime) {
36
+ style.backgroundColor = config.nonWorkingTimeBodyBgColor;
37
+ }
38
+ } else if (header.nonWorkingTime) {
25
39
  style.backgroundColor = config.nonWorkingTimeBodyBgColor;
26
40
  }
27
41
  if (groupOnly) {
@@ -52,12 +52,14 @@ class EventItem extends Component {
52
52
  const {
53
53
  left,
54
54
  top,
55
- width
55
+ width,
56
+ height
56
57
  } = props;
57
58
  this.state = {
58
59
  left,
59
60
  top,
60
61
  width,
62
+ height,
61
63
  contentMousePosX: 0,
62
64
  eventItemLeftRect: 0,
63
65
  eventItemRightRect: 0
@@ -78,13 +80,15 @@ class EventItem extends Component {
78
80
  const {
79
81
  left,
80
82
  top,
81
- width
83
+ width,
84
+ height
82
85
  } = this.props;
83
- if (prevProps.left !== left || prevProps.top !== top || prevProps.width !== width) {
86
+ if (prevProps.left !== left || prevProps.top !== top || prevProps.width !== width || prevProps.height !== height) {
84
87
  this.setState({
85
88
  left,
86
89
  top,
87
- width
90
+ width,
91
+ height
88
92
  });
89
93
  }
90
94
 
@@ -516,7 +520,8 @@ class EventItem extends Component {
516
520
  const {
517
521
  left,
518
522
  width,
519
- top
523
+ top,
524
+ height
520
525
  } = this.state;
521
526
  let roundCls;
522
527
  const popoverPlacement = config.eventItemPopoverPlacement;
@@ -552,7 +557,7 @@ class EventItem extends Component {
552
557
  let eventItemTemplate = /*#__PURE__*/_jsx("div", {
553
558
  className: `${roundCls} event-item`,
554
559
  style: {
555
- height: config.eventItemHeight,
560
+ height: height || config.eventItemHeight,
556
561
  backgroundColor: bgColor
557
562
  },
558
563
  children: /*#__PURE__*/_jsx("span", {
@@ -575,6 +580,7 @@ class EventItem extends Component {
575
580
  left,
576
581
  width,
577
582
  top,
583
+ height,
578
584
  cursor: 'pointer',
579
585
  background: 'none',
580
586
  border: 'none',
@@ -697,6 +703,7 @@ EventItem.propTypes = {
697
703
  left: PropTypes.number.isRequired,
698
704
  width: PropTypes.number.isRequired,
699
705
  top: PropTypes.number.isRequired,
706
+ height: PropTypes.number,
700
707
  isInPopover: PropTypes.bool.isRequired,
701
708
  leftIndex: PropTypes.number.isRequired,
702
709
  rightIndex: PropTypes.number.isRequired,
@@ -32,7 +32,7 @@ function HeaderView({
32
32
 
33
33
  // Week number row generation
34
34
  const weekNumberRow = useMemo(() => {
35
- if (!showWeekNumber) return null;
35
+ if (!showWeekNumber || schedulerData.isVerticalResourceView()) return null;
36
36
  const weekGroups = [];
37
37
  let currentWeekKey = null;
38
38
  let currentWeek = null;
@@ -74,12 +74,19 @@ function HeaderView({
74
74
  padding: '4px 8px',
75
75
  textAlign: 'center'
76
76
  };
77
- return weekGroups.map(group => /*#__PURE__*/_jsxs("th", {
78
- colSpan: group.colspan,
79
- style: cellStyle,
80
- children: ["W", group.week]
81
- }, `week-${group.year}-${group.week}`));
82
- }, [showWeekNumber, headers, localeDayjs, config.headerBorderColor]);
77
+ return weekGroups.map(group => {
78
+ if (group.week == null || group.year == null) return null;
79
+ return /*#__PURE__*/_jsxs("th", {
80
+ colSpan: group.colspan,
81
+ style: {
82
+ ...cellStyle,
83
+ width: group.colspan * cellWidth,
84
+ minWidth: group.colspan * cellWidth
85
+ },
86
+ children: ["W", group.week]
87
+ }, `week-${group.year}-${group.week}`);
88
+ });
89
+ }, [showWeekNumber, headers, localeDayjs, config.headerBorderColor, cellWidth, schedulerData]);
83
90
 
84
91
  // Extract common style creation logic
85
92
  const createCellStyle = useCallback((item, width, isLastCell) => {
@@ -127,6 +134,21 @@ function HeaderView({
127
134
 
128
135
  // Memoize header list generation
129
136
  const headerList = useMemo(() => {
137
+ if (schedulerData.isVerticalResourceView()) {
138
+ return headers.map(item => {
139
+ const style = {
140
+ width: cellWidth,
141
+ minWidth: cellWidth
142
+ };
143
+ return /*#__PURE__*/_jsx("th", {
144
+ className: "header3-text",
145
+ style: style,
146
+ children: /*#__PURE__*/_jsx("div", {
147
+ children: item.name
148
+ })
149
+ }, `header-${item.id}`);
150
+ });
151
+ }
130
152
  if (cellUnit === CellUnit.Hour) {
131
153
  const result = [];
132
154
  const lastIndex = headers.length - minuteStepsInHour;
@@ -152,7 +174,7 @@ function HeaderView({
152
174
  const formattedList = cellFormat.split('|').map(format => datetime.format(format));
153
175
  return renderCellContent(item, formattedList, style);
154
176
  });
155
- }, [cellUnit, headers, minuteStepsInHour, cellWidth, config, localeDayjs, createCellStyle, getCellFormat, renderCellContent]);
177
+ }, [cellUnit, headers, minuteStepsInHour, cellWidth, config, localeDayjs, createCellStyle, getCellFormat, renderCellContent, schedulerData.isVerticalResourceView]);
156
178
  return /*#__PURE__*/_jsxs("thead", {
157
179
  children: [weekNumberRow && /*#__PURE__*/_jsx("tr", {
158
180
  style: {
@@ -291,10 +291,16 @@ class ResourceEvents extends PureComponent {
291
291
  const maxRightIndex = Math.min(headers.length, resourceEvents.headerItems.length);
292
292
  let safeRightIndex = Math.max(1, rightIndex, safeLeftIndex + 1);
293
293
  safeRightIndex = Math.min(safeRightIndex, maxRightIndex);
294
- const startTime = headers[safeLeftIndex].time;
295
- let endTime = resourceEvents.headerItems[safeRightIndex - 1].end;
296
- if (cellUnit !== CellUnit.Hour) {
297
- endTime = localeDayjs(new Date(resourceEvents.headerItems[safeRightIndex - 1].start)).hour(23).minute(59).second(59).format(DATETIME_FORMAT);
294
+ let startTime, endTime;
295
+ if (schedulerData.isVerticalResourceView()) {
296
+ startTime = resourceEvents.slotId;
297
+ endTime = localeDayjs(new Date(startTime)).add(config.minuteStep, 'minutes').format(DATETIME_FORMAT);
298
+ } else {
299
+ startTime = headers[safeLeftIndex].time;
300
+ endTime = resourceEvents.headerItems[safeRightIndex - 1].end;
301
+ if (cellUnit !== CellUnit.Hour) {
302
+ endTime = localeDayjs(new Date(resourceEvents.headerItems[safeRightIndex - 1].start)).hour(23).minute(59).second(59).format(DATETIME_FORMAT);
303
+ }
298
304
  }
299
305
 
300
306
  // Get selected resource IDs
@@ -535,7 +541,14 @@ class ResourceEvents extends PureComponent {
535
541
  } else {
536
542
  width = evt.span * cellWidth - (index > 0 ? 5 : 6) > 0 ? evt.span * cellWidth - (index > 0 ? 5 : 6) : 0;
537
543
  }
538
- const top = marginTop + idx * config.eventItemLineHeight;
544
+ const top = schedulerData.isVerticalResourceView() ? 0 : marginTop + idx * config.eventItemLineHeight;
545
+ const height = schedulerData.isVerticalResourceView() ? resourceEvents.rowHeight : undefined;
546
+ if (schedulerData.isVerticalResourceView()) {
547
+ const eventCount = headerItem.count;
548
+ const itemWidth = (cellWidth - (index > 0 ? 5 : 6)) / eventCount;
549
+ left = index * cellWidth + (index > 0 ? 2 : 3) + idx * itemWidth;
550
+ width = itemWidth;
551
+ }
539
552
  const eventKey = `${evt.eventItem.id}_${resourceEvents.slotId}_${index}`;
540
553
  const eventItem = /*#__PURE__*/_jsx(EventItem, {
541
554
  schedulerData: schedulerData,
@@ -547,6 +560,7 @@ class ResourceEvents extends PureComponent {
547
560
  left: left,
548
561
  width: width,
549
562
  top: top,
563
+ height: height,
550
564
  leftIndex: index,
551
565
  rightIndex: index + evt.span
552
566
  // Passing through functional props
@@ -33,7 +33,7 @@ function ResourceView({
33
33
  const {
34
34
  renderData
35
35
  } = schedulerData;
36
- const width = schedulerData.getResourceTableWidth() - 2;
36
+ const width = schedulerData.getResourceTableWidth();
37
37
  const paddingBottom = contentScrollbarHeight;
38
38
  const displayRenderData = renderData.filter(o => o.render);
39
39
  const handleToggleExpand = item => {
@@ -135,6 +135,26 @@ function ResourceView({
135
135
  }, item.slotId);
136
136
  };
137
137
  const resourceList = displayRenderData.map(item => {
138
+ if (schedulerData.isVerticalResourceView()) {
139
+ const timeLabel = schedulerData.localeDayjs(new Date(item.slotName)).format('HH:mm');
140
+ return /*#__PURE__*/_jsx("tr", {
141
+ style: {
142
+ height: item.rowHeight
143
+ },
144
+ children: /*#__PURE__*/_jsx("td", {
145
+ "data-resource-id": item.slotId,
146
+ style: {
147
+ height: item.rowHeight,
148
+ minHeight: item.rowHeight,
149
+ textAlign: 'center'
150
+ },
151
+ className: "header2-text",
152
+ children: /*#__PURE__*/_jsx("span", {
153
+ children: timeLabel
154
+ })
155
+ })
156
+ }, item.slotId);
157
+ }
138
158
  const indents = [];
139
159
  for (let i = 0; i < item.indent; i += 1) {
140
160
  indents.push(/*#__PURE__*/_jsx("span", {
@@ -16,7 +16,7 @@ export default class SchedulerData {
16
16
  this.eventGroups = [];
17
17
  this.eventGroupsAutoGenerated = true;
18
18
  this.viewType = viewType;
19
- this.cellUnit = viewType === ViewType.Day ? CellUnit.Hour : CellUnit.Day;
19
+ this.cellUnit = viewType === ViewType.Day || viewType === ViewType.VerticalResource ? CellUnit.Hour : CellUnit.Day;
20
20
  this.showAgenda = showAgenda;
21
21
  this.isEventPerspective = isEventPerspective;
22
22
  this.resizing = false;
@@ -102,6 +102,9 @@ export default class SchedulerData {
102
102
  getVersion() {
103
103
  return this.version;
104
104
  }
105
+ isVerticalResourceView() {
106
+ return this.viewType === ViewType.VerticalResource;
107
+ }
105
108
  setSchedulerLocale(preset) {
106
109
  if (!preset) return;
107
110
  this.localeDayjs.locale(preset);
@@ -117,6 +120,10 @@ export default class SchedulerData {
117
120
  setResources(resources) {
118
121
  this._validateResource(resources);
119
122
  this.resources = Array.from(new Set(resources));
123
+ // vertical view builds its headers from the resources
124
+ if (this.isVerticalResourceView()) {
125
+ this._createHeaders();
126
+ }
120
127
  this._createRenderData();
121
128
  this.setScrollToSpecialDayjs(true);
122
129
  this.bumpVersion();
@@ -162,6 +169,9 @@ export default class SchedulerData {
162
169
  const existedResources = this.resources.filter(x => x.id === resource.id);
163
170
  if (existedResources.length === 0) {
164
171
  this.resources.push(resource);
172
+ if (this.isVerticalResourceView()) {
173
+ this._createHeaders();
174
+ }
165
175
  this._createRenderData();
166
176
  this.bumpVersion();
167
177
  }
@@ -230,10 +240,11 @@ export default class SchedulerData {
230
240
  this.showAgenda = showAgenda;
231
241
  this.isEventPerspective = isEventPerspective;
232
242
  const isCustomView = viewType === ViewType.Custom || viewType === ViewType.Custom1 || viewType === ViewType.Custom2;
233
-
234
- // Force built-in views only; custom views can provide their own cellUnit via custom date behavior.
235
- if (!isCustomView && viewType !== ViewType.Day) {
243
+ const isVerticalView = viewType === ViewType.VerticalResource;
244
+ if (!isCustomView && viewType !== ViewType.Day && !isVerticalView) {
236
245
  this.cellUnit = CellUnit.Day;
246
+ } else if (viewType === ViewType.Day || isVerticalView) {
247
+ this.cellUnit = CellUnit.Hour;
237
248
  }
238
249
 
239
250
  // If showAgenda or isEventPerspective changed, force reload
@@ -273,7 +284,7 @@ export default class SchedulerData {
273
284
  if (now >= start && now < end) {
274
285
  date = now.startOf('day');
275
286
  }
276
- if (viewType === ViewType.Day) {
287
+ if (viewType === ViewType.Day || viewType === ViewType.VerticalResource) {
277
288
  this.startDate = this.localeDayjs(new Date(date)).startOf('day');
278
289
  this.endDate = this.startDate;
279
290
  this.cellUnit = CellUnit.Hour;
@@ -365,7 +376,8 @@ export default class SchedulerData {
365
376
  getSchedulerHeight() {
366
377
  // Use the header height tracked in SchedulerData (set via _setSchedulerHeaderHeight)
367
378
  const headerHeight = this.schedulerHeaderHeight || 0;
368
- const baseHeight = this.documentHeight - headerHeight - this.config.tableHeaderHeight - (this.config.underneathHeight || 0);
379
+ const underneathHeight = this.config.underneathHeight || 0;
380
+ const baseHeight = this.documentHeight - headerHeight - this.config.tableHeaderHeight - underneathHeight;
369
381
  const validBaseHeight = baseHeight > 0 ? baseHeight : 0;
370
382
  if (this.isSchedulerResponsive() && this.config.schedulerHeight?.endsWith?.('%')) {
371
383
  return parseInt(validBaseHeight * Number(this.config.schedulerHeight.slice(0, -1)) / 100, 10);
@@ -383,7 +395,14 @@ export default class SchedulerData {
383
395
  getContentCellWidth() {
384
396
  const contentCellConfigWidth = this.getContentCellConfigWidth();
385
397
  const schedulerWidth = this.getSchedulerWidth();
386
- return this.isContentViewResponsive() ? parseInt(schedulerWidth * Number(contentCellConfigWidth.slice(0, -1)) / 100, 10) : contentCellConfigWidth;
398
+ const cellWidth = this.isContentViewResponsive() ? parseInt(schedulerWidth * Number(contentCellConfigWidth.slice(0, -1)) / 100, 10) : contentCellConfigWidth;
399
+
400
+ // vertical view: resources are the columns, so they share the width the time column doesn't need
401
+ if (this.isVerticalResourceView() && this.isSchedulerResponsive() && !this.isResourceViewResponsive() && this.headers?.length) {
402
+ const fillWidth = Math.floor((schedulerWidth - this.getResourceTableConfigWidth()) / this.headers.length);
403
+ return Math.max(cellWidth, fillWidth);
404
+ }
405
+ return cellWidth;
387
406
  }
388
407
  getContentTableWidth() {
389
408
  return this.headers.length * this.getContentCellWidth();
@@ -566,7 +585,8 @@ export default class SchedulerData {
566
585
  [ViewType.Day]: 'dayResourceTableWidth',
567
586
  [ViewType.Month]: 'monthResourceTableWidth',
568
587
  [ViewType.Year]: 'yearResourceTableWidth',
569
- [ViewType.Quarter]: 'quarterResourceTableWidth'
588
+ [ViewType.Quarter]: 'quarterResourceTableWidth',
589
+ [ViewType.VerticalResource]: 'customResourceTableWidth'
570
590
  };
571
591
  const configProperty = viewConfigMap[this.viewType] || 'customResourceTableWidth';
572
592
  return this.config[configProperty];
@@ -577,7 +597,8 @@ export default class SchedulerData {
577
597
  [ViewType.Day]: 'dayCellWidth',
578
598
  [ViewType.Month]: 'monthCellWidth',
579
599
  [ViewType.Year]: 'yearCellWidth',
580
- [ViewType.Quarter]: 'quarterCellWidth'
600
+ [ViewType.Quarter]: 'quarterCellWidth',
601
+ [ViewType.VerticalResource]: 'customCellWidth'
581
602
  };
582
603
  const configProperty = viewConfigMap[this.viewType] || 'customCellWidth';
583
604
  return this.config[configProperty];
@@ -686,6 +707,7 @@ export default class SchedulerData {
686
707
  this.endDate = this.startDate.endOf('isoWeek');
687
708
  break;
688
709
  case ViewType.Day:
710
+ case ViewType.VerticalResource:
689
711
  this.startDate = (date !== undefined ? this.selectDate : this.startDate.add(num, 'days')).startOf('day');
690
712
  this.endDate = this.startDate;
691
713
  break;
@@ -716,15 +738,14 @@ export default class SchedulerData {
716
738
  break;
717
739
  }
718
740
  }
719
-
720
- // Previous Code
721
741
  _createHeaders() {
722
- const headers = [];
742
+ const timeSlots = [];
723
743
  let start = this.localeDayjs(new Date(this.startDate));
724
744
  let end = this.localeDayjs(new Date(this.endDate));
725
745
  let header = start;
746
+ const width = this.getContentCellWidth();
726
747
  if (this.showAgenda) {
727
- headers.push({
748
+ timeSlots.push({
728
749
  time: header.format(DATETIME_FORMAT),
729
750
  nonWorkingTime: false
730
751
  });
@@ -750,7 +771,7 @@ export default class SchedulerData {
750
771
  if (hour >= this.config.dayStartFrom && hour <= this.config.dayStopTo) {
751
772
  const time = header.format(DATETIME_FORMAT);
752
773
  const nonWorkingTime = this.behaviors.isNonWorkingTimeFunc(this, time);
753
- headers.push({
774
+ timeSlots.push({
754
775
  time,
755
776
  nonWorkingTime
756
777
  });
@@ -761,12 +782,17 @@ export default class SchedulerData {
761
782
  } else if (this.cellUnit === CellUnit.Day) {
762
783
  while (header >= start && header <= end) {
763
784
  const time = header.format(DATETIME_FORMAT);
785
+ const style = {
786
+ width,
787
+ minWidth: width
788
+ };
764
789
  const dayOfWeek = header.day();
765
790
  if (this.config.displayWeekend || dayOfWeek !== 0 && dayOfWeek !== 6) {
766
791
  const nonWorkingTime = this.behaviors.isNonWorkingTimeFunc(this, time);
767
- headers.push({
792
+ timeSlots.push({
768
793
  time,
769
- nonWorkingTime
794
+ nonWorkingTime,
795
+ style
770
796
  });
771
797
  }
772
798
  header = header.add(1, 'days');
@@ -774,7 +800,7 @@ export default class SchedulerData {
774
800
  } else if (this.cellUnit === CellUnit.Week) {
775
801
  while (header >= start && header <= end) {
776
802
  const time = header.format(DATE_FORMAT);
777
- headers.push({
803
+ timeSlots.push({
778
804
  time
779
805
  });
780
806
  header = header.add(1, 'weeks').startOf('isoWeek');
@@ -782,7 +808,7 @@ export default class SchedulerData {
782
808
  } else if (this.cellUnit === CellUnit.Month) {
783
809
  while (header >= start && header <= end) {
784
810
  const time = header.format(DATE_FORMAT);
785
- headers.push({
811
+ timeSlots.push({
786
812
  time
787
813
  });
788
814
  header = header.add(1, 'months').startOf('month');
@@ -790,13 +816,23 @@ export default class SchedulerData {
790
816
  } else if (this.cellUnit === CellUnit.Year) {
791
817
  while (header >= start && header <= end) {
792
818
  const time = header.format(DATE_FORMAT);
793
- headers.push({
819
+ timeSlots.push({
794
820
  time
795
821
  });
796
822
  header = header.add(1, 'years').startOf('year');
797
823
  }
798
824
  }
799
- this.headers = headers;
825
+ this.timeSlots = timeSlots;
826
+ if (this.isVerticalResourceView()) {
827
+ const currentViewDate = this.localeDayjs(new Date(this.startDate)).format(DATETIME_FORMAT);
828
+ this.headers = this.resources.map(r => ({
829
+ ...r,
830
+ time: currentViewDate,
831
+ nonWorkingTime: false
832
+ }));
833
+ } else {
834
+ this.headers = timeSlots;
835
+ }
800
836
  }
801
837
  _createInitHeaderEvents(header) {
802
838
  const start = this.localeDayjs(new Date(header.time));
@@ -815,6 +851,10 @@ export default class SchedulerData {
815
851
  } else {
816
852
  endValue = start.add(1, incrementUnit).format(DATETIME_FORMAT);
817
853
  }
854
+ } else if (this.isVerticalResourceView()) {
855
+ // In vertical view, headers are resources. They don't have a time span.
856
+ // But we need a start/end for the headerEvent structure.
857
+ endValue = startValue;
818
858
  } else {
819
859
  const {
820
860
  amount,
@@ -832,7 +872,12 @@ export default class SchedulerData {
832
872
  count: 0,
833
873
  addMore: 0,
834
874
  addMoreIndex: 0,
835
- events: Array(3)
875
+ events: [],
876
+ id: header.id || header.time,
877
+ style: header.style || {
878
+ width: this.getContentCellWidth(),
879
+ minWidth: this.getContentCellWidth()
880
+ }
836
881
  };
837
882
  }
838
883
  _createHeaderEvent(render, span, eventItem) {
@@ -879,7 +924,13 @@ export default class SchedulerData {
879
924
  this.eventGroups = eventGroups;
880
925
  }
881
926
  _createInitRenderData(isEventPerspective, eventGroups, resources, headers) {
882
- const slots = isEventPerspective ? eventGroups : resources;
927
+ const slots = this.isVerticalResourceView() ? this.timeSlots.map(ts => ({
928
+ ...ts,
929
+ id: ts.time,
930
+ name: ts.time,
931
+ slotId: ts.time,
932
+ slotName: ts.time
933
+ })) : isEventPerspective ? eventGroups : resources;
883
934
  const slotTree = [];
884
935
  const slotMap = new Map();
885
936
  slots.forEach(slot => {
@@ -974,7 +1025,7 @@ export default class SchedulerData {
974
1025
  // _createRenderData. Headers only cover the visible window (and visible days), so the event is
975
1026
  // clamped consistently across views, and calendar-aware dayjs math keeps DST days at one cell.
976
1027
  _getSpan(startTime, endTime, headers) {
977
- if (this.showAgenda) return 1;
1028
+ if (this.showAgenda || this.isVerticalResourceView()) return 1;
978
1029
  const eventStart = normalizeEventStart(startTime);
979
1030
  const eventEnd = normalizeEventEnd(endTime);
980
1031
  const {
@@ -1054,7 +1105,18 @@ export default class SchedulerData {
1054
1105
  const cellMaxEventsCountValue = 30;
1055
1106
  this.events.forEach(item => {
1056
1107
  let targetSlotIds = [];
1057
- if (this.isEventPerspective) {
1108
+ if (this.isVerticalResourceView()) {
1109
+ const eventStart = new Date(item.start);
1110
+ const eventEnd = new Date(item.end);
1111
+ this.timeSlots.forEach(ts => {
1112
+ // Check if the time slot overlaps with the event's time
1113
+ const tsStart = new Date(ts.time);
1114
+ const tsEnd = this.localeDayjs(ts.time).add(this.config.minuteStep, 'minutes').toDate();
1115
+ if (tsEnd > eventStart && tsStart < eventEnd) {
1116
+ targetSlotIds.push(ts.time);
1117
+ }
1118
+ });
1119
+ } else if (this.isEventPerspective) {
1058
1120
  targetSlotIds = [this._getEventSlotId(item)];
1059
1121
  } else if (item.resourceIds && item.resourceIds.length > 0) {
1060
1122
  // Multi-resource event
@@ -1072,9 +1134,18 @@ export default class SchedulerData {
1072
1134
  const eventEnd = normalizeEventEnd(item.end).toDate();
1073
1135
  let pos = -1;
1074
1136
  resourceEvents.headerItems.forEach((header, index) => {
1075
- const headerStart = new Date(header.start);
1076
- const headerEnd = new Date(header.end);
1077
- if (headerEnd > eventStart && headerStart < eventEnd) {
1137
+ let isOverlapping = false;
1138
+ if (this.isVerticalResourceView()) {
1139
+ // In vertical view, headers are resources. Check if event belongs to this resource.
1140
+ const itemResourceIds = item.resourceIds || [item.resourceId];
1141
+ const targetId = header.id;
1142
+ isOverlapping = itemResourceIds.includes(targetId);
1143
+ } else {
1144
+ const headerStart = new Date(header.start);
1145
+ const headerEnd = new Date(header.end);
1146
+ isOverlapping = headerEnd > eventStart && headerStart < eventEnd;
1147
+ }
1148
+ if (isOverlapping) {
1078
1149
  header.count += 1;
1079
1150
  if (header.count > resourceEvents.rowMaxCount) {
1080
1151
  resourceEvents.rowMaxCount = header.count;
@@ -1087,12 +1158,18 @@ export default class SchedulerData {
1087
1158
  while (header.events[tmp] !== undefined) tmp += 1;
1088
1159
  pos = tmp;
1089
1160
  }
1090
- let render = headerStart <= eventStart || index === 0;
1091
- if (render === false) {
1092
- const previousHeader = resourceEvents.headerItems[index - 1];
1093
- const previousHeaderStart = new Date(previousHeader.start);
1094
- const previousHeaderEnd = new Date(previousHeader.end);
1095
- if (previousHeaderEnd <= eventStart || previousHeaderStart >= eventEnd) render = true;
1161
+ let render = false;
1162
+ if (this.isVerticalResourceView()) {
1163
+ render = true;
1164
+ } else {
1165
+ const headerStart = new Date(header.start);
1166
+ render = headerStart <= eventStart || index === 0;
1167
+ if (render === false) {
1168
+ const previousHeader = resourceEvents.headerItems[index - 1];
1169
+ const previousHeaderStart = new Date(previousHeader.start);
1170
+ const previousHeaderEnd = new Date(previousHeader.end);
1171
+ if (previousHeaderEnd <= eventStart || previousHeaderStart >= eventEnd) render = true;
1172
+ }
1096
1173
  }
1097
1174
  header.events[pos] = this._createHeaderEvent(render, span, item);
1098
1175
  }
@@ -24,81 +24,21 @@ const initDndContext = (schedulerData, dndSources) => {
24
24
  };
25
25
 
26
26
  /**
27
- * Renders the scheduler UI with resource and agenda views, responsive sizing, and drag-and-drop support.
28
- * Optimized to minimize unnecessary re-renders through memoized callbacks, stable refs, and
29
- * selectively updated state.
27
+ * Render the full scheduler table including the header toolbar, resource column, timeline header, and all resource event rows.
30
28
  *
31
29
  * @param {object} props - Component properties.
32
- * @param {SchedulerData} props.schedulerData - The view model driving all scheduler state,
33
- * configuration, layout data, and rendering logic.
34
- * @param {Array<DnDSource>} [props.dndSources] - Additional drag-and-drop sources merged into
35
- * the scheduler's DnD context on first mount. The DnD context is initialized once and never rebuilt.
36
- * @param {React.RefObject<HTMLElement>} [props.parentRef] - Ref to the parent container element.
37
- * When provided alongside `responsiveByParent: true` in config, a ResizeObserver tracks the
38
- * parent's dimensions and triggers layout updates instead of listening to the window resize event.
39
- * @param {function(SchedulerData):void} props.prevClick - Callback invoked when the user navigates
40
- * to the previous time range.
41
- * @param {function(SchedulerData):void} props.nextClick - Callback invoked when the user navigates
42
- * to the next time range.
43
- * @param {function(SchedulerData, object):void} props.onViewChange - Callback invoked when the
44
- * view type, agenda toggle, or event perspective changes.
45
- * @param {function(SchedulerData, string|Date):void} props.onSelectDate - Callback invoked when
46
- * the user selects a new date from the calendar popover.
47
- * @param {function(SchedulerData, object):void} [props.onSetAddMoreState] - Callback invoked when
48
- * a '+N more' overflow indicator is clicked, used to control the AddMorePopover visibility and position.
49
- * @param {function(SchedulerData, object, string):void} [props.updateEventStart] - Callback invoked
50
- * when the user resizes the start of an event.
51
- * @param {function(SchedulerData, object, string):void} [props.updateEventEnd] - Callback invoked
52
- * when the user resizes the end of an event.
53
- * @param {function(SchedulerData, object, string, string, string, string):void} [props.moveEvent] -
54
- * Callback invoked when an event is dragged to a new slot or time range.
55
- * @param {function} [props.movingEvent] - Callback invoked continuously while an event is being dragged.
56
- * * @param {function(SchedulerData, object, string, string, string, string, string, string):void}
57
- * [props.conflictOccurred] -
58
- * Callback invoked when a scheduling conflict is detected during create, move, or resize.
59
- * @param {function(SchedulerData, object):string} [props.subtitleGetter] - Returns a subtitle string
60
- * displayed in the event item popover.
61
- * @param {function(SchedulerData, object):void} [props.eventItemClick] - Callback invoked when an
62
- * event item is clicked.
63
- * @param {function(SchedulerData, object):void} [props.viewEventClick] - Callback invoked when the
64
- * primary action link in the event popover is clicked.
65
- * @param {string} [props.viewEventText] - Label text for the primary action link in the event popover.
66
- * @param {function(SchedulerData, object):void} [props.viewEvent2Click] - Callback invoked when the
67
- * secondary action link in the event popover is clicked.
68
- * @param {string} [props.viewEvent2Text] - Label text for the secondary action link in the event popover.
69
- * @param {function(SchedulerData, string, string, string, string, string, object):void} [props.newEvent] -
70
- * Callback invoked when a new event is created by clicking and dragging on an empty cell, or when
71
- * an external item is dropped into the scheduler.
72
- * @param {function} [props.eventItemTemplateResolver] - Returns a custom JSX element used to render
73
- * each event item, overriding the default appearance.
74
- * @param {function(SchedulerData, object):void} [props.slotClickedFunc] - Callback invoked when a
75
- * resource slot label in the left column is clicked.
76
- * @param {function(SchedulerData, string):void} [props.toggleExpandFunc] - Callback invoked when a
77
- * parent resource slot's expand or collapse control is clicked.
78
- * @param {function} [props.slotItemTemplateResolver] - Returns a custom JSX element used to render
79
- * each resource slot cell in the left column.
80
- * @param {function} [props.nonAgendaCellHeaderTemplateResolver] - Returns a custom JSX element used
81
- * to render individual header cells in the timeline header row.
82
- * @param {function(SchedulerData, HTMLElement, number):void} [props.onScrollLeft] - Callback invoked
83
- * when the scheduler content is scrolled to the leftmost position.
84
- * @param {function(SchedulerData, HTMLElement, number):void} [props.onScrollRight] - Callback invoked
85
- * when the scheduler content is scrolled to the rightmost position.
86
- * @param {function(SchedulerData, HTMLElement, number):void} [props.onScrollTop] - Callback invoked
87
- * when the scheduler content is scrolled to the topmost position.
88
- * @param {function(SchedulerData, HTMLElement, number):void} [props.onScrollBottom] - Callback invoked
89
- * when the scheduler content is scrolled to the bottommost position.
90
- * @param {React.ReactNode} [props.leftCustomHeader] - Custom content rendered on the left side of
91
- * the scheduler header toolbar.
92
- * @param {React.ReactNode} [props.rightCustomHeader] - Custom content rendered on the right side of
93
- * the scheduler header toolbar.
94
- * @param {function} [props.CustomResourceHeader] - Optional component rendered inside the resource
95
- * column header cell, replacing the default resource name label.
96
- * @param {function} [props.CustomResourceCell] - Optional component rendered inside each resource
97
- * slot cell in the left column.
98
- * @param {object} [props.configTableHeaderStyle] - Optional inline style object merged into the
99
- * resource column header container, used to override default header styling.
100
- * @returns {JSX.Element} The root scheduler table element containing the header toolbar, resource
101
- * column, timeline header, and all resource event rows.
30
+ * @param {SchedulerData} props.schedulerData - View model that supplies configuration, layout, and rendering data for the scheduler.
31
+ * @param {Array<DnDSource>} [props.dndSources] - Additional drag-and-drop sources merged into the scheduler's DnD context on first mount.
32
+ * @param {React.RefObject<HTMLElement>} [props.parentRef] - Parent container ref used when `config.responsiveByParent` is true; a ResizeObserver tracks this element's size.
33
+ * @param {function(SchedulerData):void} props.prevClick - Called when navigating to the previous time range.
34
+ * @param {function(SchedulerData):void} props.nextClick - Called when navigating to the next time range.
35
+ * @param {function(SchedulerData, object):void} props.onViewChange - Called when the view type, agenda toggle, or event perspective changes.
36
+ * @param {function(SchedulerData, string|Date):void} props.onSelectDate - Called when a date is selected from the date picker.
37
+ * @param {function(SchedulerData, HTMLElement, number):void} [props.onScrollLeft] - Called when content reaches the leftmost scroll position.
38
+ * @param {function(SchedulerData, HTMLElement, number):void} [props.onScrollRight] - Called when content reaches the rightmost scroll position.
39
+ * @param {function(SchedulerData, HTMLElement, number):void} [props.onScrollTop] - Called when content reaches the topmost scroll position.
40
+ * @param {function(SchedulerData, HTMLElement, number):void} [props.onScrollBottom] - Called when content reaches the bottommost scroll position.
41
+ * @returns {JSX.Element} The root scheduler table element containing the rendered scheduler UI.
102
42
  */
103
43
 
104
44
  function Scheduler(props) {
@@ -369,7 +309,7 @@ function Scheduler(props) {
369
309
  weekNumberRowHeight
370
310
  } = config;
371
311
  const schedulerDataVersion = schedulerData.getVersion ? schedulerData.getVersion() : 0;
372
- const schedulerWidth = schedulerData.getContentTableWidth() - 1;
312
+ const schedulerWidth = schedulerData.getContentTableWidth();
373
313
  const weekNumberRowStyle = useMemo(() => ({
374
314
  height: weekNumberRowHeight
375
315
  }), [weekNumberRowHeight]);
@@ -466,7 +406,7 @@ function Scheduler(props) {
466
406
  height: contentHeight,
467
407
  overflowX: 'auto',
468
408
  overflowY: 'auto',
469
- width: resourceTableWidth + resourceScrollbarWidth - 2,
409
+ width: resourceTableWidth + resourceScrollbarWidth,
470
410
  margin: `0px -${contentScrollbarWidth}px 0px 0px`
471
411
  };
472
412
  if (config.schedulerMaxHeight > 0) {
@@ -598,6 +538,7 @@ function Scheduler(props) {
598
538
  style: schedulerHeadInnerStyle,
599
539
  children: /*#__PURE__*/_jsx("table", {
600
540
  className: "scheduler-bg-table",
541
+ style: schedulerInnerStyle,
601
542
  children: /*#__PURE__*/_jsx(HeaderView, {
602
543
  schedulerData: schedulerData,
603
544
  schedulerDataVersion: schedulerDataVersion,
@@ -658,15 +599,25 @@ function Scheduler(props) {
658
599
  rightCustomHeader: rightCustomHeader,
659
600
  leftCustomHeader: leftCustomHeader
660
601
  }), [schedulerHeaderStyle, handleViewChange, schedulerData, onSelect, goNext, goBack, rightCustomHeader, leftCustomHeader, setSchedulerHeaderRef]);
602
+
603
+ // table-layout: fixed sizes columns from the first row (the toolbar, colSpan=2), so pin the resource column here
604
+ const resourceColumnWidth = schedulerData.showAgenda || !config.resourceViewEnabled ? undefined : schedulerData.getResourceTableWidth();
661
605
  const rootTableStyle = useMemo(() => ({
662
- width: `${width}px`
606
+ width: `${width}px`,
607
+ tableLayout: 'fixed'
663
608
  }), [width]);
664
609
  return /*#__PURE__*/_jsxs("table", {
665
610
  id: "rbs-root",
666
- className: "rbs",
611
+ className: `rbs ${schedulerData.isVerticalResourceView() ? 'vertical-view' : ''}`,
667
612
  style: rootTableStyle,
668
613
  ref: schedulerRootRef,
669
- children: [/*#__PURE__*/_jsx("thead", {
614
+ children: [resourceColumnWidth !== undefined && /*#__PURE__*/_jsxs("colgroup", {
615
+ children: [/*#__PURE__*/_jsx("col", {
616
+ style: {
617
+ width: resourceColumnWidth
618
+ }
619
+ }), /*#__PURE__*/_jsx("col", {})]
620
+ }), /*#__PURE__*/_jsx("thead", {
670
621
  children: /*#__PURE__*/_jsx("tr", {
671
622
  children: /*#__PURE__*/_jsx("td", {
672
623
  colSpan: "2",
@@ -8,7 +8,8 @@ export const ViewType = {
8
8
  Year: 4,
9
9
  Custom: 5,
10
10
  Custom1: 6,
11
- Custom2: 7
11
+ Custom2: 7,
12
+ VerticalResource: 8
12
13
  };
13
14
  export const CellUnit = {
14
15
  Day: 0,
@@ -113,5 +113,10 @@ export default {
113
113
  viewType: ViewType.Year,
114
114
  showAgenda: false,
115
115
  isEventPerspective: false
116
+ }, {
117
+ viewName: 'Vertical',
118
+ viewType: ViewType.VerticalResource,
119
+ showAgenda: false,
120
+ isEventPerspective: false
116
121
  }]
117
122
  };
@@ -51,7 +51,8 @@
51
51
 
52
52
  .rbs .scheduler-view {
53
53
  border: 1px solid #e9e9e9;
54
- margin: 0 0 0 -1px;
54
+ position: relative;
55
+ left: -1px;
55
56
  padding: 0;
56
57
  }
57
58
 
@@ -90,6 +91,7 @@
90
91
  margin: 0;
91
92
  padding: 0;
92
93
  border-spacing: 0;
94
+ border-collapse: collapse;
93
95
  }
94
96
 
95
97
  .rbs table.resource-table,
@@ -165,6 +167,10 @@
165
167
  padding-right: 20px;
166
168
  }
167
169
 
170
+ .rbs.vertical-view .event-item {
171
+ margin: 0 1px;
172
+ }
173
+
168
174
  /* Rounded Borders */
169
175
  .rbs .round-all {
170
176
  border-radius: 14px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-big-schedule",
3
- "version": "6.0.0",
3
+ "version": "6.1.1-beta.0",
4
4
  "description": "React Big Schedule is a powerful and intuitive scheduler and resource planning solution built with React. Seamlessly integrate this modern browser-compatible component into your applications to effectively manage time, appointments, and resources. With drag-and-drop functionality, interactive UI, and granular views, react-big-schedule empowers users to effortlessly schedule and allocate resources with precision. Enhance productivity and streamline your workflow with this React-based solution, designed to optimize time management and simplify calendar-based operations. Perfect for applications requiring advanced scheduling capabilities, react-big-schedule offers a seamless and intuitive experience for managing appointments, resource allocation, and time slots. Unlock the potential of your React projects with react-big-schedule and revolutionize the way you handle scheduling and resource planning. It is the updated version of react-big-scheduler",
5
5
  "keywords": [
6
6
  "react-big-schedule",