363 lines
11 KiB
PHP
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'));
|
|
}
|
|
}
|