Skip to content

Instantly share code, notes, and snippets.

@edtoken
Created October 12, 2015 07:13
Show Gist options
  • Save edtoken/2d5803e9f06efb2660e0 to your computer and use it in GitHub Desktop.
Save edtoken/2d5803e9f06efb2660e0 to your computer and use it in GitHub Desktop.
var monthNames = ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'];
/**
* количество дней в месяце
* @param date
* @returns {number}
*/
var getMonthCountDays = function (date) {
var here = new Date(date.getTime());
here.setDate(32);
return 32 - here.getDate();
};
/**
* узнать количество дней между датами
* @param beginingDate
* @param endingDate
* @returns {number}
*/
var getDaysDiff = function (beginingDate, endingDate) {
return Math.floor((endingDate.getTime() - beginingDate.getTime()) / 24 / 60 / 60 / 1000);
};
/**
* добавит количество дней к дате и вернет новую дату
* @param date
* @param days
* @returns {Date}
*/
var addDaysToDate = function (date, days) {
var out = new Date(date.getTime());
out.setDate(out.getDate() + days);
return out;
};
/**
* Одна группа с данными
*/
class DataGroup {
constructor(data) {
this.id = _.uniqueId('group-');
this.attributes = {
start: false, // дата начала
end: false, // дата конца
data: [] // данные массив объектов при сравнении сравнивается первый уровень объекта
};
_.extend(this.attributes, _.clone(data));
this.attributes.id = this.id;
}
/**
* объединит текущую группу с переданной
* @param objectDataGroup
*/
join(objectDataGroup){
var joinGroupJSON = objectDataGroup.toJSON();
//пересчитываю дату начала и конца
var newDateStart = (this.attributes.start.getDateTime().getTime() > joinGroupJSON.start.getDateTime().getTime())
? joinGroupJSON.start
: this.attributes.start;
var newDateEnd = (this.attributes.end.getDateTime().getTime() > joinGroupJSON.end.getDateTime().getTime())
? this.attributes.end
: joinGroupJSON.end;
//тут можно добавить проверку данных, или их объединение
this.attributes.start = newDateStart;
this.attributes.end = newDateEnd
}
toJSON() {
return _.clone(this.attributes);
}
getCloneData() {
return this.attributes.data.slice(0);
}
}
/**
* Отрисовка одной группы
*/
class SlideTableGroup extends Component {
constructor(props) {
super(props);
this.state = {editDates: false, data:props.data.toJSON()};
}
componentWillReciveProps(nextProps) {
this.props = nextProps;
this.setState({editDates: false, data:this.props.data.toJSON()});
}
clickChangeDate(e, data) {
e.preventDefault();
var value = data.value;
var data = this.state.data;
// если это первый элемент у него нельзя смещать первую дату
// если это последние элемент у него нельзя смещать последнюю дату
data.start = (this.props.notFirstItem)? new ObjectDate(value[0]) : this.state.data.start;
data.end = (this.props.notLastItem)? new ObjectDate(value[1]) : this.state.data.end;
console.log('clickChangeDate', data.start, data.end);
this.setState({data:data});
}
/**
* Клик разделить группу
* @param e
*/
clickSplitGroup(e) {
e.preventDefault();
this.props.smartSplit(this.state.data.id, 2);
}
/**
* клик изменить группу
* @param e
*/
clickEnableEditDates(e) {
e.preventDefault();
this.props.onChangeEditDates(this.state.data.id, true);
}
/**
* клик сохранить даты
* @param e
*/
clickSaveEditDates(e) {
e.preventDefault();
this.props.onChangeEditDates(this.state.data.id, false);
var newStart = this.state.data.start;
var newEnd = this.state.data.end;
this.props.updateGroupDatesById(this.state.data.id, newStart, newEnd);
}
/**
* Клик отменить изменения дат
* @param e
*/
clickCancelEditDates(e){
e.preventDefault();
var data = this.state.data;
var olddata = this.props.data.toJSON();
data.start = olddata.start;
data.end = olddata.end;
this.setState({data:data});
this.props.onChangeEditDates(this.state.data.id, false);
}
/**
* клик объединить с правым
* @param e
*/
clickJoinRight(e) {
e.preventDefault();
this.props.joinRightById(this.state.data.id);
}
/**
* клик объединить с левым
* @param e
*/
clickJoinLeft(e) {
e.preventDefault();
this.props.joinLeftById(this.state.data.id);
}
render() {
var groupMonthStringName;
var data = this.state.data;
var dateStart = data.start.getDateTime();
var dateEnd = data.end.getDateTime();
var enableEditDates = this.props.enableEditDates;
var enableChangeDates = this.props.enableChangeDates;
var enableJoinRight = this.props.notLastItem;
var enableJoinLeft = this.props.notFirstItem;
var enableSplit = (dateStart.getTime() !== dateEnd.getTime());
if (dateStart.getMonth() === dateEnd.getMonth()) {
groupMonthStringName = monthNames[dateStart.getMonth()];
groupMonthStringName += '(' + dateStart.getDate() + ' - ' + dateEnd.getDate() + ')';
} else {
groupMonthStringName = monthNames[dateStart.getMonth()]
+ '(' + dateStart.getDate() + ')'
+ ' - ' + monthNames[dateEnd.getMonth()]
+ '(' + dateEnd.getDate() + ')';
}
var dateComponentProps = {
config: {
title: 'Выберите период'
},
value:[data.start.formatServerDateTime(), data.end.formatServerDateTime()],
enabled:this.props.editablePeriod(data.id), // массивы периудов или дат
onChange:this.clickChangeDate.bind(this)
};
var styles = {
display:'table-cell',
//'minWidth':'150px'
};
if(enableEditDates){
styles.border = '1px solid #ddd';
styles.background = '#eee';
styles.padding = '15px';
}
var stylesHeader = {
'borderBottom':'1px dashed #ddd',
borderRight:'1px solid #fff',
'paddingBottom':'7px',
fontSize:'10px',
fontWeight:'bold',
padding:'5px',
textAlign:'center',
color:'#333335',
background:'#cce5f4',
};
var stylesHeaderTitle = {
maxWidth:'120px',
margin:'0 auto',
height:'37px',
lineHeight:'12px',
display:'block',
};
var stylesHeaderActions = {
'paddingLeft':'7px',
'paddingRight':'7px',
margin:'0 0 7px',
};
return (
<div style={styles}>
<div style={stylesHeader}>
<span style={stylesHeaderTitle}>{groupMonthStringName}</span>
<div style={stylesHeaderActions}>
{!enableEditDates && <div>
{enableChangeDates && enableJoinLeft &&
<button onClick={this.clickJoinLeft.bind(this)} className="btn btn-xs btn-link" title="Объединить с левым">&larr;</button>
}
{enableSplit && <button onClick={this.clickSplitGroup.bind(this)} className="btn btn-xs btn-link">разбить</button>}
{enableChangeDates && <button onClick={this.clickEnableEditDates.bind(this)} className="btn btn-xs btn-link"> изменить</button>}
{enableChangeDates && enableJoinRight &&
<button onClick={this.clickJoinRight.bind(this)} className="btn btn-xs btn-link" title="Объединить с правым">&rarr;</button>
}
</div>}
{enableEditDates && <div>
<FieldDate {...dateComponentProps} />
<button onClick={this.clickSaveEditDates.bind(this)} className="btn btn-xs btn-success">Сохранить</button>&nbsp;
<button onClick={this.clickCancelEditDates.bind(this)} className="btn btn-xs btn-danger">Отмена</button>
</div>}
</div>
</div>
</div>);
}
}
/**
* Основная таблица
* рисует внутри себя группы
*/
class SlideTable extends Component {
constructor(props) {
super(props);
// базовая группа
var baseGroup = new DataGroup({
start: new ObjectDate(new Date(2015, 0, 1)),
end: new ObjectDate(new Date(2015, 11, getMonthCountDays(new Date(2015, 11, 1)))),
//data:[{},{},{}]
});
this.state = {
groups: [baseGroup],
editGroupId:''
}
}
/**
* Найти группу по ID
* @param id
* @returns {*}
*/
getGroupById(id) {
return _.find(this.state.groups, group => (group.id === id));
}
/**
* получить группу по индексу
* @param index
*/
getGroupByIndex(index){
return (this.state.groups[index])? this.state.groups[index] : false;
}
/**
* Вернет все группы которые цепляют промежуток между датами
* @param from
* @param to
*/
getGroupsByDateRange(from, to){
var f = from.getDateTime().getTime();
var t = to.getDateTime().getTime();
return _.filter(this.state.groups, function(group){
var s = group.attributes.start.getDateTime().getTime();
var e = group.attributes.end.getDateTime().getTime();
// начало или конец группы находятся в промежутке
if(s >= f && s <= t) return true;
if(e >= f && e <= t) return true;
return false;
});
}
/**
* получить следующую группу от группы по переданному ID
* @param id
*/
getNextGroupById(id){
var index = this.getGroupIndexById(id);
index++;
return this.getGroupByIndex(index);
}
/**
* получить предыдущую группу от группы по переданному id
* @param id
*/
getPrevGroupById(id){
var index = this.getGroupIndexById(id);
index--;
return this.getGroupByIndex(index);
}
/**
* Получить index (положение в массиве) группы по id
* @param id
* @returns {*}
*/
getGroupIndexById(id) {
for (var i = 0; i < this.state.groups.length; i++) {
if (this.state.groups[i].id !== id) continue;
return i;
}
return undefined;
}
/**
* Вернет массив периодов для редактирования (периоды доступных дат)
* @param id
* @returns {Array}
*/
getEditPeriodByGroupId(id){
var prev = this.getPrevGroupById(id);
var next = this.getNextGroupById(id);
var out = [];
var start = (prev)? prev.toJSON().start : this.getGroupById(id).toJSON().start;
var end = (next)? next.toJSON().end : this.getGroupById(id).toJSON().end;
out.push([start.formatServerDateTime(), end.formatServerDateTime()]);
return out;
}
/**
* Проверить идентичность данных двух групп
* @param group1
* @param group2
*/
isEqualGroups(group1, group2) {
return _.isEqual(group1, group2);
}
/**
* удалить группу по ID
* @param id
*/
removeGroupById(id) {
var index = this.getGroupIndexById(id);
if (index !== undefined) {
this.state.groups.splice(index, 1);
this.sort();
}
}
/**
* сортирует переданный массив групп по датам
* @param groups
* @returns {Array}
* @private
*/
_sort(groups){
return _.sortBy(groups, group => (group.attributes.start.getDateTime().getTime()));
}
/**
* пересортировать группы
* сортировка происходит по дате start
*/
sort() {
var groups = this._sort(this.state.groups);
this.setState({groups: groups, editGroupId:''});
}
/**
* вставить группу
* @param group
*/
pushGroup(group) {
var groups = this.state.groups;
groups.push(group);
this.setState({groups: groups, editGroupId:''}, function () {
this.sort();
});
}
/**
* вставить группы
* @param arr
*/
pushGroups(arr) {
var groups = this.state.groups;
groups = groups.concat(arr);
this.setState({groups: groups, editGroupId:''}, function () {
this.sort();
});
}
/**
* Разделить группу на количество кусков
* удалить старую группу вставить сформированные
* @param groupId
*/
splitGroup(groupID, count) {
var group = this.getGroupById(groupID);
var startDate = group.attributes.start.getDateTime();
var endDate = group.attributes.end.getDateTime();
var days = getDaysDiff(startDate, endDate);
var stepValue = days / count;
var groupData = group.getCloneData();
var createGroups = [];
var steps = count;
while (steps) {
var nextEndDate = addDaysToDate(startDate, stepValue);
if (nextEndDate > endDate) {
nextEndDate = endDate;
}
createGroups.push(new DataGroup({start: new ObjectDate(startDate), end: new ObjectDate(nextEndDate), data: groupData}));
startDate = addDaysToDate(nextEndDate, 1);
steps--;
}
this.removeGroupById(groupID);
this.pushGroups(createGroups);
}
/**
* Разделит группу на количество кусков с дополнительной логикой
* @param groupId
* @param count
*/
smartSplit(groupId, count){
if(this.state.groups.length === 1) count = 6;
this.splitGroup(groupId, count);
}
/**
* Объединит группу 1 и группу 2
* @param id1
* @param id2
*/
joinGroupsByIds(id1, id2){
var groups = this.state.groups;
this.getGroupById(id1).join(this.getGroupById(id2));
this.removeGroupById(id2);
this.setState({groups:groups, editGroupId:''}, function(){
this.sort();
});
}
/**
* Объединить группу по ID с правой от неё группой
* если данные идентичны
*/
joinRightById(groupID) {
var nextGroup = this.getNextGroupById(groupID);
this.joinGroupsByIds(nextGroup.id, groupID);
}
/**
* Объединить группу по ID с лувой от неё группой
* если данные идентичны
*/
joinLeftById(groupID) {
var prevGroup = this.getPrevGroupById(groupID);
this.joinGroupsByIds(prevGroup.id, groupID);
}
/**
* Обновит значение дат группы по ID
* при этом если данная группа "заехала" на какие-либо другие, то другие будут сжаты/расширены
* @param groupId
* @param start
* @param end
*/
updateGroupDatesById(groupId, start, end){
console.log('updateGroupDatesById', groupId, start, end);
var group = this.getGroupById(groupId);
var json = group.toJSON();
var s = start.getDateTime();
var st = s.getTime();
var e = end.getDateTime();
var et = e.getTime();
// все группы которые попадают к этот временной промежуток
// отсортированные по времени
var modifyGroups = this._sort(this.getGroupsByDateRange(start, end));
console.log('modifyGroups', modifyGroups);
for(var i=0; i<modifyGroups.length;i++){
var g = modifyGroups[i]; // группа
var main = (g.id == json.id); // это основная изменяемая группа
var gs = g.attributes.start.getDateTime(); // её время начала
var gsTime = gs.getTime(); // её время начала
var ge = g.attributes.end.getDateTime();// её время конца
var geTime = ge.getTime();// её время конца
// самая первая затронутая группа
// добавить json данных групп
//var moveToLeft = (gs > s)
// если это main группа
// то ставим ей требуемые значения
//if(main){
// g.attributes.start = start;
// g.attributes.end = end;
//}
console.log('i', i, g.attributes.start.getDateTime(), g.attributes.end.getDateTime(), '|', start.getDateTime(), end.getDateTime());
}
/**
* обновляем значения групп
*/
var groups = this.state.groups;
this.setState({groups:groups});
// группа переехала левее
//var moveToLeft = start.getDateTime().getTime() < json.start.getDateTime().getTime();
//
//// левая граница осталась на месте
//var stopLeft = start.getDateTime().getTime() === json.start.getDateTime().getTime();
//
//// группа переехала правее
//var moveToRight = end.getDateTime().getTime() > json.end.getDateTime().getTime();
//
//// правая граница осталась на месте
//var stopRight = end.getDateTime().getTime() === json.end.getDateTime().getTime();
///**
// * Есть 2 точки на линии времени
// * возможны 7 видов модицикации
// */
//// если группа никуда не перехала - ниче не делаем
//if(stopLeft && stopRight){
// return;
//}
//
//// группа была целиком расширена
//// нужно изменить группы которые стоят до и после
//if(moveToRight && moveToLeft){
//
// return;
//}
//
//// группа была целиком сжата
//// нужно изменить группы которые стоят до и после
//if(!moveToRight && !moveToLeft && !stopRight && !stopLeft){
//
// return;
//}
//
//// правая граница группы переехала вперед
//// нужно изменить группы которые стоят после
//if(stopLeft && moveToRight){
//
// return;
//}
//
//// правая граница переехала назад
//// нужно изменить группы которые стоят после
//if(stopLeft && !moveToRight && !stopRight){
//
// return;
//}
//
//// левая граница перехала назад
//// нужно изменить группы которые стоят до
//if(stopRight && moveToLeft){
//
// return;
//}
//
//// левая граница перехала вперед
//// нужно изменить группы которые стоят до
//if(stopRight && !moveToLeft && !stopLeft){
//
// return;
//}
}
/**
* открыть группу на редактирование дат
* @param id
*/
onChangeEditGroupDatesById(id, value){
this.setState({editGroupId:(value)? id : false});
}
render() {
var groups = this.state.groups;
var enableChangeDates = (groups.length > 1);
var editGroupId = this.state.editGroupId;
var renderedGroups = [];
for (var i=0; i<groups.length;i++) {
var notFirstItem = (i > 0);
var enableEdit = (editGroupId == groups[i].id);
var notLastItem = (i < groups.length - 1);
var key = i + groups[i].id + groups[i].attributes.start.getDateTime().getTime() + groups[i].attributes.end.getDateTime().getTime();
renderedGroups.push(
<SlideTableGroup
key={key}
data={groups[i]}
enableEditDates={enableEdit}
editablePeriod={this.getEditPeriodByGroupId.bind(this)}
enableChangeDates={enableChangeDates}
onChangeEditDates={this.onChangeEditGroupDatesById.bind(this)}
updateGroupDatesById={this.updateGroupDatesById.bind(this)}
notFirstItem={notFirstItem}
notLastItem={notLastItem}
joinRightById={this.joinRightById.bind(this)}
joinLeftById={this.joinLeftById.bind(this)}
smartSplit={this.smartSplit.bind(this)}/>
);
}
var styles = {
border:'1px solid #ddd',
padding:'15px',
'display':'flex',
'flexWrap': 'wrap',
'justifyContent':'left'
};
return (<div style={styles}>
<div className="clearfix">
{renderedGroups}
</div>
</div>);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment