pcm-shared-components 2.0.18 → 2.0.21

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,966 +0,0 @@
1
- function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
- import React, { useRef, useState, useEffect } from 'react';
3
- import Timeline, { TimelineMarkers, CustomMarker, TimelineHeaders, DateHeader, SidebarHeader } from 'react-calendar-timeline';
4
- import 'react-calendar-timeline/lib/Timeline.css';
5
- import './css/styles.css';
6
- import './css/header.css';
7
- import moment from 'moment';
8
- import Tooltip from 'react-tooltip-lite';
9
- // import TimelinePopUp from './components/TimelinePopUp'; **************** NOTE NOW USING TimelinePopUpComponent *************************
10
- import SearchLotsTextBox from './components/SearchLotsTextBox';
11
- import IconButton from '@mui/material/IconButton';
12
- import ZoomIn from '@mui/icons-material/ZoomIn';
13
- import ZoomOut from '@mui/icons-material/ZoomOut';
14
- import Edit from '@mui/icons-material/Edit';
15
- import Close from '@mui/icons-material/Close';
16
- import * as MDIIcon from '@mdi/react';
17
- import { mdiCursorMove } from '@mdi/js';
18
- import { DATE_TIME_FORMAT } from 'app/tools/date_formatter';
19
- import { doDatesConflictWithOtherReservations } from 'app/tools/date_overlap';
20
- import { makeStyles } from '@mui/styles';
21
- // import i18next from 'i18next';
22
- import PropTypes from 'prop-types';
23
- import { ThemeProvider } from '@mui/system';
24
- import { createTheme, useTheme } from '@mui/material/styles';
25
- const ZERO_HOUR = {
26
- hour: 0
27
- };
28
- let focus_debounce, new_res_debounce;
29
- const newItemId = -1;
30
- const useStyles = makeStyles(() => ({
31
- timelineWrapper: {
32
- fontFamily: 'sans-serif',
33
- fontSize: 'large',
34
- textAlign: 'center',
35
- maxWidth: '100%',
36
- maxHeight: '90vh !important',
37
- height: ({
38
- height
39
- }) => height,
40
- padding: ({
41
- padding
42
- }) => padding,
43
- overflow: 'scroll'
44
- }
45
- }));
46
-
47
- /**
48
- * ## Importing the component in your project
49
- *
50
- ```js
51
-
52
- import {ReservationTimelineComponent} from 'pcm-shared-components';
53
-
54
- ```
55
- *
56
- */
57
- export const ReservationTimelineComponent = props => {
58
- //Refs
59
- let useRefTimeline = useRef();
60
-
61
- //---------------------------------------------------
62
- // Props
63
- //---------------------------------------------------
64
- //todo addNewProps [onItemDeselect onAddNewReservation]
65
- const {
66
- editResId,
67
- lotData,
68
- reservationData,
69
- language,
70
- defaultTimeStart,
71
- defaultTimeEnd,
72
- newReservationPlaceholderTimeStart,
73
- newReservationPlaceholderTimeEnd,
74
- allowAddNewReservation,
75
- showAddNewReservationButtonInPopup,
76
- updateReservationTimelineResize,
77
- updateNewReservationTimeLineItem,
78
- updateReservationTimelineMove,
79
- beforeItemMoveDifferentLot,
80
- numberOfReservations,
81
- onNewResItemDoubleClick,
82
- onItemDoubleClick,
83
- onItemDeselect,
84
- onItemSelect,
85
- adjustNewResStartEndTime,
86
- height,
87
- padding,
88
- getNewKey,
89
- lineHeight,
90
- itemHeightRatio,
91
- TimelinePopUpComponent,
92
- i18next,
93
- theme
94
- } = props;
95
- const compTheme = theme ? theme : useTheme();
96
- const defaultTheme = createTheme(compTheme);
97
- const classes = useStyles(props);
98
-
99
- //---------------------------------------------------
100
- // Hooks
101
- //---------------------------------------------------
102
- const [lot_search, setLotSearch] = useState('');
103
- const [should_focus_on_lot_filter, setFocusOnFilter] = useState(false);
104
- const getLotNumberFromReservationId = reservationId => {
105
- try {
106
- const reservationObject = reservationData.find(rd => Number(rd.reservation_id) === Number(reservationId));
107
- if (reservationObject) {
108
- //found the res obj now return the lot number
109
- return reservationObject.lot_number;
110
- }
111
- } catch (error) {
112
- console.error(error);
113
- }
114
- return null;
115
- };
116
- const filterLots = (lot_number_to_search = '') => {
117
- let timeline_items;
118
- try {
119
- if (lot_number_to_search.length === 0) {
120
- timeline_items = lotData.map(item => {
121
- return {
122
- ...item,
123
- id: item.lot_id,
124
- title: item.lot_id
125
- };
126
- });
127
- } else {
128
- timeline_items = lotData.filter(filter_me => filter_me.lot_id.toLowerCase().indexOf(lot_number_to_search.toLowerCase()) >= 0).map(item => {
129
- return {
130
- ...item,
131
- id: item.lot_id,
132
- title: item.lot_id
133
- };
134
- });
135
- }
136
- if (String(editResId).length > 0) {
137
- //Okay so make sure we only display the group/lot number that is associated with this reservation id.
138
- const lotNumber = getLotNumberFromReservationId(editResId);
139
- if (lotNumber) {
140
- //found the res obj now filter all lot except for the one associated to this reservation.
141
- timeline_items = timeline_items.filter(ti => ti.id === lotNumber);
142
- }
143
- }
144
- clearTimeout(focus_debounce);
145
- focus_debounce = setTimeout(() => {
146
- //have a little bug where the text box was loosing focus then the timeline lot items are being updated, this is my workaround
147
- if (should_focus_on_lot_filter && String(editResId).length === 0) {
148
- document.getElementById('lot_filter_search').focus();
149
- }
150
- }, 0.5);
151
- } catch (error) {}
152
- return timeline_items;
153
- };
154
- const [state, setState] = useState({
155
- // if having loading issues then do this..
156
- // groups: filterLots(lot_search),
157
- canMove: false,
158
- canResize: false,
159
- // Think of "items" as "a singular phase" represented in the Timeline
160
- // It can be a "Think phase item", an "Explore phase item", or any "Build phase item"
161
- // Each item can be customized - given or requirements, all item configurations are the same
162
- //loading the data groups/lots here is a must or else I will loose them when I toggle from one viewer to another and back to this one.
163
- groups: [],
164
- //only force a lot group refresh if we have not focused on the text box yet that way we only fire it once or else it's getting fired multiple times...
165
- // groups: should_focus_on_lot_filter?[]:filterLots(lot_search),//only force a lot group refresh if we have not focused on the text box yet that way we only fire it wonce or else it's getting fired multiple times...
166
- // The only differentiator between items are the "group" they belong to, their "title" and their "start/end times"
167
- items: []
168
- });
169
-
170
- //---------------------------------------------------
171
- // Use Effect
172
- //---------------------------------------------------
173
- useEffect(() => {
174
- moment.locale(language);
175
- }, []);
176
- useEffect(() => {
177
- //morph the current lot data into what is needed to create the timeline groups
178
- // need at the minimum id, and title.
179
-
180
- if (lotData.length > 0) {
181
- setState(previousState => {
182
- return {
183
- ...previousState,
184
- groups: filterLots(lot_search)
185
- };
186
- });
187
- }
188
- }, [lotData]);
189
- useEffect(() => {
190
- try {
191
- // if (data.length > 0) {
192
- //morph the reservation into timeline items.
193
- setState(previousState => {
194
- return {
195
- ...previousState,
196
- items: filterReservation()
197
- };
198
- });
199
- // }
200
- } catch (error) {}
201
- }, [reservationData]);
202
- useEffect(() => {
203
- setState(previousState => {
204
- return {
205
- ...previousState,
206
- groups: filterLots(lot_search),
207
- items: filterReservation(lot_search)
208
- };
209
- });
210
- focus_debounce = setTimeout(() => {
211
- //have a little bug where the text box was loosing focus then the timeline lot items are being updated, this is my workaround
212
- if (should_focus_on_lot_filter && String(editResId) === 0) {
213
- document.getElementById('lot_filter_search').focus();
214
- }
215
- }, 0.05);
216
- }, [lot_search]);
217
- useEffect(() => {
218
- const newItem = state.items.find(item => Number(item.id) === -1);
219
- let sameDates = false;
220
- if (newItem) {
221
- // check to see if the place holder dates match the existing new item
222
- const start_time = newItem.start_time;
223
- const end_time = newItem.end_time;
224
- // When comparing dates involving '==' or '===' should use the '+' prefix
225
- // it will then compare the dates' millisecond values
226
- if (+new Date(start_time) === +new Date(newReservationPlaceholderTimeStart) && +new Date(end_time) === +new Date(newReservationPlaceholderTimeEnd)) {
227
- sameDates = true;
228
- }
229
- }
230
- if (newReservationPlaceholderTimeStart, newReservationPlaceholderTimeEnd && !sameDates) {
231
- // new res item doesn't exist with the current dates add it...
232
- addNewReservationItem(getLotNumberFromReservationId(editResId), newReservationPlaceholderTimeStart, getNumberOfNights(newReservationPlaceholderTimeStart, newReservationPlaceholderTimeEnd));
233
- }
234
- }, [newReservationPlaceholderTimeStart, newReservationPlaceholderTimeEnd]);
235
-
236
- //Functions
237
-
238
- /**
239
- * filterReservation
240
- *
241
- * Used to filter out the reservations that needs to be displayed on the timeline.
242
- * Note: cancelled and expired reservations are filtered out....
243
- *
244
- * @param {string} [lot_number_to_search='']
245
- * @returns returns the filtered reservation (will not return cancelled reservations and filtered reservations by lot number)
246
- */
247
- const filterReservation = (lot_number_to_search = '') => {
248
- let timeline_reservations = reservationData.map(res => {
249
- return {
250
- ...res,
251
- id: res.reservation_id,
252
- group: res.lot_number,
253
- title: res.reservation_id + ' ' + res.client_name,
254
- start_time: moment(res.arrival_date, DATE_TIME_FORMAT),
255
- end_time: moment(res.departure_date, DATE_TIME_FORMAT),
256
- className: timeLineItemClassName(res)
257
- // className: res.conflict_detected === 'No' ? (parseInt(res.transaction_status_id, 10) === 2 ? 'green_row' : 'yellow_row') : 'item_collision'
258
- };
259
- }).filter(res_f => parseInt(res_f.transaction_status_id, 10) <= 2 && parseInt(res_f.reservation_status_id, 10) !== 3);
260
- try {
261
- if (lot_number_to_search.length > 0) {
262
- timeline_reservations = timeline_reservations.filter(filter_me => filter_me.lot_number.toLowerCase().indexOf(lot_number_to_search.toLowerCase()) >= 0);
263
- }
264
-
265
- //set the number of displayed reservation in the timeline after all filtering....
266
- numberOfReservations(timeline_reservations.length);
267
- } catch (error) {}
268
- return timeline_reservations;
269
- };
270
- const getNewReservationStyling = (reservation_data, lot_number, startDate, endDate) => {
271
- return doDatesConflictWithOtherReservations(reservation_data, lot_number, startDate, endDate) ? 'item_collision' : 'border-dashed bg-gray-400/75';
272
- };
273
- const addNewReservationItem = (groupId, startDate, numberNights = 2) => {
274
- // Used to add an new reservation item on the timeline
275
- try {
276
- if (allowAddNewReservation) {
277
- const {
278
- newStartTime,
279
- newEndTime
280
- } = adjustNewResStartEndTime(startDate, startDate);
281
- const new_end_date_count = newEndTime.add(numberNights, 'days');
282
- //Add new reservation item, make sure to also remove the -1 = new res item before adding a new one...
283
- setState(previousState => {
284
- return {
285
- ...previousState,
286
- canResize: true,
287
- canMove: true,
288
- items: [...previousState.items.filter(item => Number(item.id) !== -1), {
289
- id: newItemId,
290
- group: groupId,
291
- title: i18next.t('reservation_new.title.new_reservation'),
292
- start_time: newStartTime,
293
- end_time: new_end_date_count,
294
- className: getNewReservationStyling(previousState.items.filter(item => Number(item.id) !== -1), groupId, newStartTime, new_end_date_count)
295
- }]
296
- };
297
- });
298
-
299
- //Pass up the new reservation from to date changes so I can adjust the parent component accordingly
300
- updateNewReservationTimeLineItem(newStartTime, new_end_date_count);
301
- }
302
- } catch (error) {
303
- console.error('Error adding new reservation time to timeline:', error);
304
- }
305
- };
306
- const handleAddNewReservationItem = (groupId, startDate, e) => {
307
- addNewReservationItem(groupId, startDate);
308
- };
309
- const timeLineItemClassName = res => {
310
- let class_name_collection = '';
311
- if (res.conflict_detected === 'No') {
312
- class_name_collection = parseInt(res.transaction_status_id, 10) === 2 ? 'green_row' : 'yellow_row';
313
- } else {
314
- class_name_collection = 'item_collision';
315
- }
316
- if (String(editResId) === String(res.reservation_id)) {
317
- class_name_collection += ' edit_res_id';
318
- }
319
- return class_name_collection;
320
- };
321
- const handleItemResize = (itemId, time) => {
322
- let res_id;
323
- let lot_number;
324
- let new_departure_date;
325
- let arrival_date;
326
-
327
- //? this will get the information needed of the item to proceed
328
- state.items.filter(f_item => f_item.id === itemId).map(item => {
329
- res_id = item.reservation_id;
330
- lot_number = item.group;
331
- //? This will be used as the default snapback time for my hours and minutes.
332
- //? What ever previous hours and minutes defined as the start time will be used as the default snapback time for the new start drag date time
333
- const current_time = moment(item.end_time).toDate();
334
-
335
- //? Need to set and adjust the new start time of the item based on it's previous hours and minutes so it always snaps to the same value
336
- new_departure_date = moment(moment(time).set({
337
- hour: current_time.getHours(),
338
- minute: current_time.getMinutes(),
339
- second: 0,
340
- millisecond: 0
341
- }));
342
-
343
- //need the current arrival date because the same call is made to the server weather we do a move or a resize
344
- arrival_date = item.start_time;
345
- //? note the item start time will not change when the item is resized as I only allow the right part of the item to change
346
- });
347
-
348
- if (Number(itemId) > newItemId) {
349
- // valid reservation item
350
- //make the same change in redux or store, and also reevaluate the reservations conflicts for all reservations associated to this lot number/res id
351
- updateReservationTimelineResize(res_id, lot_number, arrival_date, new_departure_date, getTimeDifference(arrival_date, new_departure_date));
352
- } else {
353
- // new reservation item
354
- setState(previousState => {
355
- return {
356
- ...previousState,
357
- items: [...previousState.items.map(it => {
358
- if (Number(it.id) === Number(itemId)) {
359
- return {
360
- ...it,
361
- end_time: moment(new_departure_date),
362
- className: getNewReservationStyling(previousState.items.filter(item => Number(item.id) !== -1), it.group, it.start_time, new_departure_date)
363
- };
364
- } else {
365
- return it;
366
- }
367
- })]
368
- };
369
- });
370
-
371
- //Pass up the new reservation from to date changes so I can adjust the parent component accordingly
372
- updateNewReservationTimeLineItem(arrival_date, new_departure_date);
373
- }
374
- };
375
- const handleItemMove = (itemId, dragTime, newGroupOrder) => {
376
- let res_id;
377
- let original_lot_number;
378
- let new_lot_number = groups[newGroupOrder].lot_id;
379
- let new_departure_date;
380
- let new_arrival_date;
381
- const ARE_WE_MOVING_TO_THE_SAME_LOT_ID = state.items.filter(f_item => f_item.id === itemId).find(gr => gr.group === new_lot_number) ? true : false;
382
- const moveItem = () => {
383
- state.items.filter(f_item => f_item.id === itemId).map(item => {
384
- res_id = item.reservation_id;
385
- original_lot_number = item.group;
386
-
387
- //? This will be used as the default snap time for my hours and minutes.
388
- //? What ever previous hours and minutes defined as the start time will be used as the default snap time for the new start drag date time
389
- let current_time = moment(item.start_time).toDate();
390
-
391
- //? Need to set and adjust the new start time of the item based on it's previous hours and minutes so it always snaps to the same value
392
- new_arrival_date = moment(moment(dragTime).set({
393
- hour: current_time.getHours(),
394
- minute: current_time.getMinutes(),
395
- second: 0,
396
- millisecond: 0
397
- }));
398
-
399
- //? Need to readjust the item end time to compensate the move
400
- new_departure_date = new_arrival_date + (item.end_time - item.start_time);
401
- });
402
- if (Number(itemId) > newItemId) {
403
- //make the same change in redux, and also reevaluate the reservations conflicts for all reservations associated to this lot number/res id
404
- updateReservationTimelineMove(res_id, original_lot_number, new_lot_number, new_arrival_date, new_departure_date, getTimeDifference(new_arrival_date, new_departure_date));
405
- } else {
406
- // new reservation item
407
- const {
408
- newStartTime,
409
- newEndTime
410
- } = adjustNewResStartEndTime(new_arrival_date, new_departure_date);
411
- setState(previousState => {
412
- return {
413
- ...previousState,
414
- items: [...previousState.items.map(it => {
415
- if (Number(it.id) === Number(itemId)) {
416
- return {
417
- ...it,
418
- group: new_lot_number,
419
- start_time: newStartTime,
420
- end_time: newEndTime,
421
- className: getNewReservationStyling(previousState.items.filter(item => Number(item.id) !== -1), new_lot_number, newStartTime, newEndTime)
422
- };
423
- } else {
424
- return it;
425
- }
426
- })]
427
- };
428
- });
429
-
430
- //Pass up the new reservation from to date changes so I can adjust the parent component accordingly
431
- updateNewReservationTimeLineItem(newStartTime, newEndTime);
432
- }
433
- };
434
- if (!ARE_WE_MOVING_TO_THE_SAME_LOT_ID) {
435
- //This is where we check to see if the lot has a notification. If it does then prompt the user with the message. Else keep trucking...
436
- // showLotNotificationPopup({ ...groups[newGroupOrder] }, moveItem);
437
- beforeItemMoveDifferentLot({
438
- ...groups[newGroupOrder]
439
- }, moveItem);
440
- } else {
441
- // We are moving on the same lot number no need to check for lot notification.
442
- moveItem();
443
- }
444
- };
445
- const getTimeDifference = (start_time, end_time, simulate_time_dragging = false) => {
446
- if (simulate_time_dragging) {
447
- // Need to set the hours to 24/midnight so it properly simulated with the timeline items are dragged/resized across days. Or else it feels weird when looking at the days increase
448
- start_time = moment(moment(start_time).set({
449
- hour: 24,
450
- minute: 0,
451
- second: 0,
452
- millisecond: 0
453
- }));
454
- }
455
- return moment(end_time).diff(start_time, 'days') + 1;
456
- };
457
- const getNumberOfNights = (from_date, to_date) => {
458
- let n_nights = 0;
459
- try {
460
- let dt1 = new Date(from_date);
461
- let dt2 = new Date(to_date);
462
- n_nights = Math.floor((Date.UTC(dt2.getFullYear(), dt2.getMonth(), dt2.getDate()) - Date.UTC(dt1.getFullYear(), dt1.getMonth(), dt1.getDate())) / (1000 * 60 * 60 * 24));
463
- if (!Number(n_nights) || n_nights < 0) {
464
- n_nights = 0;
465
- }
466
- } catch (e) {
467
- return 0;
468
- // console.error(e.message);
469
- }
470
-
471
- return n_nights;
472
- };
473
- const isNewItemId = id => {
474
- //Returns true if this id is from a new item id being added to the timeline
475
- return Number(id) === newItemId;
476
- };
477
- /**
478
- * customItem
479
- *
480
- * Used to display the tooltip of the item when resizing it
481
- *
482
- * @param {*} { item, itemContext, getItemProps, getResizeProps }
483
- * @returns
484
- */
485
- const customItem = props => {
486
- // const { left: leftResizeProps } = getResizeProps();
487
- const {
488
- item,
489
- itemContext,
490
- getItemProps,
491
- getResizeProps
492
- } = props;
493
- try {
494
- const {
495
- right: rightResizeProps
496
- } = getResizeProps();
497
- const itemProps = getItemProps(item.itemProps);
498
- const isPhaseSelected = itemContext.selected && item.group !== 4;
499
- let followingTooltipsMarkup = null;
500
- if (isPhaseSelected) {
501
- const timeDifference = getTimeDifference(item.start_time, itemContext.resizeTime, true);
502
- const n_nights = i18next.t('reservation_grid.header.number_of_nights');
503
- const daysTooltipMarkup = itemContext.resizing ? ` ${n_nights} ${timeDifference} ` : null;
504
- const tooltipMarkup = /*#__PURE__*/React.createElement("div", {
505
- className: "rct-item-tooltip rct-item-following-indicator ",
506
- style: {
507
- position: 'absolute',
508
- left: itemContext.dimensions.left + itemContext.dimensions.width,
509
- top: itemContext.dimensions.top,
510
- height: `${itemContext.dimensions.height}px`
511
- }
512
- }, /*#__PURE__*/React.createElement("div", {
513
- className: "rct-item-tooltip-content"
514
- }, daysTooltipMarkup));
515
- followingTooltipsMarkup = /*#__PURE__*/React.createElement("div", null, tooltipMarkup);
516
- }
517
- const handleRightSide = itemContext.useResizeHandle && isPhaseSelected ? /*#__PURE__*/React.createElement("div", rightResizeProps, /*#__PURE__*/React.createElement("span", {
518
- className: state.canResize ? 'handle-icon' : ''
519
- })) : null;
520
- const handleLeftSide = itemContext.useResizeHandle && isPhaseSelected ? /*#__PURE__*/React.createElement("div", null, state.canMove && /*#__PURE__*/React.createElement(MDIIcon.Icon, {
521
- path: mdiCursorMove,
522
- size: 1.5,
523
- color: "charcoal",
524
- className: 'move-icon'
525
- })) : null;
526
- return /*#__PURE__*/React.createElement("div", {
527
- className: `rct-item-wrapper ${itemContext.resizing ? 'rct-item--is-resizing' : ''} ${itemContext.selected ? 'rct-item--is-selected' : ''}`
528
- }, /*#__PURE__*/React.createElement("div", itemProps, /*#__PURE__*/React.createElement(Tooltip, {
529
- zIndex: 2000,
530
- content: /*#__PURE__*/React.createElement(TimelinePopUpComponent, {
531
- res: item,
532
- showAddNewReservationButtonInPopup: showAddNewReservationButtonInPopup,
533
- getTimeDifference: getTimeDifference,
534
- addNewResOnClick: startAddingNewReservation
535
- }),
536
- mouseOutDelay: 500
537
- }, /*#__PURE__*/React.createElement("div", {
538
- className: "rct-item-content cursor-pointer h-full w-full",
539
- style: {
540
- maxHeight: `${itemContext.dimensions.height}`
541
- }
542
- }, itemContext.title)), handleLeftSide, handleRightSide), followingTooltipsMarkup);
543
- } catch (error) {
544
- console.error('Error in timeline custom item:', error);
545
- }
546
- };
547
- const getItemById = id => {
548
- return state.items.find(item => item.id === id);
549
- };
550
- const getLotByGroupId = groupId => {
551
- //Note groupId is the lot number, it's just how the timeline works.
552
- return lotData.find(ld => ld.lot_id === groupId);
553
- };
554
-
555
- // const getItemById = (itemId) => {
556
- // return state.items.find((item) => item.id === itemId);
557
- // };
558
-
559
- /**
560
- * groupRenderer
561
- *
562
- * Changes the styling of the lot number item on the group column
563
- *
564
- * @param {*} { group }
565
- * @returns
566
- */
567
- const groupRenderer = ({
568
- group
569
- }) => {
570
- return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
571
- style: {
572
- color: group.title === '106' ? 'red' : 'black'
573
- }
574
- }, group.title));
575
- };
576
- const toggleTimelineEdit = () => {
577
- setState({
578
- ...state,
579
- canMove: !state.canMove,
580
- canResize: !state.canResize
581
- });
582
- };
583
- const turnOffTimelineEdit = () => {
584
- setState(previousState => {
585
- return {
586
- ...previousState,
587
- canMove: false,
588
- canResize: false
589
- };
590
- });
591
- };
592
- const turnOnTimelineEdit = () => {
593
- setState(previousState => {
594
- return {
595
- ...previousState,
596
- canMove: true,
597
- canResize: true
598
- };
599
- });
600
- };
601
- const handleRelativeZoomOutClick = () => {
602
- useRefTimeline.current.changeZoom(1.25);
603
- };
604
- const handleRelativeZoomInClick = () => {
605
- useRefTimeline.current.changeZoom(0.75);
606
- };
607
-
608
- // --------------------------------------------
609
- // Render used to make the month and year sticky scroll sliding
610
- // --------------------------------------------
611
- // const isWeekendDay = (intervalContext, data) => {
612
- // if (data.isMonth) {
613
- // return false;
614
- // }
615
- // const day = intervalContext.interval.startTime.day();
616
- // return day === 6 || day === 0; // Saturday or Sunday
617
- // };
618
- // const isCurrentDay = (intervalContext, data) => {
619
- // return !data.isMonth && intervalContext.interval.startTime.isSame(data.currentDate, 'day');
620
- // };
621
-
622
- // const itemRenderer = ({ item, itemContext, getItemProps }) => {
623
- // return (
624
- // <div
625
- // {...getItemProps({
626
- // style: {
627
- // display: 'flex',
628
- // alignItems: 'center',
629
- // background: item.start_time.isAfter() ? '#aaa' : '#d32f2f',
630
- // border: `3px solid ${itemContext.selected ? '#fff700' : 'transparent'}`,
631
- // borderRadius: '12.5px',
632
- // boxShadow: 'rgba(0, 0, 0, 0.16) 0 0.3rem 0.6rem'
633
- // }
634
- // })}
635
- // >
636
- // <div
637
- // style={{
638
- // position: 'sticky',
639
- // left: '0',
640
- // display: 'inline-block',
641
- // overflow: 'hidden',
642
- // padding: '0 1rem',
643
- // textOverflow: 'ellipsis',
644
- // whiteSpace: 'nowrap'
645
- // }}
646
- // >
647
- // {itemContext.title}
648
- // </div>
649
- // </div>
650
- // );
651
- // };
652
-
653
- // const emptySlotClicked = (groupId, time) => {
654
- // // console.log('group id : ', groupId, ' time: ',time)
655
- // };
656
-
657
- // const onTimeChange = (start, end, updateScrollCanvas) => {
658
- // // this.setState({ zoom_out_level: 1 });
659
- // // this.zoom_days = moment(start).diff(end, "days");
660
-
661
- // // console.log(
662
- // // "hours: ",
663
- // // moment(start).diff(end, "hours"),
664
- // // "days: ",
665
- // // this.zoom_days,
666
- // // "months:",
667
- // // moment(start).diff(end, "months")
668
- // // );
669
- // // console.log("start: ", start, " end: ", end);
670
-
671
- // updateScrollCanvas(start, end);
672
- // };
673
-
674
- //Makes the year month at the top of the timeline stick to the left side when scrolling
675
- //And also bold the weekends and makes the current day red
676
- const intervalRenderer = ({
677
- intervalContext,
678
- getIntervalProps,
679
- data
680
- }) => {
681
- return /*#__PURE__*/React.createElement("div", _extends({}, getIntervalProps(), {
682
- className: `rct-dateHeader ${data.isMonth ? 'rct-dateHeader-primary' : ''}`,
683
- onClick: () => {
684
- return false;
685
- }
686
- }), /*#__PURE__*/React.createElement("span", {
687
- style: {
688
- position: data.isMonth ? 'sticky' : 'static',
689
- marginRight: data.isMonth ? 'auto' : 'inherit',
690
- left: '50%',
691
- padding: '0 1rem'
692
- // fontWeight: isWeekendDay(intervalContext, data) || isCurrentDay(intervalContext, data) ? '400' : '300',
693
- // color: isCurrentDay(intervalContext, data) ? '#d32f2f' : '#000'
694
- }
695
- }, intervalContext.intervalText));
696
- };
697
- const startAddingNewReservation = () => {
698
- //Note the new item id will always be -1 because that's what I set it as and there will only ever be one of them.
699
-
700
- try {
701
- clearTimeout(new_res_debounce);
702
- new_res_debounce = setTimeout(() => {
703
- //Get the group item so I can trace it back to the lot data item
704
- const groupItem = getItemById(newItemId);
705
- if (groupItem) {
706
- //Get the lot object, lot object is needed for the new res panel to know which lot is currently selected.
707
- const lot_item = getLotByGroupId(groupItem.group);
708
- const activeResParts = {
709
- reservation: {
710
- end_date: groupItem.end_time.format(DATE_TIME_FORMAT),
711
- start_date: groupItem.start_time.format(DATE_TIME_FORMAT)
712
- },
713
- lot: {
714
- ...lot_item
715
- }
716
- };
717
- onNewResItemDoubleClick(activeResParts);
718
-
719
- //Remove the edit mode on the timeline.
720
- turnOffTimelineEdit();
721
- }
722
- }, 300);
723
- } catch (error) {
724
- console.error('Error in startAddingNewReservation: ', error);
725
- }
726
- };
727
- const handleOnItemDoubleClick = resId => {
728
- try {
729
- if (String(editResId).length === 0) {
730
- if (!isNewItemId(resId)) {
731
- //Valid reservation, Edit the reservation
732
- onItemDoubleClick(resId);
733
- } else {
734
- //New reservation item, start adding the new reservation\
735
- startAddingNewReservation();
736
- }
737
- }
738
- } catch (error) {
739
- console.error('error in handleOnItemDoubleClick', error);
740
- }
741
- };
742
- const {
743
- items,
744
- groups
745
- } = state;
746
- const todayInMilliseconds = moment().valueOf();
747
- return /*#__PURE__*/React.createElement(ThemeProvider, {
748
- theme: defaultTheme
749
- }, /*#__PURE__*/React.createElement("div", {
750
- key: "timeline_main_1",
751
- className: classes.timelineWrapper
752
- }, /*#__PURE__*/React.createElement(Timeline, {
753
- key: getNewKey ? Math.random() : 'time_line_key' //Used to re-render the timeline each time the default dates change so I see the changes applied
754
- ,
755
- groups: groups,
756
- items: items,
757
- itemsSorted: true,
758
- canResize: state.canResize,
759
- canMove: state.canMove,
760
- itemTouchSendsClick: false,
761
- stackItems: true,
762
- defaultTimeStart: defaultTimeStart,
763
- defaultTimeEnd: defaultTimeEnd,
764
- minZoom: 16 * 60 * 60 * 1000,
765
- maxZoom: 1 * 365.24 * 86400 * 1000,
766
- lineHeight: lineHeight,
767
- itemHeightRatio: itemHeightRatio,
768
- onItemResize: handleItemResize,
769
- onItemMove: handleItemMove,
770
- onItemDeselect: e => {
771
- onItemDeselect();
772
- },
773
- onItemSelect: resId => {
774
- if (Number(resId) > newItemId) {
775
- onItemSelect(resId);
776
- }
777
- },
778
- onItemDoubleClick: handleOnItemDoubleClick,
779
- onCanvasDoubleClick: handleAddNewReservationItem,
780
- itemRenderer: customItem
781
- // groupRenderer={groupRenderer}
782
- ,
783
- useResizeHandle: true
784
- // fullUpdate
785
- // fixedHeader="sticky"
786
- // stickyOffset={40}
787
-
788
- // traditionalZoom={false}
789
- // dragSnap={1000 * 60 * 60 * 24}
790
- // onCanvasClick={emptySlotClicked}
791
- // onTimeChange={onTimeChange}
792
- // onItemSelect={handleItemSelect}
793
- ,
794
- ref: useRefTimeline
795
- }, /*#__PURE__*/React.createElement(TimelineHeaders, {
796
- key: "time_line_headers_1",
797
- className: "sticky"
798
- }, /*#__PURE__*/React.createElement(SidebarHeader, {
799
- key: "today_sidebar_1"
800
- }, ({
801
- getRootProps
802
- }) => {
803
- return /*#__PURE__*/React.createElement("div", _extends({
804
- id: "timeline_headers_1"
805
- }, getRootProps(), {
806
- className: "left-header"
807
- }), /*#__PURE__*/React.createElement("div", {
808
- className: "btn-wrapper"
809
- }, String(editResId).length === 0 && /*#__PURE__*/React.createElement(IconButton, {
810
- color: "inherit",
811
- onClick: toggleTimelineEdit,
812
- className: state.canMove ? 'blink' : ''
813
- }, /*#__PURE__*/React.createElement(Edit, null)), /*#__PURE__*/React.createElement(IconButton, {
814
- color: "inherit",
815
- onClick: handleRelativeZoomInClick
816
- }, /*#__PURE__*/React.createElement(ZoomIn, null)), /*#__PURE__*/React.createElement(IconButton, {
817
- color: "inherit",
818
- onClick: handleRelativeZoomOutClick
819
- }, /*#__PURE__*/React.createElement(ZoomOut, null)), String(editResId).length === 0 && /*#__PURE__*/React.createElement("div", {
820
- className: "flex ml-6 "
821
- }, /*#__PURE__*/React.createElement(SearchLotsTextBox, {
822
- label: i18next.t('reservation_grid.header.lot_number'),
823
- lot_search: lot_search,
824
- setLotSearch: setLotSearch,
825
- setFocusOnFilter: setFocusOnFilter,
826
- should_focus_on_lot_filter: should_focus_on_lot_filter
827
- }), lot_search && lot_search.length > 0 && /*#__PURE__*/React.createElement(IconButton, {
828
- color: "inherit",
829
- style: {
830
- marginTop: 8,
831
- maxHeight: 46,
832
- maxWidth: 46
833
- },
834
- onClick: () => setLotSearch('')
835
- }, /*#__PURE__*/React.createElement(Close, null)))));
836
- }), /*#__PURE__*/React.createElement(DateHeader, {
837
- key: "primary_date_header",
838
- unit: "primaryHeader",
839
- labelFormat: "MMMM YYYY",
840
- headerData: {
841
- isMonth: true
842
- },
843
- intervalRenderer: intervalRenderer
844
- }), /*#__PURE__*/React.createElement(DateHeader, {
845
- key: "secondary_date_header",
846
- height: 70,
847
- headerData: {
848
- isMonth: false
849
- }
850
- })), /*#__PURE__*/React.createElement(TimelineMarkers, {
851
- key: "timeline_markers_2"
852
- }, /*#__PURE__*/React.createElement(CustomMarker, {
853
- key: "custom_marker_1",
854
- date: todayInMilliseconds
855
- }, ({
856
- styles
857
- }) => {
858
- const customStyles = {
859
- ...styles,
860
- backgroundColor: 'red',
861
- width: '1.5px',
862
- zIndex: 1
863
- };
864
- return /*#__PURE__*/React.createElement("div", {
865
- style: customStyles
866
- });
867
- })))));
868
- };
869
- export default ReservationTimelineComponent;
870
- ReservationTimelineComponent.defaultProps = {
871
- language: 'en',
872
- editResId: '',
873
- getNewKey: false,
874
- allowAddNewReservation: true,
875
- showAddNewReservationButtonInPopup: true,
876
- reservationData: [],
877
- lotData: [],
878
- lineHeight: 50,
879
- itemHeightRatio: 0.70,
880
- height: '90vh !important',
881
- padding: '0 16px',
882
- defaultTimeStart: moment(ZERO_HOUR).subtract(7, 'days'),
883
- defaultTimeEnd: moment(ZERO_HOUR).add(14, 'days'),
884
- newReservationPlaceholderTimeStart: undefined,
885
- newReservationPlaceholderTimeEnd: undefined,
886
- onItemSelect: () => {},
887
- onItemDeselect: () => {},
888
- onItemDoubleClick: () => {},
889
- onNewResItemDoubleClick: () => {},
890
- updateNewReservationTimeLineItem: () => {},
891
- updateReservationTimelineMove: () => {},
892
- numberOfReservations: () => {},
893
- updateReservationTimelineResize: () => {},
894
- beforeItemMoveDifferentLot: ({
895
- ...props
896
- }, onMove) => {
897
- onMove();
898
- },
899
- //Note if this props is not supplied then we simply move the item without doing any additional work
900
- adjustNewResStartEndTime: (arrivalDate, departureDate) => {
901
- return {
902
- newStartTime: moment(arrivalDate),
903
- newEndTime: moment(departureDate)
904
- };
905
- },
906
- //If no functions defined then it will return the same arrival and departure dates with the same time without any changes
907
- TimelinePopUpComponent: undefined,
908
- i18next: undefined,
909
- theme: undefined
910
- };
911
- ReservationTimelineComponent.propTypes = {
912
- /** This is the component displayed inside of the tooltip when hovering over the timeline items*/
913
- TimelinePopUpComponent: PropTypes.elementType,
914
- /** Indicates to the timeline to use a new key value. Useful when I need the timeline to refresh after an action. And bad in some cases where I don't wan the grid to update. Only use when I'm changing the defaultTimeStart and defaultTimeEnd date so the timeline reflect the changes made. Don't use on a standard timeline or else I will lose my focus each time I select an item that is out of the scope of the initial view port*/
915
- getNewKey: PropTypes.bool,
916
- /** Allows adding the new reservation item to the timeline*/
917
- allowAddNewReservation: PropTypes.bool,
918
- /** Controls if we see the Add New Reservation button in the popup when hovering over the new res item*/
919
- showAddNewReservationButtonInPopup: PropTypes.bool,
920
- /** Language string to use for the timeline */
921
- language: PropTypes.string,
922
- /** Converts the timeline into a res edit format if a valid reservation id is supplied. Edits are disabled and the reservation is highlighted so we know which item we are focusing on */
923
- editResId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
924
- /** Reservation objects used to create the item/reservation on the timeline*/
925
- reservationData: PropTypes.array,
926
- /** Lot objects used to cerate the groups*/
927
- lotData: PropTypes.array,
928
- /** Height of each group*/
929
- lineHeight: PropTypes.number,
930
- /** Height ration of each item inside the group based on the group height*/
931
- itemHeightRatio: PropTypes.number,
932
- /** Height of the component in plain CSS notation*/
933
- height: PropTypes.string,
934
- /** Padding around the component in plain CSS notation*/
935
- padding: PropTypes.string,
936
- /** Controls the starting position of the timeline, defaults to today - 7 days */
937
- defaultTimeStart: PropTypes.oneOfType([PropTypes.instanceOf(moment), PropTypes.instanceOf(Date)]),
938
- /** Controls the ending position of the timeline, defaults to today + 14 days */
939
- defaultTimeEnd: PropTypes.oneOfType([PropTypes.instanceOf(moment), PropTypes.instanceOf(Date)]),
940
- /** Controls the starting position of the new reservation placeholder */
941
- newReservationPlaceholderTimeStart: PropTypes.instanceOf(Date),
942
- /** Controls the ending position of the new reservation placeholder */
943
- newReservationPlaceholderTimeEnd: PropTypes.instanceOf(Date),
944
- /** Callback when an item is clicked. (resId) is passed to the function */
945
- onItemSelect: PropTypes.func,
946
- /** Callback when an item is un-clicked. no props passed to the function*/
947
- onItemDeselect: PropTypes.func,
948
- /** Callback when an item is double clicked. (resId) is passed to the function*/
949
- onItemDoubleClick: PropTypes.func,
950
- /** Callback to return the number of reservations being displayed in the timeline (number) one prop is return containing the number of reservations */
951
- numberOfReservations: PropTypes.func,
952
- /** Callback to pass back upwards the new dates of the new reservation item so we can adjust the parent component's date accordingly (arrivalDate, newDepartureDate)*/
953
- updateNewReservationTimeLineItem: PropTypes.func,
954
- /** Callback to save the changes made ot the object in our store. props passed to the callback function (reservation_id, lot_number, arrival_date, departure_date, number_of_nights) */
955
- updateReservationTimelineResize: PropTypes.func,
956
- /** Callback to save the changes made ot the object in our store. props passed to the callback function (reservation_id, original_lot_number, new_lot_number, arrival_date, departure_date, number_of_nights) */
957
- updateReservationTimelineMove: PropTypes.func,
958
- /** Callback to allow us to hook in some logic before committing to change the lot to the different lot. ({...props from item groups},moveItem()) */
959
- beforeItemMoveDifferentLot: PropTypes.func,
960
- /** Callback to allow us to update the arrival and departure time of the arrivalDate departureDate. Used when adding a new reservation so we can use the specified times from the seasonal object. props=(arrivalDate, departureDate) must return the following shape {newStartTime,newEndTime}*/
961
- adjustNewResStartEndTime: PropTypes.func,
962
- /** The i18next needs to be passed in by the component or else they will be blank */
963
- i18next: PropTypes.any,
964
- /** Theme object to use on this component, if none provided then the MUI5 theme provider theme is used*/
965
- theme: PropTypes.object
966
- };