react-big-schedule 6.1.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.
- package/dist/components/BodyView.js +24 -10
- package/dist/components/EventItem.js +13 -6
- package/dist/components/HeaderView.js +30 -8
- package/dist/components/ResourceEvents.js +19 -5
- package/dist/components/ResourceView.js +21 -1
- package/dist/components/SchedulerData.js +110 -33
- package/dist/components/index.js +29 -78
- package/dist/config/default.js +2 -1
- package/dist/config/scheduler.js +5 -0
- package/dist/css/style.css +7 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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 =>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
endTime = localeDayjs(new Date(
|
|
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()
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
819
|
+
timeSlots.push({
|
|
794
820
|
time
|
|
795
821
|
});
|
|
796
822
|
header = header.add(1, 'years').startOf('year');
|
|
797
823
|
}
|
|
798
824
|
}
|
|
799
|
-
this.
|
|
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:
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
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 =
|
|
1091
|
-
if (
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
const
|
|
1095
|
-
|
|
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
|
}
|
package/dist/components/index.js
CHANGED
|
@@ -24,81 +24,21 @@ const initDndContext = (schedulerData, dndSources) => {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
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 -
|
|
33
|
-
*
|
|
34
|
-
* @param {
|
|
35
|
-
*
|
|
36
|
-
* @param {
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* @param {function(SchedulerData):void} props.
|
|
40
|
-
*
|
|
41
|
-
* @param {function(SchedulerData):void} props.
|
|
42
|
-
*
|
|
43
|
-
* @
|
|
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()
|
|
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
|
|
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:
|
|
611
|
+
className: `rbs ${schedulerData.isVerticalResourceView() ? 'vertical-view' : ''}`,
|
|
667
612
|
style: rootTableStyle,
|
|
668
613
|
ref: schedulerRootRef,
|
|
669
|
-
children: [/*#__PURE__*/
|
|
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",
|
package/dist/config/default.js
CHANGED
package/dist/config/scheduler.js
CHANGED
package/dist/css/style.css
CHANGED
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
|
|
52
52
|
.rbs .scheduler-view {
|
|
53
53
|
border: 1px solid #e9e9e9;
|
|
54
|
-
|
|
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.1.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",
|