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>
127 lines
3.3 KiB
PHP
127 lines
3.3 KiB
PHP
<?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',
|
|
};
|
|
}
|
|
}
|