361 lines
8.7 KiB
JavaScript
361 lines
8.7 KiB
JavaScript
class RyzGantt {
|
|
constructor(options) {
|
|
const a = this;
|
|
const _options = Object.assign(a.NewProject, options);
|
|
|
|
a.Project = _options;
|
|
a.Tasks = [];
|
|
|
|
a.#initialiseComponents();
|
|
}
|
|
|
|
#initialiseComponents() {
|
|
const a = this;
|
|
}
|
|
|
|
|
|
get NewProject() {
|
|
return {
|
|
Name: "",
|
|
Description: "",
|
|
StartDate: Date.today(),
|
|
Tag: null,
|
|
StartOfWeek: 1, // Monday
|
|
WorkHours: [0, 7.5, 7.5, 7.5, 7.5, 7.5, 0] // 0 = Sunday
|
|
};
|
|
}
|
|
|
|
get NewTask() {
|
|
return {
|
|
// Order: null,
|
|
ID: null,
|
|
Name: "",
|
|
Description: "",
|
|
Tag: null,
|
|
// StartDate: null, // new Date(),
|
|
// FinishDate: null, // new Date(),
|
|
StartDelay: 0, // Days
|
|
Duration: 1, // Days
|
|
DependsOnTaskID: null,
|
|
IsCollated: false,
|
|
CollatedTaskID: null,
|
|
// CalcWorkHours: 0,
|
|
ActuWorkHours: null
|
|
};
|
|
}
|
|
|
|
get NewTaskNode() {
|
|
return {
|
|
Order: null,
|
|
ID: null,
|
|
Name: "",
|
|
Description: "",
|
|
Tag: null,
|
|
StartDate: null, // new Date(),
|
|
FinishDate: null, // new Date(),
|
|
StartDelay: 0, // Days
|
|
Duration: 1, // Days
|
|
DependsOnTaskID: null,
|
|
IsCollated: false,
|
|
CollatedTaskID: null,
|
|
CalcWorkHours: 0,
|
|
ActuWorkHours: null,
|
|
Tasks: []
|
|
};
|
|
}
|
|
|
|
|
|
AddTask(task) {
|
|
const a = this;
|
|
const newTask = Object.assign(a.NewTask, task);
|
|
const newTaskNode = Object.assign(a.NewTaskNode, newTask);
|
|
|
|
if ((newTaskNode.DependsOnTaskID == null) && (newTaskNode.CollatedTaskID == null)) {
|
|
a.Tasks.push(newTaskNode);
|
|
} else if (newTaskNode.DependsOnTaskID != null) {
|
|
const node = a.FindTask(newTaskNode.DependsOnTaskID);
|
|
|
|
if (node != null) {
|
|
node.Tasks.push(newTaskNode);
|
|
} else {
|
|
a.#log("Task not found (" + newTaskNode.DependsOnTaskID + ")");
|
|
}
|
|
} else if (newTaskNode.CollatedTaskID != null) {
|
|
const node = a.FindTask(newTaskNode.CollatedTaskID);
|
|
|
|
if (node != null) {
|
|
node.Tasks.push(newTaskNode);
|
|
} else {
|
|
a.#log("Task not found (" + newTaskNode.CollatedTaskID + ")");
|
|
}
|
|
} else {
|
|
a.#log("Task not found (" + newTaskNode.ID + ")");
|
|
}
|
|
}
|
|
|
|
ClearTasks() {
|
|
const a = this;
|
|
|
|
a.Tasks = [];
|
|
}
|
|
|
|
ExportTasks() {
|
|
const a = this;
|
|
|
|
let result = a.Tasks.copy().flatten("Tasks");
|
|
for (var i=0; i<result.length; i++) {
|
|
result[i].Order = (i + 1);
|
|
|
|
delete result[i].Tasks;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
FindTask(id) {
|
|
const a = this;
|
|
|
|
return a.#searchForTaskID(a.Tasks, id);
|
|
}
|
|
|
|
/**
|
|
* Get node type.
|
|
* @returns {int} enum
|
|
*
|
|
* 0 = orphan
|
|
* 1 = task no-parent/root
|
|
* 2 = task no-parent/root, collated/group
|
|
* 3 = task with parent
|
|
* 4 = task with parent, collated/group
|
|
* 5 = sub-task
|
|
* 6 = sub-task, collated/group
|
|
*
|
|
*/
|
|
GetNodeType(node) {
|
|
if (node == null) {
|
|
// 0 = orphan
|
|
return 0;
|
|
}
|
|
|
|
if ((node.DependsOnTaskID == null) && (node.CollatedTaskID == null) && (node.IsCollated == false)) {
|
|
// 1 = task no-parent/root
|
|
return 1;
|
|
}
|
|
|
|
if ((node.DependsOnTaskID == null) && (node.CollatedTaskID == null) && (node.IsCollated == true)) {
|
|
// 2 = task no-parent/root, collated/group
|
|
return 2;
|
|
}
|
|
|
|
if ((node.DependsOnTaskID != null) && (node.CollatedTaskID == null) && (node.IsCollated == false)) {
|
|
// 3 = task with parent
|
|
return 3;
|
|
}
|
|
|
|
if ((node.DependsOnTaskID != null) && (node.CollatedTaskID == null) && (node.IsCollated == true)) {
|
|
// 4 = task with parent, collated/group
|
|
return 4;
|
|
}
|
|
|
|
if ((node.CollatedTaskID != null) && (node.IsCollated == false)) {
|
|
// 5 = sub-task
|
|
return 5;
|
|
}
|
|
|
|
if ((node.CollatedTaskID != null) && (node.IsCollated == true)) {
|
|
// 6 = sub-task, collated/group
|
|
return 6;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
Invalidate() {
|
|
const a = this;
|
|
let isSuccess = false;
|
|
|
|
if (!a.Recalculate()) {
|
|
isSuccess = false;
|
|
}
|
|
|
|
return isSuccess;
|
|
}
|
|
|
|
Recalculate() {
|
|
const a = this;
|
|
let isSuccess = false;
|
|
|
|
a.Sort();
|
|
|
|
// Get flat references
|
|
let result = a.Tasks.flatten("Tasks");
|
|
|
|
// Reset calculated values
|
|
for (var i=0; i<result.length; i++) {
|
|
result[i].Order = (i + 1);
|
|
result[i].StartDate = null;
|
|
result[i].FinishDate = null;
|
|
result[i].CalcWorkHours = null;
|
|
}
|
|
|
|
// Maximum 128 rounds
|
|
for (var x=0; x<128; x++) {
|
|
let pendingCount = result.copy().countMany(
|
|
{ propName: "StartDate", value: null },
|
|
{ propName: "FinishDate", value: null }
|
|
);
|
|
|
|
// Done
|
|
if (pendingCount <= 0) {
|
|
isSuccess = true;
|
|
break;
|
|
}
|
|
|
|
a.#log("Round " + (x + 1));
|
|
|
|
for (var i=0; i<result.length; i++) {
|
|
const nodeType = a.GetNodeType(result[i]);
|
|
|
|
switch (nodeType) {
|
|
case 1: // task no-parent/root
|
|
result[i].StartDate = Date.addDays(a.Project.StartDate, result[i].StartDelay);
|
|
result[i].FinishDate = Date.addDays(result[i].StartDate, result[i].Duration);
|
|
break;
|
|
case 2: // task no-parent/root, collated/group
|
|
result[i].StartDate = Date.addDays(a.Project.StartDate, result[i].StartDelay);
|
|
|
|
// update finish date, if possible
|
|
result[i].FinishDate = a.#calcCollatedFinishDate(result, i);
|
|
|
|
break;
|
|
case 3: // task with parent
|
|
const node3 = result.first("ID", result[i].DependsOnTaskID);
|
|
if (node3 != null) {
|
|
if (node3.FinishDate != null) {
|
|
result[i].StartDate = Date.addDays(node3.FinishDate, result[i].StartDelay);
|
|
result[i].FinishDate = Date.addDays(result[i].StartDate, result[i].Duration);
|
|
}
|
|
}
|
|
|
|
break;
|
|
case 4: // task with parent, collated/group
|
|
const node4 = result.first("ID", result[i].DependsOnTaskID);
|
|
if (node4 != null) {
|
|
if (node4.FinishDate != null) {
|
|
result[i].StartDate = Date.addDays(node4.FinishDate, result[i].StartDelay);
|
|
|
|
// update finish date, if possible
|
|
result[i].FinishDate = a.#calcCollatedFinishDate(result, i);
|
|
}
|
|
}
|
|
|
|
break;
|
|
case 5: // sub-task
|
|
const node5 = result.first("ID", result[i].CollatedTaskID);
|
|
if (node5 != null) {
|
|
if (node5.StartDate != null) {
|
|
result[i].StartDate = Date.addDays(node5.StartDate, result[i].StartDelay);
|
|
result[i].FinishDate = Date.addDays(result[i].StartDate, result[i].Duration);
|
|
}
|
|
}
|
|
|
|
break;
|
|
case 6: // sub-task, collated/group
|
|
const node6 = result.first("ID", result[i].CollatedTaskID);
|
|
if (node6 != null) {
|
|
if (node6.StartDate != null) {
|
|
result[i].StartDate = Date.addDays(node6.StartDate, result[i].StartDelay);
|
|
|
|
// update finish date, if possible
|
|
result[i].FinishDate = a.#calcCollatedFinishDate(result, i);
|
|
}
|
|
}
|
|
|
|
break;
|
|
default: // orphan
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculate work-hours
|
|
for (var i=0; i<result.length; i++) {
|
|
if (result[i].StartDate == null) {
|
|
continue;
|
|
}
|
|
|
|
if (result[i].FinishDate == null) {
|
|
continue;
|
|
}
|
|
|
|
if (result[i].Duration <= 0) {
|
|
continue;
|
|
}
|
|
|
|
let workHours = 0;
|
|
let date = new Date(result[i].StartDate);
|
|
for (let i=0; i<result[i].Duration; i++) {
|
|
const d = date.getDay();
|
|
|
|
workHours += a.Project.WorkHours[d];
|
|
}
|
|
|
|
result[i].CalcWorkHours = workHours;
|
|
}
|
|
|
|
return isSuccess;
|
|
}
|
|
|
|
Sort() {
|
|
const a = this;
|
|
|
|
a.Tasks.sortTree("Tasks", "StartDelay");
|
|
}
|
|
|
|
|
|
#calcCollatedFinishDate(array, index) {
|
|
let node2 = array.select("CollatedTaskID", array[index].ID);
|
|
if (node2.length <= 0) {
|
|
// No children
|
|
return new Date(array[index].StartDate);
|
|
}
|
|
|
|
// Not ready, calculation pending
|
|
if (node2.any("FinishDate", null)) {
|
|
return null;
|
|
}
|
|
|
|
let node2FinishDate = new Date(array[index].StartDate);
|
|
node2.forEach(e => {
|
|
if (e.FinishDate > node2FinishDate) {
|
|
node2FinishDate = e.FinishDate;
|
|
}
|
|
});
|
|
|
|
return new Date(node2FinishDate);
|
|
}
|
|
|
|
#log(message) {
|
|
console.log(message);
|
|
}
|
|
|
|
#searchForTaskID(array, id) {
|
|
const a = this;
|
|
|
|
for (let i=0; i<array.length; i++) {
|
|
if (array[i].ID == id) {
|
|
return array[i];
|
|
}
|
|
|
|
if (array[i].Tasks.length > 0) {
|
|
const result = a.#searchForTaskID(array[i].Tasks, id);
|
|
if (result != null) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
} |