Commit the Projects/Tasks/Ideas/TaskComments feature

This was already implemented, migrated, and running live, but had
never been committed. Bringing it into git: Project/Task/Idea/
TaskComment models, their JSON API (CRUD + status transitions,
task comments, idea<->project linking), Livewire UI pages, and the
nav/route wiring for them. All 42 of its own tests pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
m105
2026-08-06 20:41:57 +03:00
co-authored by Claude Sonnet 5
parent 6ff7f78c9c
commit a4ff6c0ccb
39 changed files with 3411 additions and 0 deletions
+126
View File
@@ -0,0 +1,126 @@
<?php
namespace App\Livewire\Projects;
use App\Models\Project;
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 $name = '';
public ?string $description = null;
public string $status = 'active';
public ?string $dueAt = null;
public string $search = '';
public string $statusFilter = '';
public function render()
{
$query = Project::query();
if ($this->search) {
$query->where('name', 'like', '%' . $this->search . '%')
->orWhere('description', 'like', '%' . $this->search . '%');
}
if ($this->statusFilter) {
$query->where('status', $this->statusFilter);
}
$projects = $query->orderBy('name')->get();
return view('livewire.projects.index', [
'projects' => $projects,
'statuses' => Project::STATUSES,
'totalProjects' => Project::count(),
'activeProjects' => Project::active()->count(),
'completedProjects' => Project::completed()->count(),
]);
}
public function create(): void
{
$this->resetForm();
$this->showForm = true;
}
public function edit(int $id): void
{
$project = Project::findOrFail($id);
$this->editingId = $project->id;
$this->name = $project->name;
$this->description = $project->description;
$this->status = $project->status;
$this->dueAt = $project->due_at?->format('Y-m-d\TH:i') ?? '';
$this->showForm = true;
}
public function save(): void
{
$validated = $this->validate([
'name' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string'],
'status' => ['required', 'string', 'in:' . implode(',', Project::STATUSES)],
'dueAt' => ['nullable', 'date'],
]);
Project::updateOrCreate(['id' => $this->editingId], [
'name' => $validated['name'],
'description' => $validated['description'],
'status' => $validated['status'],
'due_at' => $validated['dueAt'] ?: null,
]);
$this->resetForm();
$this->showForm = false;
}
public function delete(int $id): void
{
Project::findOrFail($id)->delete();
}
public function clearFilters(): void
{
$this->search = '';
$this->statusFilter = '';
}
public function resetForm(): void
{
$this->editingId = null;
$this->name = '';
$this->description = null;
$this->status = 'active';
$this->dueAt = null;
$this->resetValidation();
}
public function getTasksCount(int $projectId): int
{
return \App\Models\Task::where('project_id', $projectId)->count();
}
public function getStatusColor(string $status): string
{
return match ($status) {
'active' => 'bg-green-100 text-green-800',
'completed' => 'bg-blue-100 text-blue-800',
'archived' => 'bg-gray-100 text-gray-800',
'on_hold' => 'bg-yellow-100 text-yellow-800',
'planned' => 'bg-purple-100 text-purple-800',
default => 'bg-gray-100 text-gray-800',
};
}
}