Enhances user experience with dark‑mode detection, responsive layout, and streamlined role selection workflow
220 lines
6.7 KiB
HTML
220 lines
6.7 KiB
HTML
<!-- DHTMLX Gantt Assets & Styles -->
|
|
<link rel="stylesheet" href="https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.css" type="text/css">
|
|
<style>
|
|
.gantt_task_line.overdue {
|
|
background-color: #ef4444 !important;
|
|
border-color: #dc2626 !important;
|
|
}
|
|
.gantt_task_progress.overdue {
|
|
background-color: #b91c1c !important;
|
|
}
|
|
.gantt_marker.today {
|
|
background-color: #EF4444 !important;
|
|
opacity: 0.8;
|
|
z-index: 9000;
|
|
}
|
|
.gantt_marker.today .gantt_marker_content {
|
|
background-color: #EF4444 !important;
|
|
color: white !important;
|
|
font-weight: bold;
|
|
font-size: 12px;
|
|
padding: 2px 6px;
|
|
border-radius: 4px;
|
|
white-space: nowrap;
|
|
}
|
|
.dark .gantt_container,
|
|
.dark .gantt_grid,
|
|
.dark .gantt_task,
|
|
.dark .gantt_task_bg {
|
|
background-color: #030712 !important;
|
|
}
|
|
.dark .gantt_grid_scale,
|
|
.dark .gantt_grid_head_cell {
|
|
background-color: #1f2937 !important;
|
|
color: #f3f4f6 !important;
|
|
border-color: #374151 !important;
|
|
}
|
|
.dark .gantt_cell,
|
|
.dark .gantt_grid_data .gantt_cell {
|
|
background-color: #030712 !important;
|
|
color: #e5e7eb !important;
|
|
border-color: #1f2937 !important;
|
|
}
|
|
.dark .gantt_row,
|
|
.dark .gantt_row.odd {
|
|
background-color: #030712 !important;
|
|
border-color: #1f2937 !important;
|
|
}
|
|
.dark .gantt_row:hover,
|
|
.dark .gantt_row.odd:hover {
|
|
background-color: #1f2937 !important;
|
|
}
|
|
.dark .gantt_task_cell {
|
|
border-color: #1f2937 !important;
|
|
}
|
|
.dark .gantt_scale_line,
|
|
.dark .gantt_scale_cell {
|
|
background-color: #1f2937 !important;
|
|
color: #f3f4f6 !important;
|
|
border-color: #374151 !important;
|
|
}
|
|
.dark .gantt_task_scale .gantt_scale_cell {
|
|
border-color: #374151 !important;
|
|
}
|
|
.dark .gantt_side_content {
|
|
color: #e5e7eb !important;
|
|
}
|
|
.dark .gantt_link_arrow {
|
|
border-color: #9ca3af !important;
|
|
}
|
|
.dark .gantt_link_line_wrapper .gantt_link_line {
|
|
background-color: #9ca3af !important;
|
|
}
|
|
.dark .gantt_grid_data .gantt_row {
|
|
border-bottom: 1px solid #1f2937 !important;
|
|
}
|
|
.dark .gantt_grid_data .gantt_cell {
|
|
border-right: 1px solid #1f2937 !important;
|
|
}
|
|
.dark .gantt_task_row {
|
|
background-color: #030712 !important;
|
|
border-bottom: 1px solid #1f2937 !important;
|
|
}
|
|
.dark .gantt_task_row.gantt_row_task:hover {
|
|
background-color: #1f2937 !important;
|
|
}
|
|
.dark .gantt_layout_cell::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
.dark .gantt_layout_cell::-webkit-scrollbar-track {
|
|
background: #030712;
|
|
}
|
|
.dark .gantt_layout_cell::-webkit-scrollbar-thumb {
|
|
background: #374151;
|
|
border-radius: 4px;
|
|
}
|
|
.dark .gantt_layout_cell::-webkit-scrollbar-thumb:hover {
|
|
background: #6b7280;
|
|
}
|
|
</style>
|
|
|
|
<div id="{{ gantt_element_id|default:'gantt_here' }}" style="width:100%;height:{{ gantt_height|default:'600' }}px;min-height:{{ gantt_height|default:'600' }}px;">
|
|
<div class="flex items-center justify-center h-full text-gray-400 dark:text-gray-500">
|
|
<i class="fas fa-spinner fa-spin text-lg mr-2"></i> Loading timeline...
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.js"></script>
|
|
<script>
|
|
(function() {
|
|
const ganttData = {{ gantt_data|safe }};
|
|
const elementId = "{{ gantt_element_id|default:'gantt_here' }}";
|
|
const nameColumnLabel = "{{ name_column_label|default:'Name' }}";
|
|
|
|
function initGantt() {
|
|
if (typeof gantt === 'undefined' || !gantt.init) {
|
|
setTimeout(initGantt, 200);
|
|
return;
|
|
}
|
|
const container = document.getElementById(elementId);
|
|
if (!container || container.offsetParent === null) {
|
|
setTimeout(initGantt, 200);
|
|
return;
|
|
}
|
|
if (!ganttData.data || ganttData.data.length === 0) return;
|
|
|
|
try {
|
|
try { gantt.plugins({ marker: true }); } catch(e) {}
|
|
|
|
gantt.config.date_format = "%Y-%m-%d %H:%i";
|
|
gantt.config.xml_date = "%Y-%m-%d %H:%i";
|
|
|
|
{% if is_project_timeline %}
|
|
gantt.config.scales = [
|
|
{ unit: "year", step: 1, format: "%Y" },
|
|
{ unit: "month", step: 1, format: "%F" }
|
|
];
|
|
{% else %}
|
|
gantt.config.scales = [
|
|
{ unit: "month", step: 1, format: "%F %Y" },
|
|
{ unit: "day", step: 1, format: "%j" }
|
|
];
|
|
{% endif %}
|
|
|
|
gantt.config.readonly = true;
|
|
gantt.config.drag_move = false;
|
|
gantt.config.drag_resize = false;
|
|
gantt.config.drag_progress = false;
|
|
gantt.config.drag_links = false;
|
|
gantt.config.grid_width = 350;
|
|
gantt.config.row_height = 40;
|
|
gantt.config.task_height = 32;
|
|
gantt.config.bar_height = 24;
|
|
|
|
gantt.config.columns = [
|
|
{ name: "text", label: nameColumnLabel, width: 200, tree: true },
|
|
{ name: "status", label: "Status", width: 100, align: "center" },
|
|
{ name: "duration", label: "<span style='font-size:11px; letter-spacing:-0.5px;'>Duration</span>", width: 50, align: "center" }
|
|
];
|
|
|
|
gantt.templates.task_class = function(start, end, task) {
|
|
return task.is_overdue ? "overdue" : "";
|
|
};
|
|
|
|
gantt.templates.tooltip_text = function(start, end, task) {
|
|
return '<b>' + nameColumnLabel + ':</b> ' + task.text + '<br/>' +
|
|
'<b>Status:</b> ' + task.status + '<br/>' +
|
|
'<b>Duration:</b> ' + task.duration + ' day(s)<br/>' +
|
|
'<b>Progress:</b> ' + Math.round(task.progress * 100) + '%<br/>' +
|
|
'<b>Start:</b> ' + gantt.templates.tooltip_date_format(start) + '<br/>' +
|
|
'<b>End:</b> ' + gantt.templates.tooltip_date_format(end) +
|
|
(task.is_overdue ? '<br/><b style="color: #ef4444;">⚠ OVERDUE</b>' : '');
|
|
};
|
|
|
|
gantt.init(elementId);
|
|
gantt.clearAll();
|
|
|
|
const processedData = {
|
|
data: ganttData.data.map(function(task) {
|
|
function convertDate(dateStr) {
|
|
if (!dateStr) return dateStr;
|
|
if (dateStr.indexOf('-') === 2) {
|
|
var parts = dateStr.split(' ');
|
|
var datePart = parts[0];
|
|
var timePart = parts[1] || '00:00';
|
|
var d = datePart.split('-');
|
|
return d[2] + '-' + d[1] + '-' + d[0] + ' ' + timePart;
|
|
}
|
|
return dateStr;
|
|
}
|
|
return Object.assign({}, task, {
|
|
start_date: convertDate(task.start_date),
|
|
end_date: convertDate(task.end_date)
|
|
});
|
|
}),
|
|
links: ganttData.links || []
|
|
};
|
|
|
|
gantt.parse(processedData);
|
|
|
|
if (typeof gantt.addMarker === 'function') {
|
|
gantt.addMarker({
|
|
start_date: new Date(),
|
|
css: "today",
|
|
text: "Today"
|
|
});
|
|
}
|
|
} catch(e) {
|
|
console.error('Gantt init error:', e);
|
|
}
|
|
}
|
|
|
|
if (document.readyState === 'complete') {
|
|
initGantt();
|
|
} else {
|
|
window.addEventListener('load', initGantt);
|
|
}
|
|
})();
|
|
</script>
|