diff --git a/src/widgets/scheduler/Scheduler.js b/src/widgets/scheduler/Scheduler.js index d490f8b..f131817 100644 --- a/src/widgets/scheduler/Scheduler.js +++ b/src/widgets/scheduler/Scheduler.js @@ -1,12 +1,10 @@ import './scheduler.css'; import { El } from '../../utils/dom'; +import { Boot } from '../../core/Boot'; export class Scheduler { - /** - * Scheduler, Constructor - */ constructor(target, opts) { this.target = target; @@ -33,9 +31,19 @@ export class Scheduler { this.target.dxData('scheduler', this); + this.data = {}; + this.group = []; + this.ui = {}; - this.eventList = []; + this.enabled = true; + this.busy = false; + + this.widget = Boot.intData?.widget?.scheduler?.children; + + if (!this.widget) { + return; + } this.resetSelection(); @@ -44,9 +52,6 @@ export class Scheduler { this.setUiEvents(); } - /** - * Scheduler, Render Container - */ renderContainer() { this.ui.wrapper = this.target.appendChild( El('div', { className: 'sp-scheduler' }) ); @@ -115,18 +120,16 @@ export class Scheduler { uiSlot = El('div', { className: 'time-slot', style: 'height: '+ this.opts.cellSize + 'px;' }); - uiSlot.dataset.time = hours.toString().padStart(2, '0') + ':' + mins.toString().padStart(2, '0'); mins += this.opts.unitTime; this.ui.events.append( uiSlot ); } + + this.trigger('onInit'); } - /** - * Scheduler, Set UI Events - */ setUiEvents() { this.ui.events.dxOn('mousedown', '.time-slot', (e, t) => !e.button && this.mouseDownHandler(e, t)); @@ -144,7 +147,7 @@ export class Scheduler { this.ui.label.classList.add('hide'); - this.trigger('slotSelected'); + this.trigger('onSlotSelect'); this.selTarget = null; } @@ -259,6 +262,10 @@ export class Scheduler { } } + getFormattedTime(hour, min) { + return hour.toString().padStart(2, '0') + ':' + min.toString().padStart(2, '0') + } + setSelectedData() { this.selectedTime = { @@ -269,18 +276,18 @@ export class Scheduler { this.selectedTime.end = this.slotEnd.dataset.time } else { - this.selectedTime.end = this.opts.endTime[0].toString().padStart(2, '0') + ':' + this.opts.endTime[1].toString().padStart(2, '0') + this.selectedTime.end = this.getFormattedTime(this.opts.endTime[0], this.opts.endTime[1]); } } - + showLabel() { if (!this.ui.label) { - this.ui.label = this.ui.events.appendChild( El('div', { className: 'time-label' }) ); + this.ui.label = this.ui.events.appendChild( El('div', { class: 'time-label' }) ); } - this.ui.label.textContent = this.selectedTime.start + ' - ' + this.selectedTime.end + ' ( ' + this.getTimeFormat() + ' )'; - + this.ui.label.textContent = this.selectedTime.start + ' - ' + this.selectedTime.end + ' ( ' + this.getFormattedLabel() + ' )'; + this.ui.label.classList.remove('hide'); } @@ -290,7 +297,7 @@ export class Scheduler { this.ui.label.style.left = (e.pageX - this.ui.events.dxOffset().left + 14) + 'px'; } - getTimeFormat() { + getFormattedLabel() { let raw = ''; @@ -301,118 +308,95 @@ export class Scheduler { let hours = total >= 60 ? Math.floor(total / 60) : 0; let mins = total % 60; - if (hours > 0) { - - if (hours == 1) { - raw += 'o oră'; - } - else { - raw += hours + ' ore'; - } - - if (mins) { - raw += ' și '; - } + if (!hours) { + raw = this.widget.label.minutes.replace('%minutes', mins); } - - if (mins) { - raw += mins + ' minute'; + else if (hours == 1) { + raw = !mins ? this.widget.label.one_hour : this.widget.label.one_hour_minutes.replace('%minutes', mins); + } + else { + raw = !mins ? this.widget.label.hours.replace('%hours', hours) : this.widget.label.hours_minutes.replace('%hours', hours).replace('%minutes', mins); } return raw; } - addEvent(content) { - - let count = 0; - this.findSelection(_ => count++); - - let selStart = this.selectedTime.start; - let selEnd = this.selectedTime.end; - let first = null; - - this.ui.events.dxChildren('.ev-box', t => { - - if ((selStart <= t.dataset.start && selEnd > t.dataset.start) - || (selStart < t.dataset.end && selEnd > t.dataset.end) - || (selStart >= t.dataset.start && selEnd <= t.dataset.end) - ) { - first = t; - } - }); + addEventBox(id) { let evBox = this.ui.events.appendChild( - El('div', { className: 'ev-box' }, - El('div', { className: 'ev-content' }, content) + El('div', { class: 'ev-box' }, + El('div', { class: 'ev-content' }, ... [...arguments].slice(1) ) ) ); - let index = first ? first.dataset.index : Date.now(); + evBox.dataset.id = id; - evBox.dataset.index = index; - evBox.dataset.start = this.selectedTime.start; - evBox.dataset.end = this.selectedTime.end; - - evBox.style.top = this.slotStart.offsetTop + 'px'; - evBox.style.height = (this.opts.cellSize * count - 1) + 'px'; - - this.resizeGroup(index); + this.resizeEvents(); this.clearSelection(); } + addEvent(id, startTime, endTime, data) { + + this.data.push([ id, startTime, endTime, data ]); + + this.addEventBox(id, + El('span', { class: 'ev-time text-semibold' }, startTime + ' - ' + endTime), + ' - ', + El('span', { class: 'ev-data' }, data) + ); + } + + updateEvent(id, startTime, endTime, data) { + + let index = this.data.findIndex(item => id == item[0]); + + if (index == -1) { + return; + } + + this.data[ index ] = [ id, startTime, endTime, data ]; + + this.resizeEvents(); + } + + deleteEvent(ev) { + + let index = this.data.findIndex(item => ev.id == item[0]); + + if (index == -1) { + return; + } + + this.data.splice(index, 1); + + ev.container.remove(); + + this.resizeEvents(); + + this.trigger('onEventDelete'); + } + hasOverlap(target) { if (this.opts.maxEvents == -1) { return false; } - + let time = target.dataset.time; let found = 0; - this.ui.events.dxChildren('.ev-box', t => { - if (time >= t.dataset.start && time < t.dataset.end ) { + for (const item of this.data) { + + if (time >= item[1] && time < item[2]) { found++; } - }); + } return found >= this.opts.maxEvents; } - resizeGroup(index) { - - let cols = this.ui.events.dxChildren('.ev-box[data-index="'+ index +'"]', true); - - cols = cols.sort((a, b) => { - - if (a.dataset.start < b.dataset.start) { - return -1; - } - else if (a.dataset.start > b.dataset.start) { - return 1; - } - - return 0; - }); - - let unit = 100 / cols.length; - let space = 5 / cols.length; - - cols.forEach((t, i) => { - - t.style.width = (unit - space - 0.5) + '%' - t.style.left = (i * (unit - space) ) + '%'; - }); - } - - deleteEvent(ref) { - - ref.container.remove(); - this.resizeGroup(ref.index); - - this.trigger('eventDeleted'); - } findSelection(callback) { @@ -436,14 +420,25 @@ export class Scheduler { eventClickHandler(t) { - this.trigger('eventClick', + let id = t.dataset.id; + + let index = this.data.findIndex(item => id == item[0]); + + if (index == -1) { + return; + } + + let item = this.data[ index ]; + + this.trigger('onEventClick', this, { - container: t, - content: t.dxChildren('.ev-content'), - index: t.dataset.index, + scheduler: this, + container: t, + content: t.dxChildren('.ev-content'), + id: id, interval: { - start: t.dataset.start, - end: t.dataset.end + start: item[1], + end: item[2] } } ); @@ -457,6 +452,10 @@ export class Scheduler { return this.selectedTime.end; } + getData() { + return this.data; + } + leaveSelection() { this.ui.events.dxOff('mouseover'); @@ -480,16 +479,201 @@ export class Scheduler { } clearSelection() { - + if (!this.slotStart) { return; } - this.findSelection(t => t.classList.remove('selected', 'main-selected')); + //this.findSelection(t => t.classList.remove('selected', 'main-selected')); + this.ui.events.dxChildren('.time-slot', t => t.classList.remove('selected', 'main-selected')); this.resetSelection(); - this.trigger('slotCleared'); + this.trigger('onSlotClear'); + } + + getOverlapIndex(startTime, endTime, id) { + + let index = -1; + + for (let i = 0; i < this.group.length; i++) { + + for (const item of this.group[i]) { + + if (id == item[0]) { + index = i; + continue; + } + + if ((startTime <= item[1] && endTime > item[2]) + || (startTime < item[2] && endTime > item[2]) + || (startTime >= item[1] && endTime <= item[2]) + ) { + return i; + } + } + } + + return index != -1 ? index : this.group.length; + } + + + disable() { + + if (this.enabled) { + this.enabled = false; + this.ui.events.classList.add('scale-disabled'); + } + } + + enable() { + + if (!this.enabled) { + this.enabled = true; + this.ui.events.classList.remove('scale-disabled'); + } + } + + load() { + + if (!this.busy) { + this.busy = true; + this.ui.wrapper.append( + El('div', { class: 'scheduler-modal' }, + El('div', { class: 'sp-loader' }) + ) + ); + } + } + + unload() { + + if (this.busy) { + this.busy = false; + this.ui.wrapper.dxChildren('.scheduler-modal').remove(); + } + } + + sortData() { + + this.data = this.data.sort((a, b) => { + + if (a[1] < b[1]) { + return -1; + } + else if (a[1] > b[1]) { + return 1; + } + + return 0; + }); + } + + update(data) { + + this.data = data; + this.group = []; + + this.sortData(); + + this.data.forEach(item => { + + let index = this.getOverlapIndex(item[1], item[2], null); + + if (!this.group[ index ]) { + this.group[ index ] = []; + } + + this.group[ index ].push(item); + + }, this); + + for (const group of this.group) { + + let unit = 100 / group.length; + let space = 5 / group.length; + let index = 0; + + for (const item of group) { + + let evBox = this.ui.events.appendChild( + El('div', { class: 'ev-box' }, + El('div', { class: 'ev-content' }, + El('span', { class: 'ev-time text-semibold' }, item[1] + ' - ' + item[2]), + ' - ', + El('span', { class: 'ev-data' }, item[3]) + ) + ) + ); + + evBox.dataset.id = item[0]; + + const slotStart = this.ui.events.dxChildren('.time-slot[data-time="'+ item[1] +'"]'); + const slotEnd = this.ui.events.dxChildren('.time-slot[data-time="'+ item[2] +'"]'); + + evBox.style.top = slotStart.offsetTop + 'px'; + evBox.style.left = (index * (unit - space) ) + '%'; + evBox.style.width = (unit - space - 0.5) + '%' + + evBox.style.height = (slotEnd.offsetTop - slotStart.offsetTop - 1) + 'px'; + + index++; + } + } + } + + reset() { + + this.data = []; + this.group = []; + + this.ui.events.dxChildren('.ev-box', t => t.remove()); + } + + resizeEvents() { + + this.group = []; + + this.sortData(); + + this.data.forEach(item => { + + let index = this.getOverlapIndex(item[1], item[2], null); + + if (!this.group[ index ]) { + this.group[ index ] = []; + } + + this.group[ index ].push(item); + + }, this); + + for (const group of this.group) { + + let unit = 100 / group.length; + let space = 5 / group.length; + let index = 0; + + for (const item of group) { + + let evBox = this.ui.events.dxChildren('.ev-box[data-id="'+ item[0] +'"]') + + if (!evBox) { + continue; + } + + let slotStart = this.ui.events.dxChildren('.time-slot[data-time="'+ item[1] +'"]'); + let slotEnd = this.ui.events.dxChildren('.time-slot[data-time="'+ item[2] +'"]'); + + evBox.style.top = slotStart.offsetTop + 'px'; + evBox.style.left = (index * (unit - space) ) + '%'; + evBox.style.width = (unit - space - 0.5) + '%' + + evBox.style.height = (slotEnd.offsetTop - slotStart.offsetTop - 1) + 'px'; + + index++; + } + } } trigger(event) { @@ -505,7 +689,7 @@ export class Scheduler { if (typeof handler === 'function') { handler(... args); } - + this.target.dxTrigger(event, { scheduler: this, args: args }); } } \ No newline at end of file