vernova/app/Http/Controllers/TaskController.php
2026-07-26 19:58:27 +03:30

363 lines
11 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Http\Requests\StoreTaskRequest;
use App\Models\Task;
use App\Models\Project;
use App\Models\User;
use App\Models\TaskLog;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class TaskController extends Controller
{
/**
* Display a listing of tasks with filters.
*/
public function index(Request $request)
{
$this->authorize('viewAny', Task::class);
$query = Task::with(['project', 'assignee', 'reporter']);
// Filter by project
if ($request->filled('project_id')) {
$query->where('project_id', $request->project_id);
}
// Filter by status
if ($request->filled('status')) {
$query->byStatus($request->status);
}
// Filter by priority
if ($request->filled('priority')) {
$query->byPriority($request->priority);
}
// Filter by assignee
if ($request->filled('assignee_id')) {
$query->where('assignee_id', $request->assignee_id);
}
// Only top-level tasks by default
$query->topLevel();
$query->orderBy('order')->orderBy('due_date', 'asc');
$tasks = $query->paginate(20)->appends($request->query());
$projects = Project::whereIn('status', ['planning', 'active'])->get();
$users = User::where('tenant_id', $this->getTenantId())->where('is_active', true)->get();
$statuses = ['pending', 'in_progress', 'review', 'done', 'cancelled'];
$priorities = ['low', 'medium', 'high', 'urgent'];
return view('tasks.index', compact('tasks', 'projects', 'users', 'statuses', 'priorities'));
}
/**
* Show the form for creating a new task.
*/
public function create(Request $request)
{
$this->authorize('create', Task::class);
$projects = Project::whereIn('status', ['planning', 'active'])->get();
$users = User::where('tenant_id', $this->getTenantId())->where('is_active', true)->get();
$statuses = ['pending', 'in_progress', 'review', 'done', 'cancelled'];
$priorities = ['low', 'medium', 'high', 'urgent'];
$preselectedProject = $request->get('project_id');
return view('tasks.form', compact('projects', 'users', 'statuses', 'priorities', 'preselectedProject'));
}
/**
* Store a newly created task in storage.
*/
public function store(StoreTaskRequest $request)
{
$this->authorize('create', Task::class);
$data = $request->validated();
$data['tenant_id'] = $this->getTenantId();
$data['reporter_id'] = Auth::id();
// Convert jalali dates if needed
if (!empty($data['due_date'])) {
$data['due_date'] = \App\Helpers\CalendarHelper::parseDate($data['due_date']);
}
if (!empty($data['start_date'])) {
$data['start_date'] = \App\Helpers\CalendarHelper::parseDate($data['start_date']);
}
$task = Task::create($data);
// Log the creation
TaskLog::create([
'task_id' => $task->id,
'user_id' => Auth::id(),
'field_changed' => 'created',
'old_value' => null,
'new_value' => ['status' => $task->status],
'comment' => __('task.log_created'),
]);
return redirect()
->route('tasks.show', $task)
->with('success', __('task.created_successfully'));
}
/**
* Display the specified task with logs.
*/
public function show(Task $task)
{
$this->authorize('view', $task);
$task->load([
'project',
'assignee',
'reporter',
'logs.user',
'children.assignee',
'workLogs.employee',
]);
return view('tasks.show', compact('task'));
}
/**
* Show the form for editing the specified task.
*/
public function edit(Task $task)
{
$this->authorize('update', $task);
$projects = Project::whereIn('status', ['planning', 'active'])->get();
$users = User::where('tenant_id', $this->getTenantId())->where('is_active', true)->get();
$statuses = ['pending', 'in_progress', 'review', 'done', 'cancelled'];
$priorities = ['low', 'medium', 'high', 'urgent'];
return view('tasks.form', compact('task', 'projects', 'users', 'statuses', 'priorities'));
}
/**
* Update the specified task in storage.
*/
public function update(StoreTaskRequest $request, Task $task)
{
$this->authorize('update', $task);
$data = $request->validated();
// Convert jalali dates if needed
if (!empty($data['due_date'])) {
$data['due_date'] = \App\Helpers\CalendarHelper::parseDate($data['due_date']);
}
if (!empty($data['start_date'])) {
$data['start_date'] = \App\Helpers\CalendarHelper::parseDate($data['start_date']);
}
// Track changes for TaskLog
$changedFields = [];
$watchFields = ['status', 'priority', 'assignee_id', 'title', 'due_date', 'project_id'];
foreach ($watchFields as $field) {
if (isset($data[$field]) && $task->$field != $data[$field]) {
$changedFields[$field] = [
'old' => $task->$field,
'new' => $data[$field],
];
}
}
$task->update($data);
// Create TaskLog entries for changed fields
foreach ($changedFields as $field => $changes) {
TaskLog::create([
'task_id' => $task->id,
'user_id' => Auth::id(),
'field_changed' => $field,
'old_value' => $changes['old'],
'new_value' => $changes['new'],
]);
}
return redirect()
->route('tasks.show', $task)
->with('success', __('task.updated_successfully'));
}
/**
* Remove the specified task from storage.
*/
public function destroy(Task $task)
{
$this->authorize('delete', $task);
$task->delete();
return redirect()
->route('tasks.index')
->with('success', __('task.deleted_successfully'));
}
/**
* Update task status via AJAX.
*/
public function updateStatus(Request $request, Task $task)
{
$this->authorize('updateStatus', $task);
$request->validate([
'status' => 'required|in:pending,in_progress,review,done,cancelled',
]);
$oldStatus = $task->status;
$task->status = $request->status;
if ($request->status === 'done') {
$task->completed_at = now();
} elseif ($oldStatus === 'done' && $request->status !== 'done') {
$task->completed_at = null;
}
$task->save();
// Log the status change
TaskLog::create([
'task_id' => $task->id,
'user_id' => Auth::id(),
'field_changed' => 'status',
'old_value' => $oldStatus,
'new_value' => $request->status,
]);
return response()->json([
'success' => true,
'message' => __('task.status_updated'),
'status' => $task->status,
'status_label' => $task->status_label,
]);
}
/**
* Display tasks in Kanban board view.
*/
public function kanban(Request $request)
{
$this->authorize('viewAny', Task::class);
$query = Task::with(['project', 'assignee']);
if ($request->filled('project_id')) {
$query->where('project_id', $request->project_id);
}
$query->topLevel();
$allTasks = $query->get();
$kanbanColumns = [
'pending' => $allTasks->where('status', 'pending'),
'in_progress' => $allTasks->where('status', 'in_progress'),
'review' => $allTasks->where('status', 'review'),
'done' => $allTasks->where('status', 'done'),
];
$projects = Project::whereIn('status', ['planning', 'active'])->get();
$selectedProject = $request->get('project_id');
return view('tasks.kanban', compact('kanbanColumns', 'projects', 'selectedProject'));
}
/**
* Display tasks in Gantt chart view.
*/
public function gantt(Request $request, ?Task $task = null)
{
$this->authorize('viewAny', Task::class);
$query = Task::with(['project', 'assignee']);
if ($task && $task->exists) {
$query->where('project_id', $task->project_id);
}
if ($request->filled('project_id')) {
$query->where('project_id', $request->project_id);
}
$tasks = $query->get();
$projects = Project::whereIn('status', ['planning', 'active'])->get();
// Group tasks by project for virtual project rows
$ganttData = [];
$projectIdCounter = 10000;
$grouped = $tasks->groupBy('project_id');
foreach ($grouped as $projectId => $projectTasks) {
$project = $projectTasks->first()?->project;
if (!$project) continue;
// Calculate project date range from tasks or use project dates
$minStart = $projectTasks->filter(fn($t) => $t->start_date)->min('start_date')
?? $project->start_date
?? now();
$maxDue = $projectTasks->filter(fn($t) => $t->due_date)->max('due_date')
?? $project->end_date
?? now()->addDays(30);
// Add virtual project row
$ganttData[] = [
'id' => $projectIdCounter + $projectId,
'text' => $project->name,
'start_date' => \Carbon\Carbon::parse($minStart)->format('d-m-Y'),
'duration' => max(1, \Carbon\Carbon::parse($minStart)->diffInDays(\Carbon\Carbon::parse($maxDue))),
'progress' => 0,
'open' => true,
'type' => 'project',
'project_id' => $projectId,
];
// Add task rows — handle missing dates
foreach ($projectTasks as $t) {
$startDate = $t->start_date
? \Carbon\Carbon::parse($t->start_date)
: \Carbon\Carbon::parse($minStart);
$dueDate = $t->due_date
? \Carbon\Carbon::parse($t->due_date)
: $startDate->copy()->addDays(7);
$duration = max(1, $startDate->diffInDays($dueDate));
$statusColor = match($t->status) {
'done' => '#10b981',
'in_progress' => '#3b82f6',
'review' => '#f59e0b',
'cancelled' => '#ef4444',
default => '#14b8a6',
};
$ganttData[] = [
'id' => $t->id,
'text' => $t->title,
'start_date' => $startDate->format('d-m-Y'),
'duration' => $duration,
'progress' => (float) ($t->progress ?? 0) / 100,
'parent' => $projectIdCounter + $projectId,
'color' => $statusColor,
'project_id' => $projectId,
'status' => $t->status,
'assignee' => $t->assignee?->name ?? '',
];
}
}
return view('tasks.gantt', compact('ganttData', 'projects'));
}
}