562 lines
13 KiB
Vue
562 lines
13 KiB
Vue
<script>
|
|
import { gantt } from 'dhtmlx-gantt'
|
|
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
|
|
|
|
export default {
|
|
name: 'GanttChart',
|
|
data() {
|
|
return {
|
|
tasks: {
|
|
data: [
|
|
{
|
|
id: 1,
|
|
text: "Office Itinerary",
|
|
start_date: "01-04-2023",
|
|
duration: 17,
|
|
progress: 0.95,
|
|
open: true
|
|
},
|
|
{
|
|
id: 11,
|
|
text: "Office facing",
|
|
start_date: "01-04-2023",
|
|
duration: 5,
|
|
parent: 1,
|
|
progress: 1
|
|
},
|
|
{
|
|
id: 111,
|
|
text: "Interior office",
|
|
start_date: "01-04-2023",
|
|
duration: 3,
|
|
parent: 11,
|
|
progress: 1
|
|
},
|
|
{
|
|
id: 112,
|
|
text: "Air conditioner check",
|
|
start_date: "05-04-2023",
|
|
duration: 2,
|
|
parent: 11,
|
|
progress: 1
|
|
},
|
|
{
|
|
id: 12,
|
|
text: "Furniture installation",
|
|
start_date: "08-04-2023",
|
|
duration: 2,
|
|
parent: 1,
|
|
progress: 1
|
|
},
|
|
{
|
|
id: 13,
|
|
text: "Employee relocation",
|
|
start_date: "10-04-2023",
|
|
duration: 8,
|
|
parent: 1,
|
|
progress: 0.67,
|
|
open: true
|
|
},
|
|
{
|
|
id: 131,
|
|
text: "Preparing workplaces",
|
|
start_date: "10-04-2023",
|
|
duration: 3,
|
|
parent: 13,
|
|
progress: 1
|
|
},
|
|
{
|
|
id: 132,
|
|
text: "Workplaces importation",
|
|
start_date: "13-04-2023",
|
|
duration: 3,
|
|
parent: 13,
|
|
progress: 1
|
|
},
|
|
{
|
|
id: 133,
|
|
text: "Workplaces exportation",
|
|
start_date: "16-04-2023",
|
|
duration: 2,
|
|
parent: 13,
|
|
progress: 0
|
|
},
|
|
{
|
|
id: 2,
|
|
text: "Product launch",
|
|
start_date: "01-04-2023",
|
|
duration: 18,
|
|
progress: 0.73,
|
|
open: true
|
|
},
|
|
{
|
|
id: 21,
|
|
text: "Perform initial testing",
|
|
start_date: "01-04-2023",
|
|
duration: 5,
|
|
parent: 2,
|
|
progress: 1
|
|
},
|
|
{
|
|
id: 22,
|
|
text: "Development",
|
|
start_date: "03-04-2023",
|
|
duration: 16,
|
|
parent: 2,
|
|
progress: 0.68,
|
|
open: true
|
|
},
|
|
{
|
|
id: 221,
|
|
text: "Develop System",
|
|
start_date: "03-04-2023",
|
|
duration: 5,
|
|
parent: 22,
|
|
progress: 1
|
|
},
|
|
{
|
|
id: 222,
|
|
text: "Beta Release",
|
|
start_date: "08-04-2023",
|
|
duration: 1,
|
|
parent: 22,
|
|
progress: 1
|
|
},
|
|
{
|
|
id: 223,
|
|
text: "Integrate System",
|
|
start_date: "09-04-2023",
|
|
duration: 4,
|
|
parent: 22,
|
|
progress: 1
|
|
},
|
|
{
|
|
id: 224,
|
|
text: "Test",
|
|
start_date: "13-04-2023",
|
|
duration: 3,
|
|
parent: 22,
|
|
progress: 0.67
|
|
},
|
|
{
|
|
id: 225,
|
|
text: "Marketing",
|
|
start_date: "16-04-2023",
|
|
duration: 3,
|
|
parent: 22,
|
|
progress: 0
|
|
},
|
|
{
|
|
id: 23,
|
|
text: "Analysis",
|
|
start_date: "01-04-2023",
|
|
duration: 4,
|
|
parent: 2,
|
|
progress: 1
|
|
},
|
|
{
|
|
id: 24,
|
|
text: "Design",
|
|
start_date: "06-04-2023",
|
|
duration: 6,
|
|
parent: 2,
|
|
progress: 0.75,
|
|
open: true
|
|
},
|
|
{
|
|
id: 241,
|
|
text: "Design database",
|
|
start_date: "06-04-2023",
|
|
duration: 4,
|
|
parent: 24,
|
|
progress: 1
|
|
},
|
|
{
|
|
id: 242,
|
|
text: "Software design",
|
|
start_date: "10-04-2023",
|
|
duration: 2,
|
|
parent: 24,
|
|
progress: 0.5
|
|
}
|
|
],
|
|
links: [
|
|
{ id: 1, source: 111, target: 112, type: "0" },
|
|
{ id: 2, source: 112, target: 12, type: "0" },
|
|
{ id: 3, source: 12, target: 13, type: "0" },
|
|
{ id: 4, source: 131, target: 132, type: "0" },
|
|
{ id: 5, source: 132, target: 133, type: "0" },
|
|
{ id: 6, source: 221, target: 222, type: "0" },
|
|
{ id: 7, source: 222, target: 223, type: "0" },
|
|
{ id: 8, source: 223, target: 224, type: "0" },
|
|
{ id: 9, source: 224, target: 225, type: "0" },
|
|
{ id: 10, source: 23, target: 24, type: "0" },
|
|
{ id: 11, source: 241, target: 242, type: "0" }
|
|
]
|
|
}
|
|
}
|
|
},
|
|
mounted() {
|
|
this.initGantt()
|
|
},
|
|
beforeUnmount() {
|
|
if (gantt.$container) {
|
|
gantt.clearAll()
|
|
}
|
|
},
|
|
methods: {
|
|
initGantt() {
|
|
gantt.config.date_format = "%d-%m-%Y"
|
|
gantt.config.columns = [
|
|
{ name: "wbs", label: "WBS", width: 50, align: "center" },
|
|
{ name: "text", label: "TASK NAME", width: 250, tree: true },
|
|
{ name: "start_date", label: "START TIME", width: 100, align: "center" },
|
|
{ name: "duration", label: "DURATION", width: 80, align: "center" }
|
|
]
|
|
|
|
gantt.config.scale_unit = "day"
|
|
gantt.config.date_scale = "%d %M"
|
|
gantt.config.scale_height = 50
|
|
gantt.config.row_height = 40
|
|
gantt.config.bar_height = 24
|
|
gantt.config.grid_width = 450
|
|
|
|
gantt.config.subscales = [
|
|
{ unit: "month", step: 1, date: "%M %Y" },
|
|
{ unit: "day", step: 1, date: "%j" }
|
|
]
|
|
|
|
gantt.config.drag_move = true
|
|
gantt.config.drag_resize = true
|
|
gantt.config.drag_progress = true
|
|
gantt.config.drag_links = true
|
|
gantt.config.details_on_create = true
|
|
gantt.config.details_on_dblclick = true
|
|
|
|
gantt.templates.task_class = function(start, end, task) {
|
|
if (task.progress === 1) return "completed-task"
|
|
if (task.progress >= 0.75) return "high-progress-task"
|
|
if (task.progress >= 0.25) return "medium-progress-task"
|
|
return "low-progress-task"
|
|
}
|
|
|
|
gantt.templates.grid_row_class = function(start, end, task) {
|
|
if (task.$level === 0) return "summary-row"
|
|
return ""
|
|
}
|
|
|
|
gantt.templates.leftside_text = function(start, end, task) {
|
|
return ""
|
|
}
|
|
|
|
gantt.templates.rightside_text = function(start, end, task) {
|
|
if (task.progress === 1) {
|
|
return "<span class='completed-mark'>✓</span>"
|
|
}
|
|
return Math.round(task.progress * 100) + "%"
|
|
}
|
|
|
|
let taskCounter = 1
|
|
gantt.templates.grid_row_class = function(start, end, task) {
|
|
if (task.$level === 0) return "summary-row"
|
|
return ""
|
|
}
|
|
|
|
gantt.templates.grid_cell_value = function(item, column) {
|
|
if (column === "wbs") {
|
|
if (item.$level === 0) {
|
|
return taskCounter++
|
|
} else if (item.$level === 1) {
|
|
let parentIndex = gantt.getParent(item.id)
|
|
let parent = gantt.getTask(parentIndex)
|
|
let siblings = gantt.getChildren(parentIndex)
|
|
let childIndex = siblings.indexOf(item.id) + 1
|
|
return parent.$wbs + "." + childIndex
|
|
} else if (item.$level === 2) {
|
|
let parentIndex = gantt.getParent(item.id)
|
|
let grandParentIndex = gantt.getParent(parentIndex)
|
|
let parent = gantt.getTask(parentIndex)
|
|
let siblings = gantt.getChildren(parentIndex)
|
|
let childIndex = siblings.indexOf(item.id) + 1
|
|
return parent.$wbs + "." + childIndex
|
|
}
|
|
}
|
|
return gantt.templates.grid_cell_value_default(item, column)
|
|
}
|
|
|
|
gantt.attachEvent("onTaskLoading", function(task) {
|
|
if (task.$level === 0) {
|
|
task.$wbs = taskCounter
|
|
taskCounter++
|
|
} else if (task.$level === 1) {
|
|
let parent = gantt.getTask(gantt.getParent(task.id))
|
|
let siblings = gantt.getChildren(gantt.getParent(task.id))
|
|
let index = siblings.indexOf(task.id) + 1
|
|
task.$wbs = parent.$wbs + "." + index
|
|
} else if (task.$level === 2) {
|
|
let parentId = gantt.getParent(task.id)
|
|
let parent = gantt.getTask(parentId)
|
|
let siblings = gantt.getChildren(parentId)
|
|
let index = siblings.indexOf(task.id) + 1
|
|
task.$wbs = parent.$wbs + "." + index
|
|
}
|
|
return true
|
|
})
|
|
|
|
gantt.attachEvent("onAfterTaskAdd", function(id, item) {
|
|
this.saveToServer()
|
|
}.bind(this))
|
|
|
|
gantt.attachEvent("onAfterTaskDelete", function(id, item) {
|
|
this.saveToServer()
|
|
}.bind(this))
|
|
|
|
gantt.attachEvent("onAfterTaskUpdate", function(id, item) {
|
|
this.saveToServer()
|
|
}.bind(this))
|
|
|
|
gantt.attachEvent("onAfterLinkAdd", function(id, item) {
|
|
this.saveToServer()
|
|
}.bind(this))
|
|
|
|
gantt.attachEvent("onAfterLinkDelete", function(id, item) {
|
|
this.saveToServer()
|
|
}.bind(this))
|
|
|
|
gantt.init(this.$refs.ganttContainer)
|
|
gantt.parse(this.tasks)
|
|
},
|
|
addTask() {
|
|
const selectedId = gantt.getSelectedId()
|
|
const newTaskId = gantt.uid()
|
|
const today = new Date()
|
|
const formattedDate = gantt.date.date_to_str("%d-%m-%Y")(today)
|
|
|
|
const newTask = {
|
|
id: newTaskId,
|
|
text: "New Task",
|
|
start_date: formattedDate,
|
|
duration: 3,
|
|
progress: 0,
|
|
parent: selectedId || 0
|
|
}
|
|
|
|
gantt.addTask(newTask, selectedId || "")
|
|
gantt.selectTask(newTaskId)
|
|
gantt.showTask(newTaskId)
|
|
},
|
|
deleteTask() {
|
|
const selectedTask = gantt.getSelectedId()
|
|
if (selectedTask) {
|
|
if (confirm('Are you sure you want to delete this task?')) {
|
|
gantt.deleteTask(selectedTask)
|
|
}
|
|
} else {
|
|
alert('Please select a task to delete')
|
|
}
|
|
},
|
|
markCompleted() {
|
|
const selectedTask = gantt.getSelectedId()
|
|
if (selectedTask) {
|
|
let task = gantt.getTask(selectedTask)
|
|
task.progress = task.progress === 1 ? 0 : 1
|
|
gantt.updateTask(selectedTask)
|
|
gantt.render()
|
|
} else {
|
|
alert('Please select a task to mark as completed')
|
|
}
|
|
},
|
|
saveToServer() {
|
|
console.log("Saving tasks to server...")
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="gantt-container">
|
|
<div class="gantt-toolbar">
|
|
<button @click="addTask" class="btn btn-primary">Add Task</button>
|
|
<button @click="deleteTask" class="btn btn-danger">Delete Selected</button>
|
|
<button @click="markCompleted" class="btn btn-success">Toggle Complete</button>
|
|
</div>
|
|
<div ref="ganttContainer" class="gantt-chart"></div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.gantt-container {
|
|
width: 100%;
|
|
height: 650px;
|
|
background: white;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.gantt-toolbar {
|
|
padding: 10px;
|
|
background: #f8f9fa;
|
|
border-bottom: 1px solid #dee2e6;
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
|
|
.btn {
|
|
padding: 8px 16px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: #007bff;
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: #0056b3;
|
|
}
|
|
|
|
.btn-danger {
|
|
background: #dc3545;
|
|
color: white;
|
|
}
|
|
|
|
.btn-danger:hover {
|
|
background: #c82333;
|
|
}
|
|
|
|
.btn-success {
|
|
background: #28a745;
|
|
color: white;
|
|
}
|
|
|
|
.btn-success:hover {
|
|
background: #218838;
|
|
}
|
|
|
|
.gantt-chart {
|
|
height: calc(100% - 60px);
|
|
min-height: 500px;
|
|
}
|
|
</style>
|
|
|
|
<style>
|
|
.gantt_container {
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.gantt_grid_head_cell,
|
|
.gantt_grid_data .gantt_cell {
|
|
border-right: 1px solid #dee2e6;
|
|
border-bottom: 1px solid #dee2e6;
|
|
}
|
|
|
|
.gantt_grid_head_cell {
|
|
background: #f8f9fa;
|
|
font-weight: 600;
|
|
text-align: center;
|
|
padding: 12px 8px;
|
|
color: #495057;
|
|
}
|
|
|
|
.gantt_cell {
|
|
padding: 10px 8px;
|
|
vertical-align: middle;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.gantt_tree_content {
|
|
padding-left: 5px;
|
|
}
|
|
|
|
.gantt_tree_icon {
|
|
width: 16px;
|
|
height: 16px;
|
|
margin-right: 5px;
|
|
}
|
|
|
|
.gantt_task_line.completed-task {
|
|
background: #28a745;
|
|
border: 1px solid #1e7e34;
|
|
}
|
|
|
|
.gantt_task_line.high-progress-task {
|
|
background: #17a2b8;
|
|
border: 1px solid #138496;
|
|
}
|
|
|
|
.gantt_task_line.medium-progress-task {
|
|
background: #ffc107;
|
|
border: 1px solid #e0a800;
|
|
}
|
|
|
|
.gantt_task_line.low-progress-task {
|
|
background: #dc3545;
|
|
border: 1px solid #c82333;
|
|
}
|
|
|
|
.summary-row {
|
|
background: #f1f3f4;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.gantt_task_progress {
|
|
background: rgba(255, 255, 255, 0.4);
|
|
}
|
|
|
|
.gantt_scale_cell {
|
|
border-bottom: 1px solid #ced4da;
|
|
border-right: 1px solid #ced4da;
|
|
background: #f8f9fa;
|
|
font-weight: 600;
|
|
color: #495057;
|
|
}
|
|
|
|
.gantt_task_line {
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.gantt_side_content {
|
|
color: #212529;
|
|
font-size: 11px;
|
|
margin-top: 2px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.completed-mark {
|
|
color: #28a745;
|
|
font-weight: bold;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.gantt_grid_data .gantt_row:nth-child(odd) {
|
|
background: #fafafa;
|
|
}
|
|
|
|
.gantt_grid_data .gantt_row:hover {
|
|
background: #e3f2fd;
|
|
}
|
|
|
|
.gantt_selected .gantt_cell {
|
|
background: #007bff;
|
|
color: white;
|
|
}
|
|
|
|
.gantt_task_link {
|
|
stroke: #007bff;
|
|
stroke-width: 2;
|
|
}
|
|
|
|
.gantt_task_link .gantt_link_arrow {
|
|
fill: #007bff;
|
|
}
|
|
|
|
.gantt_milestone {
|
|
background: #6f42c1;
|
|
border: 2px solid #5a2d91;
|
|
}
|
|
</style> |