vernova/resources/views/tasks/gantt.blade.php
2026-07-26 19:58:27 +03:30

151 lines
6.9 KiB
PHP

@extends('layouts.app')
@section('content')
<div class="space-y-4">
<!-- Page Header -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<h1 class="text-2xl font-bold text-gray-900">{{ __('task.gantt') }}</h1>
<div class="flex items-center gap-2">
<select id="project-filter" onchange="filterProject(this.value)"
class="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-teal-500">
<option value="">{{ __('common.all') }} {{ __('project.projects') }}</option>
@foreach($projects as $project)
<option value="{{ $project->id }}" {{ request('project_id') == $project->id ? 'selected' : '' }}>{{ $project->name }}</option>
@endforeach
</select>
</div>
</div>
@if(empty($ganttData))
<div class="bg-white rounded-xl border border-gray-200 p-12 text-center">
<svg class="w-12 h-12 mx-auto text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2"/></svg>
<p class="mt-3 text-gray-500">{{ __('task.gantt_no_tasks') }}</p>
</div>
@else
<!-- Gantt Chart -->
<div class="bg-white rounded-xl border border-gray-200 overflow-hidden">
<div id="gantt_container" style="height: 600px;"></div>
</div>
<!-- Legend -->
<div class="bg-white rounded-xl border border-gray-200 p-4">
<h3 class="text-xs font-bold text-gray-700 mb-3">{{ __('task.gantt_legend') }}</h3>
<div class="flex flex-wrap gap-4">
<span class="flex items-center gap-1.5 text-xs text-gray-600"><span class="w-3 h-3 rounded" style="background:#14b8a6"></span> {{ __('task.status_pending') }}</span>
<span class="flex items-center gap-1.5 text-xs text-gray-600"><span class="w-3 h-3 rounded" style="background:#3b82f6"></span> {{ __('task.status_in_progress') }}</span>
<span class="flex items-center gap-1.5 text-xs text-gray-600"><span class="w-3 h-3 rounded" style="background:#f59e0b"></span> {{ __('task.status_review') }}</span>
<span class="flex items-center gap-1.5 text-xs text-gray-600"><span class="w-3 h-3 rounded" style="background:#10b981"></span> {{ __('task.status_done') }}</span>
<span class="flex items-center gap-1.5 text-xs text-gray-600"><span class="w-3 h-3 rounded" style="background:#ef4444"></span> {{ __('task.status_cancelled') }}</span>
</div>
</div>
@endif
</div>
@if(!empty($ganttData))
@push('styles')
<link rel="stylesheet" href="https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.css">
<style>
.gantt_grid_head_row { direction: ltr; }
.gantt_task_line { border-radius: 4px !important; }
.gantt_task_progress { border-radius: 4px 0 0 4px !important; opacity: 0.3; }
.gantt_row.project .gantt_cell { font-weight: bold; }
.today-marker { background: #ef4444; }
</style>
@endpush
@push('scripts')
<script src="https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.js"></script>
<script src="https://cdn.dhtmlx.com/gantt/edge/ext/dhtmlxgantt_marker.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Configure Gantt
gantt.config.readonly = true;
gantt.config.drag_move = false;
gantt.config.drag_progress = false;
gantt.config.drag_resize = false;
gantt.config.drag_links = false;
gantt.config.show_links = false;
gantt.config.row_height = 36;
gantt.config.bar_height = 22;
gantt.config.date_format = '%d-%m-%Y';
gantt.config.scale_unit = 'week';
gantt.config.subscales = [{unit: 'day', step: 1, date: '%d'}];
gantt.config.min_column_width = 30;
gantt.config.fit_tasks = true;
gantt.config.auto_types = true;
// Columns
gantt.config.columns = [
{name: 'text', label: '{{ __("task.task_name") }}', tree: true, width: 200},
{name: 'start_date', label: '{{ __("task.start_date") }}', align: 'center', width: 80},
{name: 'duration', label: '{{ __("task.gantt_duration") }}', align: 'center', width: 50, template: function(task) { return task.duration + ' {{ __("task.gantt_day") }}'; }},
];
// Tooltip
gantt.templates.tooltip_text = function(start, end, task) {
let statusLabels = {
'pending': '{{ __("task.status_pending") }}',
'in_progress': '{{ __("task.status_in_progress") }}',
'review': '{{ __("task.status_review") }}',
'done': '{{ __("task.status_done") }}',
'cancelled': '{{ __("task.status_cancelled") }}'
};
let html = '<b>' + task.text + '</b><br>';
html += '{{ __("task.start_date") }}: ' + gantt.templates.tooltip_date_format(start) + '<br>';
html += '{{ __("task.gantt_duration") }}: ' + task.duration + ' {{ __("task.gantt_day") }}<br>';
if (task.assignee) html += '{{ __("task.assignee") }}: ' + task.assignee + '<br>';
if (task.status && statusLabels[task.status]) html += '{{ __("task.status") }}: ' + statusLabels[task.status];
return html;
};
// Today marker
gantt.plugins({ marker: true });
gantt.addMarker({
start_date: new Date(),
css: 'today-marker',
text: '{{ __("task.gantt_today") }}'
});
// Init
gantt.init('gantt_container');
var ganttData = @json($ganttData);
gantt.parse({ data: ganttData });
// Ctrl+Scroll zoom
var currentZoom = 'week';
var zoomLevels = {
'day': { scale_unit: 'day', subscales: [], min_column_width: 30 },
'week': { scale_unit: 'week', subscales: [{unit: 'day', step: 1, date: '%d'}], min_column_width: 30 },
'month': { scale_unit: 'month', subscales: [{unit: 'week', step: 1, date: '%w'}], min_column_width: 40 }
};
document.getElementById('gantt_container').addEventListener('wheel', function(e) {
if (e.ctrlKey) {
e.preventDefault();
var levels = ['day', 'week', 'month'];
var idx = levels.indexOf(currentZoom);
if (e.deltaY < 0 && idx > 0) {
currentZoom = levels[idx - 1];
} else if (e.deltaY > 0 && idx < levels.length - 1) {
currentZoom = levels[idx + 1];
}
var cfg = zoomLevels[currentZoom];
gantt.config.scale_unit = cfg.scale_unit;
gantt.config.subscales = cfg.subscales;
gantt.config.min_column_width = cfg.min_column_width;
gantt.render();
}
}, { passive: false });
});
function filterProject(projectId) {
var url = '{{ route("tasks.gantt") }}';
if (projectId) url += '?project_id=' + projectId;
window.location.href = url;
}
</script>
@endpush
@endif
@endsection