Renamed project

Added project research
Added project demo page
This commit is contained in:
Ray 2023-12-27 15:09:20 +00:00
parent 2c328659ff
commit bf086e9c13
11 changed files with 344 additions and 148 deletions

View File

@ -249,6 +249,20 @@ Array.prototype.selectMany = function (...filters) {
return result;
};
Array.prototype.toList = function (propName) {
let result = [];
this.forEach(e => {
if (typeof(e[propName]) == undefined) {
return;
}
result.push(e[propName]);
});
return result;
};
Array.prototype.sortTree = function (childPropName, sortPropName) {
this.orderBy(sortPropName);

View File

@ -3,4 +3,14 @@ Math.randomN = function (min, max) {
max = Math.floor(max);
return Math.floor(Math.random() * (max - min) + min);
};
Math.average = function (values) {
let result = 0;
values.forEach(e => {
result += parseFloat(e);
});
return Math.round(result / values.length);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

BIN
project/research/GANTT.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 KiB

View File

@ -1,128 +0,0 @@
<!doctype html>
<html lang="en-GB">
<head>
<meta charset="UTF-8" />
<meta http-equiv="content-type" content="text/html" charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="" />
<meta name="keyword" content="" />
<script src="ryzjsext.min.js"></script>
<script src="ryz-gantts.js"></script>
<!-- <script src="bsdialog4.min.js"></script> -->
<title></title>
<style>
.text-sm {
font-size: 0.8em;
}
</style>
<script>
var gantt1 = new RyzGantt({ Name: "New Project 1" });
gantt1.AddTask({
ID: 1,
Name: "Task A",
StartDelay: 0,
Duration: 1,
DependsOnTaskID: null,
IsCollated: false,
CollatedTaskID: null
});
gantt1.AddTask({
ID: 2,
Name: "Task B",
StartDelay: 1,
Duration: 1,
DependsOnTaskID: 1,
IsCollated: false,
CollatedTaskID: null
});
gantt1.AddTask({
ID: 3,
Name: "Task C",
StartDelay: 5,
Duration: 1,
DependsOnTaskID: null,
IsCollated: false,
CollatedTaskID: null
});
gantt1.AddTask({
ID: 4,
Name: "Task D",
StartDelay: 1,
Duration: 1,
DependsOnTaskID: 3,
IsCollated: false,
CollatedTaskID: null
});
gantt1.AddTask({
ID: 5,
Name: "Task B1",
StartDelay: 4,
Duration: 1,
DependsOnTaskID: 2,
IsCollated: true,
CollatedTaskID: null
});
gantt1.AddTask({
ID: 6,
Name: "Task B11",
StartDelay: 0,
Duration: 1,
DependsOnTaskID: null,
IsCollated: false,
CollatedTaskID: 5
});
gantt1.AddTask({
ID: 7,
Name: "Task B12",
StartDelay: 0,
Duration: 7,
DependsOnTaskID: null,
IsCollated: false,
CollatedTaskID: 5
});
gantt1.AddTask({
ID: 8,
Name: "Task E",
StartDelay: 3,
Duration: 1,
DependsOnTaskID: null,
IsCollated: true,
CollatedTaskID: null
});
gantt1.AddTask({
ID: 9,
Name: "Task E1",
StartDelay: 3,
Duration: 1,
DependsOnTaskID: null,
IsCollated: false,
CollatedTaskID: 8
});
gantt1.Invalidate();
console.log(gantt1.ExportTasks());
</script>
</head>
<body>
</body>
</html>

View File

@ -36,12 +36,13 @@ class RyzGantt {
// FinishDate: null, // new Date(),
StartDelay: 0, // Days
Duration: 1, // Days
DependsOnTaskID: null,
PredecessorTaskID: null,
IsCollated: false,
CollatedTaskID: null,
// CalcWorkHours: 0,
ActuWorkHours: null,
Progress: 0
Progress: 0,
Resources: []
};
}
@ -56,12 +57,14 @@ class RyzGantt {
FinishDate: null, // new Date(),
StartDelay: 0, // Days
Duration: 1, // Days
DependsOnTaskID: null,
PredecessorTaskID: null,
IsCollated: false,
CollatedTaskID: null,
CalcWorkHours: 0,
ActuWorkHours: null,
Progress: 0,
Resources: [],
Level: 0,
Tasks: []
};
}
@ -72,15 +75,15 @@ class RyzGantt {
const newTask = Object.assign(a.NewTask, task);
const newTaskNode = Object.assign(a.NewTaskNode, newTask);
if ((newTaskNode.DependsOnTaskID == null) && (newTaskNode.CollatedTaskID == null)) {
if ((newTaskNode.PredecessorTaskID == null) && (newTaskNode.CollatedTaskID == null)) {
a.Tasks.push(newTaskNode);
} else if (newTaskNode.DependsOnTaskID != null) {
const node = a.FindTask(newTaskNode.DependsOnTaskID);
} else if (newTaskNode.PredecessorTaskID != null) {
const node = a.FindTask(newTaskNode.PredecessorTaskID);
if (node != null) {
node.Tasks.push(newTaskNode);
} else {
a.#log("Task not found (" + newTaskNode.DependsOnTaskID + ")");
a.#log("Task not found (" + newTaskNode.PredecessorTaskID + ")");
}
} else if (newTaskNode.CollatedTaskID != null) {
const node = a.FindTask(newTaskNode.CollatedTaskID);
@ -151,22 +154,22 @@ class RyzGantt {
return 0;
}
if ((node.DependsOnTaskID == null) && (node.CollatedTaskID == null) && (node.IsCollated == false)) {
if ((node.PredecessorTaskID == null) && (node.CollatedTaskID == null) && (node.IsCollated == false)) {
// 1 = task no-parent/root
return 1;
}
if ((node.DependsOnTaskID == null) && (node.CollatedTaskID == null) && (node.IsCollated == true)) {
if ((node.PredecessorTaskID == 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)) {
if ((node.PredecessorTaskID != null) && (node.CollatedTaskID == null) && (node.IsCollated == false)) {
// 3 = task with parent
return 3;
}
if ((node.DependsOnTaskID != null) && (node.CollatedTaskID == null) && (node.IsCollated == true)) {
if ((node.PredecessorTaskID != null) && (node.CollatedTaskID == null) && (node.IsCollated == true)) {
// 4 = task with parent, collated/group
return 4;
}
@ -237,13 +240,14 @@ class RyzGantt {
break;
case 2: // task no-parent/root, collated/group
result[i].StartDate = Date.addDays(a.Project.StartDate, result[i].StartDelay);
result[i].Progress = 0;
// update finish date, if possible
result[i].FinishDate = a.#calcCollatedFinishDate(result, i);
a.#updateCollatedTask(result, i);
break;
case 3: // task with parent
const node3 = result.first("ID", result[i].DependsOnTaskID);
const node3 = result.first("ID", result[i].PredecessorTaskID);
if (node3 != null) {
if (node3.FinishDate != null) {
result[i].StartDate = Date.addDays(node3.FinishDate, result[i].StartDelay);
@ -253,13 +257,14 @@ class RyzGantt {
break;
case 4: // task with parent, collated/group
const node4 = result.first("ID", result[i].DependsOnTaskID);
const node4 = result.first("ID", result[i].PredecessorTaskID);
if (node4 != null) {
if (node4.FinishDate != null) {
result[i].StartDate = Date.addDays(node4.FinishDate, result[i].StartDelay);
result[i].Progress = 0;
// update finish date, if possible
result[i].FinishDate = a.#calcCollatedFinishDate(result, i);
a.#updateCollatedTask(result, i);
}
}
@ -270,6 +275,7 @@ class RyzGantt {
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);
result[i].Level = (node5.Level + 1);
}
}
@ -279,9 +285,11 @@ class RyzGantt {
if (node6 != null) {
if (node6.StartDate != null) {
result[i].StartDate = Date.addDays(node6.StartDate, result[i].StartDelay);
result[i].Progress = 0;
result[i].Level = (node6.Level + 1);
// update finish date, if possible
result[i].FinishDate = a.#calcCollatedFinishDate(result, i);
a.#updateCollatedTask(result, i);
}
}
@ -326,8 +334,49 @@ class RyzGantt {
a.Tasks.sortTree("Tasks", "StartDelay");
}
RenderTaskGrid() {
const a = this;
let htmlContent = "";
#calcCollatedFinishDate(array, index) {
const result = a.ExportTasks();
result.forEach(e => {
let predecessorNo = "";
if (e.PredecessorTaskID != null) {
const predecessor = a.FindTask(e.PredecessorTaskID);
if (predecessor != null) {
predecessorNo = predecessor.Order;
}
}
if (e.IsCollated == true) {
htmlContent += "<tr class='b'>";
} else {
htmlContent += "<tr>";
}
htmlContent += "<td class='c'>" + e.Order + "</td>";
htmlContent += "<td></td>";
htmlContent += "<td>";
for (let i=0; i<e.Level; i++) {
htmlContent += "<span class='i'></span>";
}
htmlContent += e.Name;
htmlContent += "</td>";
htmlContent += "<td>" + e.Duration + " day" + (parseInt(e.Duration) == 1 ? "" : "s") + "</td>";
htmlContent += "<td class='c'>" + new Date(e.StartDate).toLocaleDateString() + "</td>";
htmlContent += "<td class='c'>" + new Date(e.FinishDate).toLocaleDateString() + "</td>";
htmlContent += "<td class='c'>" + predecessorNo + "</td>";
htmlContent += "<td></td>";
htmlContent += "<td></td>";
htmlContent += "</tr>";
});
return htmlContent;
}
#updateCollatedTask(array, index) {
let node2 = array.select("CollatedTaskID", array[index].ID);
if (node2.length <= 0) {
// No children
@ -346,7 +395,8 @@ class RyzGantt {
}
});
return new Date(node2FinishDate);
array[index].Progress = Math.average(node2.toList("Progress"));
array[index].FinishDate = new Date(node2FinishDate);
}
#log(message) {

4
ryzjsext.min.js vendored
View File

@ -7,12 +7,12 @@ Array.prototype.countMany=function(...a){let b=0;a.forEach(c=>{b+=this.count(c.p
Array.prototype.joinIfNotNullOrWhitespace=function(a){let b="";for(let c=0;c<this.length;c++)String.isNullOrWhitespace(this[c])||(String.isNullOrWhitespace(b)||(b+=a),b+=this[c]);return b};Array.prototype.first=function(a,b){for(let c=0;c<this.length;c++)if("undefined"!=typeof this[c][a]&&this[c][a]==b)return this[c];return null};Array.prototype.forEachTree=function(a,b){for(let c=0;c<this.length&&!1!==b(this[c])&&(0>=this[c][a].length||!1!==this[c][a].forEachTree(a,b));c++);};
Array.prototype.indexes=function(a,b){let c=[];for(let d=0;d<this.length;d++)"undefined"!=typeof this[d][a]&&this[d][a]==b&&c.push(d);return c};Array.prototype.orderBy=function(a){this.sort(function(b,c){return b[a]<c[a]?-1:b[a]>c[a]?1:0});return this};Array.prototype.orderByDesc=function(a){this.sort(function(b,c){return b[a]<c[a]?1:b[a]>c[a]?-1:0});return this};
Array.prototype.remove=function(a){let b=[];for(let c=0;c<this.length;c++)this[c]==a&&b.push(c);for(a=b.length-1;0<=a;a--)this.removeAt(b[a]);return this};Array.prototype.removeAt=function(a){if(0>a||a>=this.length)return this;this.splice(a,1);return this};Array.prototype.removeRange=function(a){for(let b=0;b<a.length;b++)this.remove(a[b]);return this};Array.prototype.select=function(a,b){let c=[];for(let d=0;d<this.length;d++)"undefined"!=typeof this[d][a]&&this[d][a]==b&&c.push(this[d]);return c};
Array.prototype.selectMany=function(...a){let b=this;a.forEach(c=>{b=b.select(c.propName,c.value)});return b};Array.prototype.sortTree=function(a,b){this.orderBy(b);for(let c=0;c<this.length;c++)0>=this[c][a].length||this[c][a].orderBy(b)};Boolean.isFalse=function(a){return String.isNullOrUndefined(a)?!0:a.toString().containsCI("false","f","y","0","x")};Boolean.isTrue=function(a){return String.isNullOrUndefined(a)?!1:a.toString().containsCI("true","t","n","1","o")};Boolean.ifTrue=function(a,b,c){return Boolean.isTrue(a)?b:c};Date.addDays=function(a,b){a=new Date(a);a.addDays(b);return a};Date.addMonths=function(a,b){a=new Date(a);a.addMonths(b);return a};Date.addYears=function(a,b){a=new Date(a);a.addYears(b);return a};Date.today=function(){let a=new Date;a.setHours(0);a.setMinutes(0);a.setSeconds(0);a.setMilliseconds(0);return a};Date.prototype.addDays=function(a){this.setDate(this.getDate()+parseInt(a))};Date.prototype.addMonths=function(a){this.setMonth(this.getMonth()+parseInt(a))};
Array.prototype.selectMany=function(...a){let b=this;a.forEach(c=>{b=b.select(c.propName,c.value)});return b};Array.prototype.toList=function(a){let b=[];this.forEach(c=>{b.push(c[a])});return b};Array.prototype.sortTree=function(a,b){this.orderBy(b);for(let c=0;c<this.length;c++)0>=this[c][a].length||this[c][a].orderBy(b)};Boolean.isFalse=function(a){return String.isNullOrUndefined(a)?!0:a.toString().containsCI("false","f","y","0","x")};Boolean.isTrue=function(a){return String.isNullOrUndefined(a)?!1:a.toString().containsCI("true","t","n","1","o")};Boolean.ifTrue=function(a,b,c){return Boolean.isTrue(a)?b:c};Date.addDays=function(a,b){a=new Date(a);a.addDays(b);return a};Date.addMonths=function(a,b){a=new Date(a);a.addMonths(b);return a};Date.addYears=function(a,b){a=new Date(a);a.addYears(b);return a};Date.today=function(){let a=new Date;a.setHours(0);a.setMinutes(0);a.setSeconds(0);a.setMilliseconds(0);return a};Date.prototype.addDays=function(a){this.setDate(this.getDate()+parseInt(a))};Date.prototype.addMonths=function(a){this.setMonth(this.getMonth()+parseInt(a))};
Date.prototype.addYears=function(a){this.setFullYear(this.getFullYear()+parseInt(a))};
Date.prototype.toCString=function(a){a=a.replace("fffffff",this.getMilliseconds().toString().padStart(7,"0"));a=a.replace("ffffff",this.getMilliseconds().toString().padStart(6,"0"));a=a.replace("fffff",this.getMilliseconds().toString().padStart(5,"0"));a=a.replace("yyyy",this.getFullYear().toString().padStart(4,"0"));a=a.replace("MMMM","{1}");a=a.replace("dddd","{2}");a=a.replace("ffff",this.getMilliseconds().toString().padStart(4,"0"));a=a.replace("yyy",this.getFullYear().toString().padStart(3,"0"));
a=a.replace("MMM","{3}");a=a.replace("ddd","{4}");a=a.replace("fff",this.getMilliseconds().toString().padStart(3,"0"));a=a.replace("zzz","");a=a.replace("yy",this.getFullYear().toString().slice(-2));a=a.replace("MM",(this.getMonth()+1).toString().padStart(2,"0"));a=a.replace("dd",this.getDate().toString().padStart(2,"0"));a=a.replace("HH",this.getHours().toString().padStart(2,"0"));a=a.replace("hh",(12<this.getHours()?this.getHours()-12:this.getHours()).toString().padStart(2,"0"));a=a.replace("mm",
this.getMinutes().toString().padStart(2,"0"));a=a.replace("ss",this.getSeconds().toString().padStart(2,"0"));a=a.replace("ff",this.getMilliseconds().toString().padStart(2,"0"));a=a.replace("tt","{5}");a=a.replace("zz","");a=a.replace("y",this.getFullYear().toString());a=a.replace("M",(this.getMonth()+1).toString());a=a.replace("d",this.getDate().toString());a=a.replace("H",this.getHours().toString());a=a.replace("h",(12<this.getHours()?this.getHours()-12:this.getHours()).toString());a=a.replace("m",
this.getMinutes().toString());a=a.replace("s",this.getSeconds().toString());a=a.replace("z","");a=a.replace("t","{6}");a=a.replace("Z","");a=a.replace("{1}",this.toLocaleString("default",{month:"long"}));a=a.replace("{2}",this.toLocaleString("default",{weekday:"long"}));a=a.replace("{3}",this.toLocaleString("default",{month:"short"}));a=a.replace("{4}",this.toLocaleString("default",{weekday:"short"}));a=a.replace("{5}",12<=this.getHours()?"PM":"AM");return a=a.replace("{6}",12<=this.getHours()?"P":
"A")};Document.ready=async function(a){(async function(){"loading"!==document.readyState?a():document.addEventListener("DOMContentLoaded",function(){a()})})()};Math.randomN=function(a,b){a=Math.ceil(a);b=Math.floor(b);return Math.floor(Math.random()*(b-a)+a)};String.isNullOrUndefined=function(a){return"undefined"==typeof a||null==a?!0:!1};String.isNullOrWhitespace=function(a){return String.isNullOrUndefined(a)?!0:"string"==typeof a?0>=a.trim().length:0>=a.toString().trim().length};String.joinIfNotNullOrWhitespace=function(a,...b){let c="";for(let d=0;d<b.length;d++)String.isNullOrWhitespace(b[d])||(String.isNullOrWhitespace(c)||(c+=a),c+=b[d]);return c};String.prototype.contains=function(...a){for(let b of a)if(this==b)return!0;return!1};
"A")};Document.ready=async function(a){(async function(){"loading"!==document.readyState?a():document.addEventListener("DOMContentLoaded",function(){a()})})()};Math.randomN=function(a,b){a=Math.ceil(a);b=Math.floor(b);return Math.floor(Math.random()*(b-a)+a)};Math.average=function(a){let b=0;a.forEach(c=>{b+=parseFloat(c)});return Math.round(b/a.length)};String.isNullOrUndefined=function(a){return"undefined"==typeof a||null==a?!0:!1};String.isNullOrWhitespace=function(a){return String.isNullOrUndefined(a)?!0:"string"==typeof a?0>=a.trim().length:0>=a.toString().trim().length};String.joinIfNotNullOrWhitespace=function(a,...b){let c="";for(let d=0;d<b.length;d++)String.isNullOrWhitespace(b[d])||(String.isNullOrWhitespace(c)||(c+=a),c+=b[d]);return c};String.prototype.contains=function(...a){for(let b of a)if(this==b)return!0;return!1};
String.prototype.containsCI=function(...a){for(let b of a)if(this.toLowerCase()==b.toLowerCase())return!0;return!1};String.prototype.encodeHtmlLinks=function(){return value.replace(/(http[s]{0,1}:\/\/[^\s]+)/g,"<a href='$1'>$1</a>")};String.prototype.toTitleCase=function(){let a;a=this.replace(/([A-Z]{1})/g," $1");a=a.trim();return a=a.charAt(0).toUpperCase()+a.substr(1)};String.prototype.getFilename=function(){return this.substring(this.lastIndexOf("/")+1)};Window.goToTop=function(){Window.scrollTo(0,0)};
Window.fragment={get:function(){if(!window.location.hash)return null;const a=window.location.hash.indexOf("?");return 0>a?window.location.hash.substring(1):window.location.hash.substring(1,a)},getQuery:function(){if(!window.location.hash)return null;var a=window.location.hash;a=a.indexOf("?");if(0>a)return null;a=hasQueryString.substring(a+1);a=new URLSearchParams(a);const b={};for(const [c,d]of a.entries())b[c]=d;return b},clear:function(){location.hash="";history.replaceState("","",location.pathname)}};

250
ryzproj-test.html Normal file
View File

@ -0,0 +1,250 @@
<!doctype html>
<html lang="en-GB">
<head>
<meta charset="UTF-8" />
<meta http-equiv="content-type" content="text/html" charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="" />
<meta name="keyword" content="" />
<script src="ryzjsext.min.js"></script>
<script src="ryz-proj.js"></script>
<!-- <script src="bsdialog4.min.js"></script> -->
<title></title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 10pt;
}
.ryz-project {
border-spacing: 0px;
border-collapse: separate;
cursor: default;
}
.ryz-project .b {
font-weight: bold;
}
.ryz-project .c {
text-align: center;
}
.ryz-project .i {
display: inline-block;
width: 20px;
}
.ryz-project thead tr {
font-weight: bold;
font-size: 0.8em;
user-select: none;
height: 40px;
text-align: center;
vertical-align: bottom;
}
.ryz-project thead tr th {
background-color: #E1E1E1;
border-color: #B8B8B8;
border-style: solid;
border-width: 0px 0px 1px 1px;
color: #3E7138;
margin: 0px;
padding: 6px 10px 5px 10px;
min-width: 20px;
}
.ryz-project thead tr th:first-child {
background-color: inherit;
border-left-width: 0px;
}
.ryz-project thead tr th:last-child {
border-right-width: 1px;
}
.ryz-project tbody tr td {
border-color: #B8B8B8;
border-style: solid;
border-width: 0px 0px 1px 0px;
margin: 0px;
padding: 5px 5px 5px 5px;
}
.ryz-project tbody tr td:first-child {
border-right-width: 1px;
color: #7E7E7E;
font-size: 0.8em;
text-align: center;
user-select: none;
}
.ryz-project tbody tr td:last-child {
border-right-width: 1px;
}
.ryz-project tbody tr:hover td:first-child {
background-color: #D1F2C7;
color: #3E7138;
font-weight: bold;
}
</style>
<script>
var gantt1 = new RyzGantt({ Name: "New Project 1" });
gantt1.AddTask({
ID: 1,
Name: "Task A",
StartDelay: 0,
Duration: 1,
PredecessorTaskID: null,
IsCollated: false,
CollatedTaskID: null
});
gantt1.AddTask({
ID: 2,
Name: "Task B",
StartDelay: 1,
Duration: 1,
PredecessorTaskID: 1,
IsCollated: false,
CollatedTaskID: null
});
gantt1.AddTask({
ID: 3,
Name: "Task C",
StartDelay: 5,
Duration: 1,
PredecessorTaskID: null,
IsCollated: false,
CollatedTaskID: null
});
gantt1.AddTask({
ID: 4,
Name: "Task D",
StartDelay: 1,
Duration: 1,
PredecessorTaskID: 3,
IsCollated: false,
CollatedTaskID: null
});
gantt1.AddTask({
ID: 5,
Name: "Task B1",
StartDelay: 4,
Duration: 1,
PredecessorTaskID: 2,
IsCollated: true,
CollatedTaskID: null
});
gantt1.AddTask({
ID: 6,
Name: "Task B11",
StartDelay: 0,
Duration: 1,
PredecessorTaskID: null,
IsCollated: false,
CollatedTaskID: 5
});
gantt1.AddTask({
ID: 7,
Name: "Task B12",
StartDelay: 0,
Duration: 7,
PredecessorTaskID: null,
IsCollated: false,
CollatedTaskID: 5,
Progress: 50
});
gantt1.AddTask({
ID: 8,
Name: "Task E",
StartDelay: 3,
Duration: 1,
PredecessorTaskID: null,
IsCollated: true,
CollatedTaskID: null
});
gantt1.AddTask({
ID: 9,
Name: "Task E1",
StartDelay: 3,
Duration: 1,
PredecessorTaskID: null,
IsCollated: false,
CollatedTaskID: 8
});
gantt1.Invalidate();
console.log(gantt1.ExportTasks());
Document.ready(async function() {
const taskGrid1 = document.getElementById("projectTaskGrid1");
taskGrid1.innerHTML = gantt1.RenderTaskGrid();
});
</script>
</head>
<body>
<div>
<table class="ryz-project">
<thead>
<tr>
<th></th>
<th></th>
<th>Task Name</th>
<th>Duration</th>
<th>Start</th>
<th>Finish</th>
<th>Predecessor</th>
<th>Resource Names</th>
<th></th>
</tr>
</thead>
<tbody id="projectTaskGrid1">
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>