|
var locale = navigator.language; |
|
console.log(navigator.languages, locale, locale.length); |
|
if (locale && locale !== undefined) locale = locale.match(/^\w{2}/)[0]; |
|
moment.locale(locale); |
|
|
|
$(function() { |
|
document.getElementById("imgLoad").style.display = "none"; |
|
document.getElementById("main-div").style.display = "block"; |
|
timelineVis(); |
|
}); |
|
|
|
function timelineVis() { |
|
var dataArray = [ |
|
["Statut", "Pause", "Taĉhe", "Catégorie", "Ville", "Prévisionnel heures", "Réel", |
|
"Prévisionnel jours", "Jours restant", "Progression", "Date début", "Date fin", "Prévisionnel salariés" |
|
], |
|
["en cours", "BREAK", "0402 - VILLAS", "#cat001", "Mulhouse, France", 1613, 1450, 539, 5, 0.95, |
|
"2016-05-22", "2018-07-11", 2.06794871794872 |
|
], |
|
["en cours", "", "0227 - LA CIE", "#cat002", "Strasbourg, France", 10200, 7040, 473, 81, 0.7, |
|
"2016-12-04", "2018-10-25", 4.8433048433048445 |
|
], |
|
["en cours", "", "0285 - CONSTRUCTION D'UN IMMEUBLE DE 56 LOGEMENTS", "#cat003", "Amiens, France", 2423, |
|
1840, 262, 56, 0.8, "2017-09-03", "2018-09-20", 1.1094322344322343 |
|
], |
|
["en cours", "", "0267 D - TRANCHE 4 / BAT 18", "#cat004", "Lyon, France", 1060, 624, 127, 43, |
|
0.5, "2018-02-27", "2018-09-03", 1.5795020870602265 |
|
], |
|
["en cours", "", "0254 - CREATION D'UNE PISCINE COUVERTE", "#cat005", "Paris, France", 709, 858, 107, 31, |
|
0.6, "2018-03-11", "2018-08-16", 1.171712158808933 |
|
], |
|
["en cours", "", "0200 - REAMENAGEMENT", "#cat006", "Mulhouse, France", 1500, 624, 139, 81, 0.35, |
|
"2018-04-08", "2018-10-25", 1.5432098765432098 |
|
], |
|
["en cours", "", "4500 - MUR COUPE FEU", "#cat007", "Amiens, France", 295, 351, 30, 1, 0.95, |
|
"2018-05-27", "2018-07-05", 1.8918589743589762 |
|
], |
|
["en cours", "", "0300 - COPRO VILLE", "#cat005", "Metz, France", 2365, 815, 153, 146, 0.15, |
|
"2018-06-17", "2019-01-24", 1.7652353354408148 |
|
], |
|
["prévu", "", "0400 - CAFETERIA", "#cat007", "Paris, France", 400, "", 19, "", "", |
|
"2018-07-08", "2018-08-02", 2.699055330634278 |
|
], |
|
["prévu", "", "0299 - MAGS", "#cat007", "Amiens, France", 800, "", 90, "", "", |
|
"2018-09-02", "2019-01-10", 1.1396011396011396 |
|
], |
|
["prévu", "", "5210 - TRUC BIDULE", "#cat007", "Amiens, France", 500, "", 29, "", "", |
|
"2018-09-23", "2018-11-01", 2.2104332449160036 |
|
], |
|
["prévu", "", "0265 - MJC", "#cat008", "Mulhouse, France", 663, "", 62, "", "", |
|
"2018-11-25", "2019-02-21", 1.3690239867659222 |
|
], |
|
["terminé", "", "0282 - ESPACE LOTS 10 A 15", "#cat001", "Paris, France", 5865, 5458, 523, "", 1, |
|
"2016-06-05", "2016-12-03", "" |
|
], |
|
["terminé", "", "0126 - PÔLE CULTUREL", "#cat009", "Paris, France", 1654, 1536, 51, "", 1, |
|
"2018-04-03", "2018-06-18", "" |
|
] |
|
]; |
|
|
|
|
|
var array = dataArray.slice(1, dataArray.length); |
|
$("#puce-showHideAllGroups").removeClass("puce-triangle-right").addClass("puce-triangle-down"); |
|
|
|
var values = array.filter(function(el) { |
|
return el[2] !== null |
|
}); |
|
|
|
var container = document.getElementById("timeline-vis"); |
|
var types = ["box", "point", "range", "background"]; |
|
var categories = values.map(function(el) { |
|
return el[3] |
|
}).filter(function(elem, i, array) { |
|
return array.indexOf(elem) === i |
|
}); |
|
|
|
var groups = new vis.DataSet, |
|
items = new vis.DataSet, |
|
categories_maxSize = categories.length <= 1000 ? 1000 : 10000; |
|
var groupId = categories_maxSize + 1, // 1000 |
|
globalDelta = 0; |
|
for (var g = 0, lg = categories.length; g < lg; g++) { |
|
var nested = [], |
|
count = 0, |
|
groupDelta = 0, |
|
itemContent, tooltip, reel, className, visibleFrameTemplate, delta, classDelta, progress; |
|
for (var k in values) { |
|
var start = moment(values[k][10]).format("DD/MM/YYYY"), |
|
end = moment(values[k][11]).format("DD/MM/YYYY"); |
|
(values[k][6]) ? reel = parseInt(values[k][6], 10): reel = 0; |
|
|
|
progress = getStatut(moment(values[k][10]), moment(values[k][11]), values[k][9], values[k][1]).progress; |
|
className = getClassName(getStatut(moment(values[k][10]), moment(values[k][11]), values[k][9], values[k][1]).statut, values[k][1]).className; |
|
delta = getDelta(values[k][5], values[k][6]); |
|
|
|
if (progress) { |
|
progress = parseInt(progress * 100, 10) + "%"; |
|
visibleFrameTemplate = '<div id="' + groupId + |
|
'" class="progress-wrapper"><div class="progress"><label class="progress-label">' + progress + |
|
'<label></div></div>'; |
|
} else { |
|
visibleFrameTemplate = 0; |
|
} |
|
|
|
(values[k][12] && values[k][12] !== undefined) ? values[k][12] = values[k][12]: values[k][12] = 0; |
|
|
|
itemContent = parseInt(values[k][12], 10) + ' employees' + ' (' + |
|
getClassName(getStatut(moment(values[k][10]), moment(values[k][11]), values[k][9], values[k][1]).statut, values[k][1]).statutContent + ')'; |
|
|
|
if (categories[g] == values[k][3]) { |
|
delta >= 0 ? classDelta = "spanDeltaGreen" : classDelta = "spanDeltaRed"; |
|
items.add({ |
|
id: groupId, |
|
group: groupId, |
|
content: itemContent, |
|
statutContent: getClassName(getStatut(moment(values[k][10]), moment(values[k][11]), values[k][9], values[k][1]).statut, values[k][1]).statutContent, |
|
value: getStatut(moment(values[k][10]), moment(values[k][11]), values[k][9], values[k][1]).progress, |
|
progression: progress, |
|
start: moment(values[k][10]).toDate(), |
|
end: moment(values[k][11]).endOf("day").toDate(), |
|
className: className, |
|
title: getTooltip(getStatut(moment(values[k][10]), moment(values[k][11]), values[k][9], values[k][1]).statut, |
|
delta, classDelta, values[k][2], start, end, values[k][5], reel), |
|
classDelta: classDelta, |
|
delta: delta, |
|
visibleFrameTemplate: visibleFrameTemplate, |
|
"statut": getStatut(moment(values[k][10]), moment(values[k][11]), values[k][9], values[k][1]).statut, |
|
"task": values[k][2], |
|
"break": values[k][1], |
|
"category": values[k][3], |
|
"location": values[k][4], |
|
"estimated": values[k][5], |
|
"worked": (values[k][6]) ? parseInt(values[k][6], 10) : 0, |
|
"estimated days": values[k][7], |
|
"remaining days": values[k][8], |
|
"employees": values[k][12] |
|
}); |
|
|
|
groups.add({ |
|
id: groupId, |
|
content: values[k][2] + ' <span class="' + classDelta + '">Δ ' + parseInt(delta, 10) + |
|
"</span>", |
|
groupDates: getGroupDates(values[k][10], values[k][11]), |
|
delta: delta, |
|
order: groupId |
|
}); |
|
nested.push(groupId); |
|
count++; |
|
groupDelta += parseInt(delta, 10); |
|
groupId += 1; |
|
} |
|
} // (var k in values) |
|
groupDelta > 0 ? classDelta = "spanDeltaGreen" : classDelta = "spanDeltaRed"; |
|
groups.add({ |
|
id: g + 1, |
|
category: categories[g], |
|
title: getCategory(categories[g], count, groupDelta), |
|
content: getContentCategorie(categories[g], count, classDelta, groupDelta), |
|
nestedGroups: nested, |
|
showNested: true, |
|
order: g + 1 |
|
}); |
|
globalDelta += groupDelta |
|
} // (var g = 0, lg = categories.length; g < lg; g++) |
|
globalDelta > 0 ? classDelta = "spanDeltaGreen" : classDelta = "spanDeltaRed"; |
|
var spanGlobalDelta = document.getElementById("spanGlobalDelta"); |
|
spanGlobalDelta.innerHTML = "Δ " + Math.round(globalDelta); |
|
spanGlobalDelta.className += classDelta; |
|
var timelineHeight = Math.round($(window).height() * .85) + "px"; |
|
var itemsOnUpdate = []; |
|
var options = { |
|
editable: true, |
|
groupEditable: true, |
|
itemsAlwaysDraggable: true, |
|
verticalScroll: true, |
|
orientation: { |
|
axis: "both", |
|
item: "top" |
|
}, |
|
groupOrder: "id", |
|
width: "100%", |
|
height: timelineHeight, |
|
stack: false, |
|
onMoving: function(item, callback) { |
|
var group = groups.get(item.group); |
|
console.log(item, item.id === group.id); |
|
if (item.content != null && item.id === group.id) { |
|
callback(item); // send back adjusted item |
|
} else { |
|
callback(null); // cancel updating the item |
|
} |
|
}, |
|
onMove: function(item, callback) { |
|
promiseUpdateItem(item).then(value => { |
|
if (value) { |
|
item["statut"] = getStatut( |
|
moment(item.start), |
|
moment(item.end), |
|
item["Progression"], |
|
item["break"] |
|
).statut; |
|
// console.log(item["statut"], item["break"]); |
|
(item["statut"] === "planned" && item["break"] === "BREAK") ? item["break"] = "": item["break"] = item["break"]; |
|
item["title"] = changeTooltip(item); |
|
item["content"] = changeContent(item); |
|
item["className"] = getClassName(item["statut"], item["break"]).className; |
|
item["progression"] = (item["statut"] !== "planned") ? parseFloat(item["value"] * 100).toFixed(0) + "%" : "0%"; |
|
item.visibleFrameTemplate = '<div id="' + item.id + |
|
'" class="progress-wrapper"><div class="progress"><label class="progress-label">' + item["progression"] + |
|
'<label></div></div>'; |
|
groups.update({ |
|
id: item.id, |
|
content: item["task"] + ' <span class="' + item["classDelta"] + '">Δ ' + parseInt(item["delta"], 10) + "</span>", |
|
groupDates: getGroupDates(item.start, item.end), |
|
delta: item["delta"], |
|
order: item.id |
|
}); |
|
callback(item); // send back item as confirmation (can be changed) |
|
timeline.redraw(); |
|
// https://github.com/almende/vis/issues/577 |
|
} else { |
|
callback(null); // cancel editing item |
|
} |
|
}, function(err) { |
|
console.error('Erreur !'); |
|
alert(err); |
|
}); |
|
}, |
|
onUpdate: function(item, callback) { |
|
prettyPromptItemUpdate('Modify item on update', 'SweatAlert!', item, groups, timeline, callback); |
|
}, |
|
onRemove: function(item, callback) { |
|
prettyConfirmRemove('Remove item', 'Do you really want to remove item: ', item, items, groups, timeline, callback) |
|
}, |
|
onAdd: function(item, callback) { |
|
var group = groups.get(item.group); |
|
// console.log(getCategoriesIds()); |
|
if (item.content != null && getCategoriesIds().indexOf(group.id) !== -1) { |
|
var index = getItemsIds()[getItemsIds().length - 1]; |
|
var nestedGroups = group.nestedGroups, |
|
delta = 0, |
|
classDelta; |
|
nestedGroups.push(index + 1); |
|
// Values for New item |
|
item["value"] = 0; |
|
item["break"] = ""; |
|
item["progression"] = "0%"; |
|
item["type"] = "range"; |
|
item["end"] = moment(item.start).add(3, 'months'); |
|
item["statutContent"] = getClassName(getStatut(moment(item.start), moment(item.end), 0, "").statut, "").statutContent; |
|
item["content"] = "New item"; |
|
item["id"] = index + 1; |
|
item["group"] = index + 1; |
|
item["task"] = "New item"; |
|
item["category"] = "#cat00" + group.id; |
|
item["location"] = ""; |
|
item["estimated"] = 0; |
|
item["worked"] = 0; |
|
item["delta"] = getDelta(item["estimated"], item["worked"]); |
|
item["estimated days"] = ""; |
|
item["remaining days"] = ""; |
|
item["employees"] = 0; |
|
item["className"] = getClassName(getStatut(moment(item.start), moment(item.end), 0, "").statut, "").className; |
|
item["delta"] >= 0 ? classDelta = "spanDeltaGreen" : classDelta = "spanDeltaRed"; |
|
item["title"] = getTooltip(getStatut(moment(item.start), moment(item.end), item.value, item["break"]).statut, item.delta, classDelta, |
|
item["task"], moment(item.start).format("DD/MM/YYYY"), moment(item.end).format("DD/MM/YYYY"), item["estimated"], item["worked"]); |
|
// add item group |
|
groups.add({ |
|
id: index + 1, |
|
groupDates: getGroupDates(item.start, item.end), |
|
nestedInGroup: group.id, |
|
showNested: true, |
|
order: index + 1, |
|
content: 'New item' + ' <span class="' + classDelta + '">Δ ' + delta + "</span>", |
|
delta: 0 |
|
}); |
|
// update group category |
|
groups.forEach(function(group) { |
|
if (nestedGroups.indexOf(group.id) !== -1) delta += group.delta; |
|
}); |
|
delta >= 0 ? classDelta = "spanDeltaGreen" : classDelta = "spanDeltaRed"; |
|
// console.log(delta); |
|
groups.update({ |
|
id: group.id, |
|
category: group.category, |
|
title: getCategory(group.category, nestedGroups.length, delta), |
|
content: getContentCategorie(group.category, nestedGroups.length, classDelta, delta), |
|
nestedGroups: nestedGroups, |
|
showNested: true, |
|
order: parseInt(group.id, 10) |
|
}); |
|
callback(item); // send back adjusted item |
|
document.getElementById("spanSumEvents").innerHTML = items.length; |
|
timeline.redraw(); |
|
} else { |
|
callback(null); // cancel updating the item |
|
} |
|
}, |
|
visibleFrameTemplate: function(item) { |
|
if (item.visibleFrameTemplate) { |
|
$($("#" + item.id).children()).css("width", item.progression); |
|
if (item["statut"] === "done") { |
|
$($("#" + item.id).children()).css({ |
|
color: "white", |
|
background: "#B40404" |
|
}); |
|
} else if (item["statut"] === "in progress") { |
|
$($("#" + item.id).children()).css({ |
|
color: "#333", |
|
background: "#63ed63" |
|
}); |
|
} else if (item["statut"] === "planned") { |
|
// console.log(item["statut"]); |
|
$($("#" + item.id).children()).css({ |
|
color: "#333", |
|
background: "#FE9A2E" |
|
}); |
|
} |
|
return item.visibleFrameTemplate; |
|
} |
|
}, |
|
tooltip: { |
|
overflowMethod: "cap" |
|
} |
|
}; |
|
|
|
showHideAllGroups = function(button) { |
|
var currentCategories = new vis.DataView(groups, { filter: function(item) { return (item.id < categories_maxSize); } }); |
|
if (button.dataset.value == "allGroups-hidden") { |
|
groups.forEach(function(group) { |
|
groups.update({ |
|
id: group.id, |
|
visible: true, |
|
showNested: true |
|
}) |
|
}); |
|
button.dataset.value = "allGroups-visible"; |
|
$("#puce-showHideAllGroups").removeClass("puce-triangle-right").addClass("puce-triangle-down"); |
|
changeTitleAttr($("#puce-showHideAllGroups").hasClass("puce-triangle-down")); |
|
} else { |
|
groups.forEach(function(group) { |
|
if (group["id"] <= currentCategories.length) groups.update({ |
|
id: group.id, |
|
// visible: true, |
|
showNested: false |
|
}); |
|
else groups.update({ |
|
id: group.id, |
|
visible: false |
|
}); |
|
}); |
|
button.dataset.value = "allGroups-hidden"; |
|
$("#puce-showHideAllGroups").removeClass("puce-triangle-down").addClass("puce-triangle-right"); |
|
changeTitleAttr($("#puce-showHideAllGroups").hasClass("puce-triangle-down")); |
|
} |
|
}; |
|
|
|
document.getElementById('allGroups-div').addEventListener('click', function() { |
|
showHideAllGroups(this); |
|
}, false); |
|
|
|
var timeline = new vis.Timeline(container); |
|
timeline.setOptions(options); |
|
timeline.setGroups(groups); |
|
timeline.setItems(items); |
|
|
|
items.on('*', function(event, properties) { |
|
console.log(event, properties); |
|
}); |
|
|
|
// to set tooltip position if necessary |
|
timeline.on('mouseOver', function(prop, timeZone) { |
|
if (prop.what == "item") { |
|
// console.log(prop.what, prop, $('.vis-tooltip')); |
|
$('.vis-tooltip').offset({ |
|
left : prop.pageX + 50, // to adapt to its needs |
|
// top : prop.pageY |
|
}); |
|
} |
|
}); |
|
|
|
function getCategoriesIds() { |
|
var targetGroupsIds = []; |
|
groups.forEach(function(group) { |
|
if (group.id <= categories_maxSize) targetGroupsIds.push(group.id); |
|
}); |
|
return targetGroupsIds; |
|
} |
|
|
|
function getItemsIds() { |
|
var itemIds = []; |
|
items.forEach(function(item) { |
|
if (item.id > categories_maxSize) itemIds.push(item.id); |
|
}); |
|
return itemIds; |
|
} |
|
|
|
document.getElementById('spanSumCat').innerHTML = categories.length; |
|
document.getElementById("spanSumEvents").innerHTML = items.length; |
|
|
|
var labelCurrentTime = document.createElement("label"); |
|
labelCurrentTime.setAttribute("id", "labelCurrentTime"); |
|
labelCurrentTime.innerHTML = moment(timeline.getCurrentTime()).format("dddd DD MMM YYYY"); |
|
timeline.currentTime.bar.appendChild(labelCurrentTime); |
|
|
|
timeline.on("click", function(properties) { |
|
var target = properties.event.target; |
|
var event = properties.event; |
|
var group = groups.get(properties.group); |
|
var itemDates = group.groupDates; |
|
// console.log(properties.what, items, groups) |
|
if (properties.what === "group-label" && itemDates) { |
|
timeline.fit(); |
|
if (itemDates && itemDates.length) { |
|
var itemDateStart = itemDates[0], |
|
itemDateEnd = itemDates[1]; |
|
if (itemDateStart && itemDateEnd) timeline.setWindow({ |
|
start: itemDateStart.valueOf() - 864E4, |
|
end: itemDateEnd.valueOf() + 864E4 |
|
}) |
|
} |
|
event.stopPropagation(); |
|
} else if (properties.what === "group-label" && !itemDates && target.tagName === "I") { |
|
prettyPromptCat("Edit category", "SweatAlert!", target, group, groups, timeline, categories_maxSize) |
|
event.stopPropagation(); |
|
} else if (properties.what === "item") { |
|
var item = items.get(properties.item); |
|
event.stopPropagation(); |
|
} |
|
}); |
|
|
|
document.getElementById("moveToDate").addEventListener('click', function() { |
|
var dateToMove = document.getElementById("inputDateToMove").value, |
|
itemCustomTime, endDateToMove; |
|
endDateToMove = moment(moment(dateToMove).valueOf() + 5184E5).format("YYYY-MM-DD"); |
|
if (items.get(dateToMove)) itemCustomTime = items.get(dateToMove).id; |
|
if (dateToMove && !items.get(dateToMove)) { |
|
timeline.addCustomTime(moment(dateToMove).toISOString(), { |
|
id: dateToMove |
|
}); |
|
items.add({ |
|
id: dateToMove, |
|
content: moment(dateToMove).format("DD MMM"), |
|
start: moment(dateToMove).toISOString(), |
|
end: moment(moment(endDateToMove).endOf("day")).toISOString(), |
|
type: "background" |
|
}); |
|
timeline.setWindow(moment(dateToMove).toISOString(), moment(moment(endDateToMove).endOf("day")).toISOString(), { |
|
animation: { |
|
duration: 1000, |
|
easingFunction: "linear" |
|
} |
|
}); |
|
showAllGroupsWeek(dateToMove, endDateToMove); |
|
} else if (dateToMove && dateToMove === itemCustomTime) { |
|
timeline.setWindow(moment(dateToMove).toISOString(), moment(moment(endDateToMove).endOf("day")).toISOString(), { |
|
animation: { |
|
duration: 1000, |
|
easingFunction: "linear" |
|
} |
|
}); |
|
showAllGroupsWeek(dateToMove, endDateToMove); |
|
} |
|
}, false); |
|
|
|
document.getElementById("globalView").addEventListener('click', function() { |
|
var groupsVisible = [], |
|
itemsVisible = []; |
|
groups.forEach(function(group) { |
|
if (group.id >= categories.length && group.visible === true) groupsVisible.push(group.id) |
|
}); |
|
items.forEach(function(item) { |
|
if (groupsVisible.indexOf(item.group) >= 0) itemsVisible.push(item.id) |
|
}); |
|
timeline.focus(itemsVisible) |
|
}, false); |
|
|
|
function showAllGroups(dateToMove, endDateToMove) { |
|
console.log(groups); |
|
groups.forEach(function(group) { |
|
if (group.id >= categories.length && group.groupDates && group.groupDates.length) { |
|
var startDateGroup = group.groupDates[0], |
|
endDateGroup = group.groupDates[1]; |
|
if (!(endDateGroup <= |
|
moment(dateToMove) || startDateGroup >= moment(endDateToMove))) groups.update({ |
|
id: group.id, |
|
visible: true, |
|
showNested: true |
|
}); |
|
else groups.update({ |
|
id: group.id, |
|
visible: false |
|
}) |
|
} |
|
}) |
|
} |
|
|
|
function showAllGroupsWeek(dateToMove, endDateToMove) { |
|
groups.forEach(function(group) { |
|
//console.log(group.groupDates); |
|
if (group.id >= categories.length && group.groupDates && (group.groupDates).length) { |
|
var startDateGroup = group.groupDates[0], |
|
endDateGroup = group.groupDates[1]; |
|
//console.log(startDateGroup, endDateGroup, moment(dateToMove)); |
|
if (!(endDateGroup <= moment(dateToMove) || startDateGroup >= moment(endDateToMove))) { |
|
groups.update({ id: group.nestedInGroup, visible: true, showNested: true }); |
|
groups.update({ |
|
id: group.id, |
|
visible: true |
|
}); |
|
} else { |
|
groups.update({ id: group.id, visible: false }); |
|
} |
|
} |
|
}); |
|
} |
|
|
|
function move(percentage) { |
|
var range = timeline.getWindow(); |
|
var interval = range.end - range.start; |
|
timeline.setWindow({ |
|
start: range.start.valueOf() - interval * percentage, |
|
end: range.end.valueOf() - interval * percentage |
|
}) |
|
} |
|
document.getElementById("zoomIn").onclick = function() { |
|
timeline.zoomIn(.5); |
|
}; |
|
document.getElementById("zoomOut").onclick = function() { |
|
timeline.zoomOut(.5); |
|
}; |
|
document.getElementById("moveLeft").onclick = function() { |
|
move(.5); |
|
}; |
|
document.getElementById("moveRight").onclick = function() { |
|
move(-.5); |
|
} |
|
|
|
document.getElementById('exportCSV-btn').addEventListener('click', function(e) { |
|
// Get data updated |
|
var datavis = (items.get()).map((obj) => { |
|
var newObj = {}; |
|
newObj["Statut"] = obj["statut"]; |
|
newObj["Break"] = obj["break"]; |
|
newObj["Task"] = obj["task"]; |
|
newObj["Category"] = obj["category"]; |
|
newObj["Location"] = obj["location"]; |
|
newObj["Estimated hours"] = obj["estimated"]; |
|
newObj["Worked hours"] = obj["worked"]; |
|
newObj["Estimated days"] = ""; // obj["estimated days"]; |
|
newObj["Remaining days"] = ""; // obj["remaining days"]; |
|
newObj["Progress"] = obj["progression"]; |
|
newObj["Start"] = moment(new Date(obj["start"])).format("YYYY-MM-DD"); |
|
newObj["End"] = moment(new Date(obj["end"])).format("YYYY-MM-DD"); |
|
newObj["Employees"] = (isNaN(obj["employees"])) ? obj["employees"] : Math.round(parseFloat(obj["employees"]) * 100) / 100; |
|
return newObj; |
|
}); |
|
var delimiter = Papa.parse(Papa.unparse(datavis)).meta.delimiter; |
|
var dataExport = d3.dsvFormat(delimiter).parseRows(Papa.unparse(datavis), (d) => d); |
|
console.log(dataExport); |
|
exportCSV(dataExport, "planning-export") |
|
}); |
|
|
|
document.getElementById('groupadd-btn').addEventListener('click', function(e) { |
|
var title = (document.getElementById('groupadd-input').value) ? (document.getElementById('groupadd-input').value) : "New category"; |
|
console.log(title); |
|
var currentCategories = new vis.DataView(groups, { filter: function(item) { return (item.id < categories_maxSize); } }); |
|
console.log(currentCategories); |
|
// update group item |
|
groups.add({ |
|
id: d3.max(currentCategories.getIds()) + 1, |
|
category: title, |
|
title: getCategory(title, 0, 0), |
|
content: getContentCategorie(title, 0, classDelta, 0), |
|
nestedGroups: [], |
|
showNested: true, |
|
order: d3.max(currentCategories.getIds()) + 1 |
|
}); |
|
document.getElementById('spanSumCat').innerHTML = currentCategories.length; |
|
timeline.redraw(); |
|
}); |
|
|
|
$(function() { |
|
// redraw to consider the visibleFrameTemplate option after load to update the timeline but this needs to be reviewed. |
|
timeline.redraw(); |
|
changeTitleAttr($("#puce-showHideAllGroups").hasClass("puce-triangle-down")); |
|
}); |
|
|
|
} // END OF TIMELINE |
|
|
|
/* SweetAlert */ |
|
function promiseUpdateItem(item) { |
|
return new Promise((resolve, reject) => { |
|
if (item) { |
|
resolve("ok"); |
|
} else { |
|
reject("error"); |
|
} |
|
}); |
|
} |
|
|
|
function prettyPromptCat(title, text, target, group, groups, timeline, categories_maxSize) { |
|
console.log(group, groups); |
|
swal(title, text, { |
|
buttons: { |
|
cancel: "Cancel", |
|
catch: { |
|
text: "Change title", |
|
value: "title", |
|
}, |
|
defeat: { |
|
text: "Remove category", |
|
value: "remove", |
|
}, |
|
}, |
|
}) |
|
.then((value) => { |
|
switch (value) { |
|
case "remove": |
|
swal("Remove category", 'Do you really want to remove this category and all items it contains?', { |
|
buttons: ["Cancel", "Confirm"] |
|
} |
|
).then(value => { |
|
var nestedGroups = group.nestedGroups; |
|
var globalDelta = 0, |
|
classDelta; |
|
if (value) { |
|
if (nestedGroups && nestedGroups !== undefined) { |
|
console.log(nestedGroups); |
|
nestedGroups.forEach(function(groupId) { |
|
groups.remove(groupId); |
|
}); |
|
groups.remove(group); |
|
var currentCategories = new vis.DataView(groups, { filter: function(item) { return (item.id < categories_maxSize); } }); |
|
var currentItems = new vis.DataView(groups, { filter: function(item) { return (item.id > categories_maxSize); } }); |
|
groups.forEach(function(group) { |
|
if (group.delta) globalDelta += group.delta; |
|
}); |
|
console.log(globalDelta); |
|
document.getElementById('spanSumCat').innerHTML = currentCategories.length; |
|
document.getElementById("spanSumEvents").innerHTML = currentItems.length; |
|
globalDelta > 0 ? classDelta = "spanDeltaGreen" : classDelta = "spanDeltaRed"; |
|
var spanGlobalDelta = document.getElementById("spanGlobalDelta"); |
|
spanGlobalDelta.innerHTML = "Δ " + Math.round(globalDelta); |
|
spanGlobalDelta.className = classDelta; |
|
timeline.redraw(); |
|
} |
|
} |
|
}); |
|
break; |
|
case "title": |
|
swal({ |
|
title: "Change title", |
|
text: '', |
|
content: { |
|
element: "input", |
|
attributes: { |
|
placeholder: "Enter new title...", |
|
type: "text", |
|
}, |
|
}, |
|
// icon: "success" |
|
}).then(value => { |
|
var nestedGroups = group.nestedGroups; |
|
var delta = 0, |
|
classDelta; |
|
groups.forEach(function(group) { |
|
if (nestedGroups.indexOf(group.id) !== -1) delta += group.delta; |
|
}); |
|
delta >= 0 ? classDelta = "spanDeltaGreen" : classDelta = "spanDeltaRed"; |
|
if (value) { |
|
groups.update({ |
|
id: group.id, |
|
category: value, |
|
title: getCategory(value, group.nestedGroups.length, delta), |
|
content: getContentCategorie(value, group.nestedGroups.length, classDelta, delta), |
|
nestedGroups: group.nestedGroups, |
|
showNested: true, |
|
order: parseInt(group.id, 10) |
|
}); |
|
} |
|
}); |
|
break; |
|
default: |
|
; |
|
} |
|
}); |
|
} |
|
|
|
function prettyConfirmRemove(title, text, item, items, groups, timeline, callback) { |
|
swal({ |
|
title: title, |
|
text: text + item.task + " ?", |
|
}).then(value => { |
|
console.log(value); |
|
if (value && item.content != null) { |
|
var group = groups.get(item.group), |
|
groupParent |
|
delta = 0; |
|
if (group.nestedInGroup !== undefined) groupParent = groups.get(group.nestedInGroup); |
|
if (groupParent !== undefined) { |
|
var nestedGroups = groupParent.nestedGroups; |
|
nestedGroups.splice(nestedGroups.indexOf(group.id), 1); |
|
groups.remove(group.id); |
|
groups.forEach(function(group) { |
|
if (nestedGroups.indexOf(group.id) !== -1) delta += group.delta; |
|
}); |
|
delta >= 0 ? classDelta = "spanDeltaGreen" : classDelta = "spanDeltaRed"; |
|
groups.update({ |
|
id: groupParent.id, |
|
category: groupParent.category, |
|
title: getCategory(groupParent.category, nestedGroups.length, delta), |
|
content: getContentCategorie(groupParent.category, nestedGroups.length, classDelta, delta), |
|
nestedGroups: nestedGroups, |
|
showNested: true, |
|
order: parseInt(groupParent.id, 10) |
|
}); |
|
} else { |
|
groups.remove(group.id); |
|
} |
|
callback(item); // send back adjusted item |
|
document.getElementById("spanSumEvents").innerHTML = items.length; |
|
timeline.redraw(); |
|
} else { |
|
callback(null); // cancel updating the item |
|
} |
|
}); |
|
} |
|
|
|
function prettyPromptItemUpdate(title, text, item, groups, timeline, callback) { |
|
var html = htmlForSweetAlert(item); |
|
swal({ |
|
title: title, |
|
text: text, |
|
content: html, |
|
// icon: "success" |
|
}).then(value => { |
|
if (value) { |
|
var delta = 0, |
|
globalDelta = 0, |
|
classDelta, category; |
|
item["task"] = document.getElementById('inputTitle').value; |
|
item["estimated"] = document.getElementById('inputPrevisionnel').value; |
|
item["worked"] = document.getElementById('inputReel').value; |
|
item["delta"] = getDelta(item["estimated"], item["worked"]); |
|
item["delta"] >= 0 ? classDelta = "spanDeltaGreen" : classDelta = "spanDeltaRed"; |
|
item["break"] = document.getElementById('selectPause').value; |
|
item["statut"] = getStatut( |
|
moment(item.start), |
|
moment(item.end), |
|
document.getElementById('inputProgress').value, |
|
item["break"] |
|
).statut; |
|
item["value"] = (item["statut"] !== "planned") ? document.getElementById('inputProgress').value : 0; |
|
item["employees"] = document.getElementById('inputSalaries').value; |
|
item["title"] = changeTooltip(item); |
|
item["statutContent"] = getClassName(getStatut(moment(item.start), moment(item.end), 0, "").statut, "").statutContent; |
|
item["content"] = changeContent(item); |
|
item["className"] = getClassName(item["statut"], item["break"]).className; |
|
item["progression"] = (item["statut"] !== "planned") ? parseFloat(document.getElementById('inputProgress').value * 100).toFixed(0) + "%" : "0%"; |
|
item["title"] = getTooltip(getStatut(moment(item.start), moment(item.end), item.value, item["break"]).statut, item.delta, classDelta, |
|
item["task"], moment(item.start).format("DD/MM/YYYY"), moment(item.end).format("DD/MM/YYYY"), item["estimated"], item["worked"]); |
|
item["visibleFrameTemplate"] = '<div id="' + item.id + |
|
'" class="progress-wrapper"><div class="progress"><label class="progress-label">' + item["progression"] + |
|
'<label></div></div>'; |
|
callback(item); // send back item as confirmation (can be changed) |
|
groups.update({ |
|
id: item.id, |
|
content: item["task"] + ' <span class="' + classDelta + '">Δ ' + parseInt(item["delta"], 10) + "</span>", |
|
groupDates: getGroupDates(item.start, item.end), |
|
delta: item["delta"], |
|
order: item.id |
|
}); |
|
// // update group category |
|
var category = groups.get(groups.get(item.id).nestedInGroup); |
|
var nestedGroups = category.nestedGroups; |
|
// console.log(nestedGroups); |
|
groups.forEach(function(group) { |
|
if (nestedGroups.indexOf(group.id) !== -1) delta += group.delta; |
|
}); |
|
delta >= 0 ? classDelta = "spanDeltaGreen" : classDelta = "spanDeltaRed"; |
|
// console.log(nestedGroups, delta); |
|
groups.update({ |
|
id: category.id, |
|
category: category.category, |
|
title: getCategory(category.category, nestedGroups.length, delta), |
|
content: getContentCategorie(category.category, nestedGroups.length, classDelta, delta), |
|
nestedGroups: nestedGroups, |
|
showNested: true, |
|
order: parseInt(category.id, 10) |
|
}); |
|
groups.forEach(function(group) { |
|
if (group.delta) globalDelta += group.delta; |
|
}); |
|
// console.log(globalDelta); |
|
globalDelta > 0 ? classDelta = "spanDeltaGreen" : classDelta = "spanDeltaRed"; |
|
var spanGlobalDelta = document.getElementById("spanGlobalDelta"); |
|
spanGlobalDelta.innerHTML = "Δ " + Math.round(globalDelta); |
|
spanGlobalDelta.className = classDelta; |
|
timeline.redraw(); |
|
// https://github.com/almende/vis/issues/577 |
|
} else { |
|
callback(null); // cancel editing item |
|
} |
|
}); |
|
} |
|
|
|
function htmlForSweetAlert(item) { |
|
var form = document.createElement('FORM'); |
|
form.setAttribute("role", "form"); |
|
form.setAttribute("class", "form-horizontal"); |
|
|
|
var divForm_row01 = document.createElement('DIV'); |
|
divForm_row01.setAttribute("class", "form-group"); |
|
var divTitle = document.createElement('DIV'); |
|
divTitle.setAttribute("class", "col-sm-8"); |
|
var labelTitle = document.createElement('LABEL'); |
|
labelTitle.innerHTML = "title"; |
|
labelTitle.setAttribute("for", "inputTitle"); |
|
labelTitle.setAttribute("class", "col-form-label") |
|
var inputTitle = document.createElement('INPUT'); |
|
inputTitle.id = "inputTitle"; |
|
inputTitle.setAttribute("class", "form-control"); |
|
inputTitle.setAttribute("type", "text"); |
|
inputTitle.setAttribute("value", item["task"]) |
|
|
|
var divPause = document.createElement('DIV'); |
|
divPause.setAttribute("class", "col-sm-4"); |
|
var labelPause = document.createElement('LABEL'); |
|
labelPause.innerHTML = "break"; |
|
labelPause.setAttribute("for", "selectPause"); |
|
labelPause.setAttribute("class", "col-form-label") |
|
var selectPause = document.createElement('SELECT'); |
|
selectPause.id = "selectPause"; |
|
selectPause.setAttribute("class", "form-control"); |
|
var array = ["", "BREAK"]; |
|
for (var i = 0; i < array.length; i++) { |
|
var option = document.createElement("option"); |
|
option.value = array[i]; |
|
option.text = array[i]; |
|
selectPause.appendChild(option); |
|
}; |
|
selectPause.value = item["break"]; |
|
|
|
/* row 2 */ |
|
var divForm_row02 = document.createElement('DIV'); |
|
divForm_row02.setAttribute("class", "form-group"); |
|
|
|
var divProgress = document.createElement('DIV'); |
|
divProgress.setAttribute("class", "col-sm-3"); |
|
var labelProgress = document.createElement('LABEL'); |
|
labelProgress.innerHTML = "progress"; |
|
labelProgress.setAttribute("for", "inputProgress"); |
|
labelProgress.setAttribute("class", "col-form-label") |
|
var inputProgress = document.createElement('INPUT'); |
|
inputProgress.id = "inputProgress"; |
|
inputProgress.setAttribute("class", "form-control"); |
|
inputProgress.setAttribute("type", "number"); |
|
inputProgress.setAttribute("max", 1); |
|
inputProgress.setAttribute("min", 0); |
|
inputProgress.setAttribute("step", 0.05); |
|
inputProgress.setAttribute("value", item["value"]); |
|
|
|
var divSalaries = document.createElement('DIV'); |
|
divSalaries.setAttribute("class", "col-sm-3"); |
|
var labelSalaries = document.createElement('LABEL'); |
|
labelSalaries.innerHTML = "employees"; |
|
labelSalaries.setAttribute("for", "inputSalaries"); |
|
labelSalaries.setAttribute("class", "col-form-label") |
|
var inputSalaries = document.createElement('INPUT'); |
|
inputSalaries.id = "inputSalaries"; |
|
inputSalaries.setAttribute("class", "form-control"); |
|
inputSalaries.setAttribute("type", "number"); |
|
inputSalaries.setAttribute("min", 0); |
|
inputSalaries.setAttribute("step", 1); |
|
inputSalaries.setAttribute("value", item["employees"]); |
|
|
|
var divPrevisionnel = document.createElement('DIV'); |
|
divPrevisionnel.setAttribute("class", "col-sm-3"); |
|
var labelPrevisionnel = document.createElement('LABEL'); |
|
labelPrevisionnel.innerHTML = "estimated"; |
|
labelPrevisionnel.setAttribute("for", "inputPrevisionnel"); |
|
labelPrevisionnel.setAttribute("class", "col-form-label") |
|
var inputPrevisionnel = document.createElement('INPUT'); |
|
inputPrevisionnel.id = "inputPrevisionnel"; |
|
inputPrevisionnel.setAttribute("class", "form-control"); |
|
inputPrevisionnel.setAttribute("type", "number"); |
|
inputPrevisionnel.setAttribute("min", 0); |
|
inputPrevisionnel.setAttribute("step", 1); |
|
inputPrevisionnel.setAttribute("value", item["estimated"]); |
|
|
|
var divReel = document.createElement('DIV'); |
|
divReel.setAttribute("class", "col-sm-3"); |
|
var labelReel = document.createElement('LABEL'); |
|
labelReel.innerHTML = "worked"; |
|
labelReel.setAttribute("for", "inputReel"); |
|
labelReel.setAttribute("class", "col-form-label") |
|
var inputReel = document.createElement('INPUT'); |
|
inputReel.id = "inputReel"; |
|
inputReel.setAttribute("class", "form-control"); |
|
inputReel.setAttribute("type", "number"); |
|
inputReel.setAttribute("min", 0); |
|
inputReel.setAttribute("step", 1); |
|
inputReel.setAttribute("value", item["worked"]); |
|
|
|
/* BUILD FORM */ |
|
/* ROW 01*/ |
|
divTitle.appendChild(inputTitle); |
|
inputTitle.parentNode.insertBefore(labelTitle, inputTitle); |
|
|
|
divPause.appendChild(selectPause); |
|
selectPause.parentNode.insertBefore(labelPause, selectPause); |
|
|
|
divForm_row01.appendChild(divTitle); |
|
divForm_row01.appendChild(divPause); |
|
|
|
/* ROW 02 */ |
|
divProgress.appendChild(inputProgress); |
|
inputProgress.parentNode.insertBefore(labelProgress, inputProgress); |
|
|
|
divSalaries.appendChild(inputSalaries); |
|
inputSalaries.parentNode.insertBefore(labelSalaries, inputSalaries); |
|
|
|
divPrevisionnel.appendChild(inputPrevisionnel); |
|
inputPrevisionnel.parentNode.insertBefore(labelPrevisionnel, inputPrevisionnel); |
|
|
|
divReel.appendChild(inputReel); |
|
inputReel.parentNode.insertBefore(labelReel, inputReel); |
|
|
|
divForm_row02.appendChild(divProgress); |
|
divForm_row02.appendChild(divSalaries); |
|
divForm_row02.appendChild(divPrevisionnel); |
|
divForm_row02.appendChild(divReel); |
|
|
|
form.appendChild(divForm_row01); |
|
form.appendChild(divForm_row02); |
|
return form; |
|
} |
|
|
|
/* HELPERS dataset */ |
|
|
|
function getCategory(category, count, delta) { |
|
return category + ", " + count + " items" + ", delta: " + Math.round(delta); |
|
} |
|
|
|
function getContentCategorie(category, count, classDelta, delta) { |
|
return '<i class="glyphicon glyphicon-pencil" title="long click to edit"></i> ' + category + |
|
' <span class="spanGroup" title="items">' + count + "</span>" + ' <span class="' + |
|
classDelta + '" title="delta">Δ ' + Math.round(delta) + "</span>" |
|
} |
|
|
|
function getClassName(statut, pause) { |
|
if (statut === "in progress") className = "default"; |
|
if (statut === "planned") className = "prevu"; |
|
if (statut === "done") className = "termine"; |
|
if (statut && pause && pause === "BREAK") { |
|
statut = '<span class="glyphicon glyphicon-exclamation-sign" ></span> ' + pause; |
|
className = "pause" |
|
} |
|
// console.log(statut); |
|
return { 'statutContent': statut, 'className': className }; |
|
} |
|
|
|
function getStatut(start, end, progress, pause) { |
|
var currentDate = moment(), |
|
statut; |
|
// console.log(progress, pause); |
|
if (start <= currentDate && end >= currentDate) { |
|
statut = "in progress"; |
|
progress = progress; |
|
className = "default"; |
|
} else if (start <= currentDate && end <= currentDate) { |
|
statut = "done"; |
|
progress = (pause === "BREAK") ? progress : 1; |
|
className = "termine"; |
|
} else if (start >= currentDate && end > currentDate) { |
|
statut = "planned"; |
|
progress = 0; |
|
className = "prevu"; |
|
} |
|
// console.log(statut, progress); |
|
return { 'statut': statut, 'progress': progress, "className": className }; |
|
} |
|
|
|
function getDelta(previsionnel, reel) { |
|
var delta = 0; |
|
if (previsionnel && reel) { |
|
delta = parseInt(previsionnel, 10) - parseInt(reel, 10); |
|
} else { |
|
delta = parseInt(previsionnel, 10); |
|
reel = 0 |
|
} |
|
return delta |
|
} |
|
|
|
function getTooltip(statut, delta, classDelta, title, start, end, previsionnel, reel) { |
|
// console.log(statut, reel); |
|
var tooltip; |
|
if (statut === "in progress" || statut === "done") { |
|
delta >= 0 ? classDelta = "spanDeltaGreen" : classDelta = "spanDeltaRed"; |
|
tooltip = title + "<br>du " + start + " au " + end + "<br>prévisionnel : " + previsionnel + |
|
"hrs" + "<br>réel : " + reel + "hrs" + ' <span class="' + classDelta + |
|
'">Δ' + delta + "</span>"; |
|
} else { |
|
tooltip = title + "<br>du " + start + |
|
" au " + end + "<br>prévisionnel : " + previsionnel + "hrs"; |
|
} |
|
return tooltip |
|
} |
|
|
|
function getGroupDates(start, end) { |
|
if (isValidDate(moment(start).toDate()) && isValidDate(moment(end).endOf("day").toDate())) { |
|
groupDates = [moment(start).toDate(), moment(end).endOf("day").toDate()] |
|
} else { |
|
groupDates = [null, null]; |
|
} |
|
return groupDates; |
|
} |
|
|
|
/* Change with modal */ |
|
|
|
function changeTooltip(item) { |
|
var tooltip = item["task"] + "<br>du " + moment(item.start).format("DD/MM/YYYY") + " au " + moment(item.end).format("DD/MM/YYYY"); |
|
tooltip += "<br>prévisionnel : " + item["estimated"] + "hrs" + "<br>réel : " + item["worked"] + "hrs"; |
|
tooltip += ' <span class="' + item.classDelta + '">Δ' + item.delta + "</span>"; |
|
return tooltip; |
|
} |
|
|
|
function changeContent(item) { |
|
var content = parseInt(item["employees"], 10) + ' employees' + ' (' + |
|
getClassName(getStatut(moment(item.start), moment(item.end), item["value"], item["break"]).statut, item["break"]).statutContent + ')'; |
|
return content; |
|
} |
|
|
|
function changeVisibleFrameTemplate(item) { |
|
console.log(item); |
|
if (item["value"]) { |
|
progress = parseInt(item["value"] * 100, 10) + "%"; |
|
visibleFrameTemplate = '<div id="' + item.id + |
|
'" class="progress-wrapper"><div class="progress"><label class="progress-label">' + progress + |
|
'<label></div></div>'; |
|
} else { |
|
visibleFrameTemplate = 0; |
|
} |
|
return visibleFrameTemplate; |
|
} |
|
|
|
// To hide show all groups |
|
function changeTitleAttr(flag) { |
|
//console.log($("#puce-showHideAllGroups").hasClass("puce-triangle-down")); |
|
if (flag) { |
|
document.getElementById('allGroups-div').title = 'collapse all'; |
|
} else { |
|
document.getElementById('allGroups-div').title = 'expand all'; |
|
} |
|
} |
|
|
|
/* Export data using with FileSaver.js and PapaParse libraries*/ |
|
function exportCSV(data, filename) { |
|
//console.log(data); |
|
dataToCSV = Papa.unparse(data); |
|
var blob = new Blob([dataToCSV], { type: "text/csv" }); |
|
saveAs(blob, filename + ".csv"); |
|
} |
|
|
|
// https://stackoverflow.com/questions/7445328/check-if-a-string-is-a-date-value |
|
function isValidDate(d) { |
|
var formats = [ |
|
moment.ISO_8601, |
|
"YYYY-MM-DD" |
|
]; |
|
return moment(d, formats, true).isValid(); |
|
} |
|
|
|
/** |
|
* CUstom date picker jQuery |
|
*/ |
|
$(function() { |
|
$("#inputDateToMove").datepicker(); |
|
$("#inputDateToMove").datepicker("setDate", moment().format("YYYY-MM-DD")); |
|
}); |
|
|
|
$.datepicker.setDefaults({ |
|
dateFormat: "yy-mm-dd", |
|
changeYear: true, |
|
showWeek: true, |
|
weekHeader: weekHeader(), |
|
monthNames: monthNames(), |
|
dayNamesMin: dayNamesMin(), |
|
firstDay: 1 |
|
}); |
|
|
|
function dayNamesMin() { |
|
if (locale === "fr") { |
|
return ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"]; |
|
} else { |
|
return ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]; |
|
} |
|
} |
|
|
|
function monthNames() { |
|
if (locale === "fr") { |
|
return [ |
|
"Janvier", |
|
"Février", |
|
"Mars", |
|
"Avril", |
|
"Mai", |
|
"Juin", |
|
"Juillet", |
|
"Août", |
|
"Septembre", |
|
"Octobre", |
|
"Novembre", |
|
"Decembre" |
|
]; |
|
} else { |
|
return [ |
|
"January", |
|
"February", |
|
"March", |
|
"April", |
|
"May", |
|
"June", |
|
"July", |
|
"August", |
|
"September", |
|
"October", |
|
"November", |
|
"December" |
|
]; |
|
} |
|
} |
|
|
|
function weekHeader() { |
|
if (locale === "fr") { |
|
return ["Sem"]; |
|
} else { |
|
return ["Wk"]; |
|
} |
|
} |