<?php
$keyMaster= $_SESSION['project_id'];
$html = <<<EOT
<script src="anychart_8.13.1/js/anychart-base.min.js"></script>
<script src="anychart_8.13.1/js/anychart-ui.min.js"></script>
<script src="anychart_8.13.1/js/anychart-exports.min.js"></script>
<script src="anychart_8.13.1/js/anychart-gantt.min.js"></script>
<script src="anychart_8.13.1/js/anychart-data-adapter.min.js"></script>
<link href="anychart_8.13.1/css/anychart-ui.min.css" type="text/css" rel="stylesheet">
<link href="https://cdn.anychart.com/releases/8.13.1/fonts/css/anychart-font.min.css" type="text/css" rel="stylesheet">
EOT;
if ($_SESSION['language'] == "Spanish") {
$language = 'es-es';
$dateFormat='dd/MM/yyy';
$textName = 'Nombre';
$textProgress = 'Progreso';
$html .= <<<EOT
<script src="anychart_8.13.1/locales/es-es.js"></script>
EOT;
} else {
$language='en-us';
$dateFormat='MM/dd/yyy';
$textName = 'Name';
$textProgress = 'Progress';
$html .= <<<EOT
<script src="anychart_8.13.1/locales/en-us.js"></script>
EOT;
}
$records =[];
// Read Project the Project
$sql="SELECT (999000000+p.project_id) task_id,
concat(p.short_name,' - ',p.name) name,
DATE_FORMAT(p.`start_date`,'%Y-%m-%d') `start_date`,
DATE_FORMAT(ifnull(p.`actual_end_date`,p.`end_date`),'%Y-%m-%d') `end_date`,
0 `dynamic`,
'' `related_url`,
0 `milestone`,
p.`percent_complete`,
0 parent,
0 dependent,
p .`description`,
ifnull(u.login,'') creator
FROM projects p
inner join users u on (p.owner=u.user_id)
WHERE p.project_parent = $keyMaster";
$resql = DB::Query($sql);
while ($row = $resql->fetchAssoc()) {
$item = [
"id" => $row['task_id'],
"name" => addslashes($row['name']),
"actualStart" => strtotime($row['start_date']) * 1000,
"actualEnd" => strtotime($row['end_date']) * 1000,
"progressValue"=> $row['percent_complete'] / 100,
"collapsed" => true,
"task_link" => $row['related_url'],
"task_owner" => $row['creator'],
"task_description" => $row['description'],
"task_note" => addslashes(str_replace(hex2bin('0a'),'',str_replace(hex2bin('0d'),'',str_replace(PHP_EOL,'<br />', $task_description)))),
"actual"=> [
"fill"=> "#4CAF50", // color de la barra de fondo
"stroke"=> "#2E7D32" // borde opcional
],
"progress"=> [
"fill"=> "#319435" // color del % ejecutado
],
];
// Añadir al array principal
$records[] = $item;
}
// Read Task the Project
$sql="SELECT
t.`task_id`,
t.`name`,
DATE_FORMAT(t.`start_date`,'%Y-%m-%d') `start_date`,
DATE_FORMAT(t.`end_date`,'%Y-%m-%d') `end_date`,
t.`dynamic`,
t.`related_url`,
t.`milestone`,
t.`percent_complete`,
ifnull(t.`parent`,0) parent,
ifnull(t.`dependent`,0) dependent,
t.`description`,
ifnull(u.login,'') creator
FROM tasks t
inner join users u on (t.creator=u.user_id)
WHERE t.`projects_project_id` = $keyMaster
ORDER BY t.`order`";
$resql = DB::Query($sql);
while ($row = $resql->fetchAssoc()) {
$item = [
"id" => $row['task_id'],
"name" => addslashes($row['name']),
"actualStart" => strtotime($row['start_date']) * 1000,
"actualEnd" => strtotime($row['end_date']) * 1000,
"progressValue"=> $row['percent_complete'] / 100,
"collapsed" => true,
"task_link" => $row['related_url'],
"task_owner" => $row['creator'],
"task_description" => $row['description'],
"task_note" => addslashes(str_replace(hex2bin('0a'),'',str_replace(hex2bin('0d'),'',str_replace(PHP_EOL,'<br />', $task_description)))),
];
// Si tiene parent
if (!empty($row['parent'])) {
$item["parent"] = $row['parent'];
}
// Si tiene conexión
if (!empty($row['dependent'])) {
$item["connectTo"] = $row['dependent'];
}
// Si es Milestone
if (!empty($row['milestone'])) {
$item["actualEnd"] = strtotime($row['start_date']) * 1000;
}
// Añadir al array principal
$records[] = $item;
}
$data = json_encode($records, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK);
$html .= <<<EOT
<style>
#container1 {
margin: 0;
padding: 0;
width: 100%;
height: 45vh;
display: flex;
flex-direction: column;
}
</style>
<div id="container1"></div>
<script>
let chart;
const data = $data;
anychart.onDocumentReady(function () {
// Activar locale español (funciona con CDN)
anychart.format.inputLocale('$language');
anychart.format.inputDateTimeFormat('$dateFormat'); //Like '12/03/2012'
anychart.format.outputLocale('$language');
var treeData = anychart.data.tree(data, "as-table");
// console.log("treeData: ", treeData);
chart = anychart.ganttProject();
// Licencia (si la tienes)
window.anychart.licenseKey('XXXXXXXXX-XXXXXXXXX-XXXXXXXXX');
var credits = chart.credits();
credits.enabled(false);
// Activa el panel de controles (export, print, fullscreen)
var exports = chart.exports();
// console.log("exports: ", exports);
// Set client side export settings.
exports.clientside({
'path': 'https://cdn.anychart.com/3rd/', // path to dependencies
'enabled': true, // clientside export enabled
'fallback': false // fallback to server side exporting
});
chart.data(treeData);
// set chart row's height
chart.defaultRowHeight(30);
// set start splitter position settings
chart.splitterPosition(324);
// get chart data grid
var dataGrid = chart.dataGrid();
// set data grid's first column settings
dataGrid.column(0).width(30).labels({
hAlign: 'center'
});
// set data grid's seconds column settings
dataGrid
.column(1)
.collapseExpandButtons(false) // disable collapse/expand buttons
.width(180)
.title('$textName');
// create data grid's third column and set it's settings
dataGrid.column(2).width(80).title('$textProgress');
// create column with collapse/expand buttons
dataGrid
.column(3)
.title(null)
.width(30)
.collapseExpandButtons(true);
// set column labels overrider
dataGrid.column(0).labelsOverrider(labelsTextSettings);
dataGrid.column(1).labelsOverrider(labelsTextSettings);
dataGrid.column(2).labelsOverrider(labelsTextSettings);
// set labels formatter for the third column
dataGrid
.column(2)
.labels()
.format(function () {
if (this.item.numChildren()) {
// formatter for groupping tasks data grid labels
if (this.progress !== 0) {
return this.progress < 1 ? 'In progress' : 'Done';
}
}
if (this.item.get('actualEnd')) {
// format labels in group
return Math.round(this.progress * 100) + '%';
}
if (this.item.getParent()) {
// formatter for milestones
return parseInt(
this.item.getParent().get('progressValue')
) === 100
? 'Ok'
: '';
}
});
// get data grid buttons
var button = dataGrid.buttons();
// set buttons width and height
button.width(20).height(15);
// disable button's background for every state
button.background(false);
button.hovered().background(false);
button.selected().background(false);
// set data grid button's content using function
var buttonContent = function (path) {
// get center of button's path
var centerX = this.width / 2;
var centerY = this.height / 2;
// draw arrow in path
if (this.state === 'selected') {
path
.clear()
.moveTo(3, 4)
.lineTo(centerX, this.height - 2)
.lineTo(this.width - 3, 4)
.stroke('#00a8e0', 3, 'solid', 'miter', 'round');
} else {
path
.clear()
.moveTo(6, 2)
.lineTo(this.width - 4, centerY + 1)
.lineTo(6, this.height)
.stroke('#748a8d', 3, 'solid', 'miter', 'round');
}
};
// set function as content of button
button.content(buttonContent);
button.selected().content(buttonContent);
// get timeline
var timeline = chart.getTimeline();
// get timeline's scale
var scale = timeline.scale();
// configure the scale
// scale.zoomLevels([["week", "month", "quarter"]]);
scale.zoomLevels([["week", "quarter"]]);
// Set scale maximum and minimum.
scale.minimumGap(10,true);
scale.maximumGap(15,true);
// zoom chart all dates range
// chart.fitAll();
// zoom the timeline to the given units
chart.zoomTo("week", 25, "first-date"); // Ponemos 25 semanas de visualización
// Crear un marcador vertical
const todayMarker = timeline.lineMarker();
// Fecha fija: 2010-02-15
const fechaFija = new Date().getTime();
// const fechaFija = new Date("2010-05-02").getTime();
// Configurar el marcador
todayMarker
.value(fechaFija) // fecha fija en milisegundos
.stroke('#ff0000', 2) // color y grosor
.scale(timeline.scale()); // importante: usar la escala del timeline
// hide groupping tasks progress bars
timeline.groupingTasks().progress().fill(null).stroke(null);
timeline
.groupingTasks()
.progress()
.selected()
.fill(null)
.stroke(null);
// set shapes for groupping tasks rendering
timeline
.groupingTasks()
.rendering()
.shapes([
{
name: 'task',
shapeType: 'path',
disablePointerEvents: false
}
]);
// set custom drawer for groupping tasks
timeline.groupingTasks().rendering().drawer(groupingTaskDrawer);
// set groupping tasks bar's position
timeline.groupingTasks().anchor('center').position('center');
// set groupping tasks labels settings
timeline
.groupingTasks()
.labels()
.format(function () {
return this.name;
})
.fontWeight('bold')
.fontColor('#fff')
.anchor('center')
.position('center');
// set timeline tasks fill and stroke settings
timeline.tasks().selected().fill('#1876d1');
timeline.tasks().selected().stroke('#1876d1');
// set timeline tasks progress fill and stroke settings
timeline.tasks().progress().selected().fill('#203951');
timeline.tasks().progress().selected().stroke('#203951');
chart.container("container1");
chart.draw();
// set "collapsed" data value after row collapse/expand
chart.listen('rowCollapseExpand', function (e) {
e.item.set('collapsed', e.collapsed);
});
// configure tooltips of the timeline
chart.getTimeline().tooltip().useHtml(true);
chart.dataGrid().tooltip().useHtml(true);
chart.getTimeline().tooltip().format(
"<span style='font-weight:600;font-size:12pt'>" +
"{%actualStart}{dateTimeFormat:dd MMM} - " +
"{%actualEnd}{dateTimeFormat:dd MMM}</span><br><br>" +
"Progress: {%progress}<br>" +
"Manager: {%task_owner}"
);
// listen for task edit event
chart.listen("rowDblClick", function (e) {
const item = e.item; // tarea seleccionada
const id = item.get("id");
const name = item.get("name");
const start = item.get("actualStart");
const end = item.get("actualEnd");
const progress = item.get("progressValue");
console.log("Tarea seleccionada:", { id, name, start, end, progress });
// Aquí llamas a tu modal o lógica de edición
editarTarea({ id, name, start, end, progress });
});
// Captura de Tarea desde TimeLine
/*
chart.getTimeline().listen("pointClick", function (e) {
const item = e.item;
console.log("Click en barra:", item.get("id"));
editarTarea({
id: item.get("id"),
name: item.get("name"),
start: item.get("actualStart"),
end: item.get("actualEnd"),
progress: item.get("progressValue")
});
});
*/
// set tasks labels settings
timeline.tasks().labels().useHtml(true);
timeline.tasks().labels()
.format("- <span style='color:#ffffff'>{%progress}</span>")
.anchor('center')
.position('center');
});
// custom drawer function for the groupping tasks
function groupingTaskDrawer() {
var path;
var itemBounds;
// get path from shapes
path = this.shapes.task;
// get recommended bounds for drawing
var bounds = this.predictedBounds;
// get shift value
var shift = halfShift(path.strokeThickness());
// set bar's bounds
itemBounds = anychart.math.rect(
Math.round(bounds.left) - shift,
Math.round(bounds.top) - 5 - shift,
Math.round(bounds.width) - shift,
21 - shift
);
// get progress value from data
var progress = parseInt(this.item.get('progressValue')) / 100;
// set path's fill and stroke settings
path.fill(function () {
if (progress !== 0) {
return progress < 1 ? '#f4a700' : '#27a858';
}
return '#e51a23';
});
path.stroke(null);
// draw rounded rectangle on the path
anychart.graphics.vector.primitives.roundedRect(
path,
itemBounds,
0
);
}
function halfShift(strokeThickness) {
return strokeThickness % 2 ? 0.5 : 0;
}
// labels overrider function
function labelsTextSettings(label, item) {
// filter data items with "collapsed" = true
var collapsedItems = chart.data().filter(function (item) {
return item.get('collapsed');
});
if (item.numChildren()) {
// override only groupping tasks labels
// set label's weight
label.fontWeight('bold');
// set label's color
label.fontColor(
collapsedItems.indexOf(item) > -1 ? '#748a8d' : '#00a8e0'
);
}
}
// Actualizar tarea después de editarla
function actualizarTarea(id, cambios) {
const dataItem = chart.data().search("id", id);
if (dataItem) {
Object.entries(cambios).forEach(([key, value]) => {
dataItem.set(key, value);
});
chart.draw(); // refresca el gráfico
}
}
/* ejemplo de actualización de tarea:
actualizarTarea("step1", {
actualStart: new Date("2024-05-01").getTime(),
actualEnd: new Date("2024-05-10").getTime(),
progressValue: "80%"
});
*/
// Función para abrir modal de visualización y edición
function editarTarea(item ) {
console.log("Editar tarea:", item);
const isFullScreen = !!document.fullscreenElement; // Controla si está en pantalla completa
if (!isFullScreen && item.id < 10000000 ) { //sólo cuando no está pantalla comleta y no en proyecto dependiente, se "levanta" ventana popup
var Ev_id = item.id;
var title = item.name;
var url = "tasks_view.php?page=view_gantt&editid1="+Ev_id;
var header = '<h2 data-itemtype="view_header" data-itemid="view_header" data-pageid="10">'+'Tarea: '+ title+'</h2>' ;
window.popup = Runner.displayPopup({
url: url,
width: 1000,
height: 600,
header: header
});
}
};
</script>
EOT;
echo $html;
?>