232 lines
6.9 KiB
PHP
232 lines
6.9 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
namespace App\Livewire\Tasks;
|
||
|
|
|
||
|
|
use App\Models\Project;
|
||
|
|
use App\Models\Task;
|
||
|
|
use Livewire\Attributes\Layout;
|
||
|
|
use Livewire\Component;
|
||
|
|
|
||
|
|
#[Layout('components.layouts.app')]
|
||
|
|
class Index extends Component
|
||
|
|
{
|
||
|
|
public bool $showForm = false;
|
||
|
|
|
||
|
|
public ?int $editingId = null;
|
||
|
|
|
||
|
|
public string $title = '';
|
||
|
|
|
||
|
|
public ?string $description = null;
|
||
|
|
|
||
|
|
public string $status = 'todo';
|
||
|
|
|
||
|
|
public ?int $projectId = null;
|
||
|
|
|
||
|
|
public int $priority = 0;
|
||
|
|
|
||
|
|
public ?string $dueAt = null;
|
||
|
|
|
||
|
|
public ?string $estimatedHours = null;
|
||
|
|
|
||
|
|
public ?string $actualHours = null;
|
||
|
|
|
||
|
|
public string $search = '';
|
||
|
|
|
||
|
|
public string $statusFilter = '';
|
||
|
|
|
||
|
|
public string $priorityFilter = '';
|
||
|
|
|
||
|
|
public ?int $projectFilter = null;
|
||
|
|
|
||
|
|
public function mount()
|
||
|
|
{
|
||
|
|
// If project_id is passed as a query parameter, filter by it
|
||
|
|
if (request()->has('project_id')) {
|
||
|
|
$this->projectFilter = (int) request('project_id');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public function render()
|
||
|
|
{
|
||
|
|
$query = Task::with(['project', 'comments']);
|
||
|
|
|
||
|
|
if ($this->search) {
|
||
|
|
$query->where('title', 'like', '%' . $this->search . '%')
|
||
|
|
->orWhere('description', 'like', '%' . $this->search . '%');
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($this->statusFilter) {
|
||
|
|
$query->where('status', $this->statusFilter);
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($this->priorityFilter) {
|
||
|
|
$query->where('priority', $this->priorityFilter);
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($this->projectFilter) {
|
||
|
|
$query->where('project_id', $this->projectFilter);
|
||
|
|
}
|
||
|
|
|
||
|
|
$tasks = $query->orderBy('priority', 'desc')->orderBy('created_at')->get();
|
||
|
|
|
||
|
|
return view('livewire.tasks.index', [
|
||
|
|
'tasks' => $tasks,
|
||
|
|
'projects' => Project::orderBy('name')->get(),
|
||
|
|
'statuses' => Task::STATUSES,
|
||
|
|
'totalTasks' => Task::count(),
|
||
|
|
'todoTasks' => Task::todo()->count(),
|
||
|
|
'inProgressTasks' => Task::inProgress()->count(),
|
||
|
|
'doneTasks' => Task::done()->count(),
|
||
|
|
'blockedTasks' => Task::blocked()->count(),
|
||
|
|
'waitingReviewTasks' => Task::waitingReview()->count(),
|
||
|
|
'dueSoonTasks' => Task::dueSoon()->count(),
|
||
|
|
'overdueTasks' => Task::overdue()->count(),
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function create(): void
|
||
|
|
{
|
||
|
|
$this->resetForm();
|
||
|
|
// Pre-select the filtered project if one is selected
|
||
|
|
if ($this->projectFilter) {
|
||
|
|
$this->projectId = $this->projectFilter;
|
||
|
|
}
|
||
|
|
$this->showForm = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function edit(int $id): void
|
||
|
|
{
|
||
|
|
$task = Task::findOrFail($id);
|
||
|
|
$this->editingId = $task->id;
|
||
|
|
$this->title = $task->title;
|
||
|
|
$this->description = $task->description;
|
||
|
|
$this->status = $task->status;
|
||
|
|
$this->projectId = $task->project_id;
|
||
|
|
$this->priority = $task->priority;
|
||
|
|
$this->dueAt = $task->due_at?->format('Y-m-d\TH:i') ?? '';
|
||
|
|
$this->estimatedHours = $task->estimated_hours;
|
||
|
|
$this->actualHours = $task->actual_hours;
|
||
|
|
$this->showForm = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function save(): void
|
||
|
|
{
|
||
|
|
$validated = $this->validate([
|
||
|
|
'title' => ['required', 'string', 'max:255'],
|
||
|
|
'description' => ['nullable', 'string'],
|
||
|
|
'status' => ['required', 'string', 'in:' . implode(',', Task::STATUSES)],
|
||
|
|
'projectId' => ['nullable', 'integer', 'exists:projects,id'],
|
||
|
|
'priority' => ['integer', 'min:0', 'max:10'],
|
||
|
|
'dueAt' => ['nullable', 'date'],
|
||
|
|
'estimatedHours' => ['nullable', 'numeric', 'min:0'],
|
||
|
|
'actualHours' => ['nullable', 'numeric', 'min:0'],
|
||
|
|
]);
|
||
|
|
|
||
|
|
Task::updateOrCreate(['id' => $this->editingId], [
|
||
|
|
'title' => $validated['title'],
|
||
|
|
'description' => $validated['description'],
|
||
|
|
'status' => $validated['status'],
|
||
|
|
'project_id' => $validated['projectId'],
|
||
|
|
'priority' => $validated['priority'],
|
||
|
|
'due_at' => $validated['dueAt'] ?: null,
|
||
|
|
'estimated_hours' => $validated['estimatedHours'] ?: null,
|
||
|
|
'actual_hours' => $validated['actualHours'] ?: null,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$this->resetForm();
|
||
|
|
$this->showForm = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function delete(int $id): void
|
||
|
|
{
|
||
|
|
Task::findOrFail($id)->delete();
|
||
|
|
}
|
||
|
|
|
||
|
|
public function changeStatus(int $id, string $newStatus): void
|
||
|
|
{
|
||
|
|
if (!in_array($newStatus, Task::STATUSES)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$task = Task::findOrFail($id);
|
||
|
|
$task->update(['status' => $newStatus]);
|
||
|
|
|
||
|
|
// Auto-set timestamps based on status
|
||
|
|
if ($newStatus === 'in_progress' && !$task->started_at) {
|
||
|
|
$task->update(['started_at' => now()]);
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($newStatus === 'done' && !$task->completed_at) {
|
||
|
|
$task->update(['completed_at' => now()]);
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($newStatus !== 'in_progress' && $newStatus !== 'done') {
|
||
|
|
$task->update(['started_at' => null, 'completed_at' => null]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public function clearFilters(): void
|
||
|
|
{
|
||
|
|
$this->search = '';
|
||
|
|
$this->statusFilter = '';
|
||
|
|
$this->priorityFilter = '';
|
||
|
|
$this->projectFilter = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function resetForm(): void
|
||
|
|
{
|
||
|
|
$this->editingId = null;
|
||
|
|
$this->title = '';
|
||
|
|
$this->description = null;
|
||
|
|
$this->status = 'todo';
|
||
|
|
$this->projectId = null;
|
||
|
|
$this->priority = 0;
|
||
|
|
$this->dueAt = null;
|
||
|
|
$this->estimatedHours = null;
|
||
|
|
$this->actualHours = null;
|
||
|
|
$this->resetValidation();
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getStatusColor(string $status): string
|
||
|
|
{
|
||
|
|
return match ($status) {
|
||
|
|
'todo' => 'bg-gray-100 text-gray-800',
|
||
|
|
'in_progress' => 'bg-blue-100 text-blue-800',
|
||
|
|
'done' => 'bg-green-100 text-green-800',
|
||
|
|
'blocked' => 'bg-red-100 text-red-800',
|
||
|
|
'waiting_review' => 'bg-yellow-100 text-yellow-800',
|
||
|
|
default => 'bg-gray-100 text-gray-800',
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getPriorityLabel(int $priority): string
|
||
|
|
{
|
||
|
|
if ($priority === 0) return 'None';
|
||
|
|
if ($priority <= 3) return 'Low';
|
||
|
|
if ($priority <= 6) return 'Medium';
|
||
|
|
if ($priority <= 8) return 'High';
|
||
|
|
return 'Critical';
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getCommentsCount(int $taskId): int
|
||
|
|
{
|
||
|
|
return \App\Models\TaskComment::where('task_id', $taskId)->count();
|
||
|
|
}
|
||
|
|
|
||
|
|
public function startTracking(int $id): void
|
||
|
|
{
|
||
|
|
$task = Task::findOrFail($id);
|
||
|
|
if ($task->status !== 'in_progress') {
|
||
|
|
$task->update(['status' => 'in_progress', 'started_at' => now()]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public function stopTracking(int $id): void
|
||
|
|
{
|
||
|
|
// This would need more complex time tracking logic
|
||
|
|
// For now, just mark as done
|
||
|
|
$task = Task::findOrFail($id);
|
||
|
|
$task->update(['status' => 'done', 'completed_at' => now()]);
|
||
|
|
}
|
||
|
|
}
|