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:
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreIdeaRequest;
|
||||
use App\Http\Requests\UpdateIdeaRequest;
|
||||
use App\Http\Resources\IdeaResource;
|
||||
use App\Models\Idea;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class IdeaController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Idea::query();
|
||||
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->string('status'));
|
||||
}
|
||||
|
||||
if ($request->filled('category')) {
|
||||
$query->where('category', $request->string('category'));
|
||||
}
|
||||
|
||||
if ($request->filled('project_id')) {
|
||||
$query->where('project_id', $request->integer('project_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$query->search($request->string('search'));
|
||||
}
|
||||
|
||||
if ($request->filled('unlinked')) {
|
||||
$query->unlinked();
|
||||
}
|
||||
|
||||
if ($request->filled('priority')) {
|
||||
$query->where('priority', $request->integer('priority'));
|
||||
}
|
||||
|
||||
$ideas = $query->orderBy('priority', 'desc')->orderBy('created_at', 'desc')->get();
|
||||
|
||||
// Eager load relationships if requested
|
||||
if ($request->boolean('with_project')) {
|
||||
$ideas->load(['project']);
|
||||
}
|
||||
|
||||
if ($request->boolean('with_user')) {
|
||||
$ideas->load(['user']);
|
||||
}
|
||||
|
||||
return IdeaResource::collection($ideas);
|
||||
}
|
||||
|
||||
public function store(StoreIdeaRequest $request)
|
||||
{
|
||||
$idea = Idea::create($request->validated());
|
||||
|
||||
return (new IdeaResource($idea))
|
||||
->response()
|
||||
->setStatusCode(201);
|
||||
}
|
||||
|
||||
public function show(Request $request, Idea $idea)
|
||||
{
|
||||
if ($request->boolean('with_project')) {
|
||||
$idea->load(['project']);
|
||||
}
|
||||
|
||||
if ($request->boolean('with_user')) {
|
||||
$idea->load(['user']);
|
||||
}
|
||||
|
||||
return new IdeaResource($idea);
|
||||
}
|
||||
|
||||
public function update(UpdateIdeaRequest $request, Idea $idea)
|
||||
{
|
||||
$idea->update($request->validated());
|
||||
|
||||
return new IdeaResource($idea);
|
||||
}
|
||||
|
||||
public function destroy(Idea $idea)
|
||||
{
|
||||
$idea->delete();
|
||||
|
||||
return response()->noContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change idea status.
|
||||
*/
|
||||
public function changeStatus(Request $request, Idea $idea)
|
||||
{
|
||||
$request->validate([
|
||||
'status' => ['required', 'string', 'in:' . implode(',', Idea::STATUSES)],
|
||||
]);
|
||||
|
||||
$idea->update(['status' => $request->string('status')]);
|
||||
|
||||
return new IdeaResource($idea);
|
||||
}
|
||||
|
||||
/**
|
||||
* Link an idea to a project.
|
||||
*/
|
||||
public function linkToProject(Request $request, Idea $idea)
|
||||
{
|
||||
$request->validate([
|
||||
'project_id' => ['required', 'integer', 'exists:projects,id'],
|
||||
]);
|
||||
|
||||
$idea->update(['project_id' => $request->integer('project_id')]);
|
||||
|
||||
return new IdeaResource($idea->load(['project']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink an idea from a project.
|
||||
*/
|
||||
public function unlinkFromProject(Idea $idea)
|
||||
{
|
||||
$idea->update(['project_id' => null]);
|
||||
|
||||
return new IdeaResource($idea);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreProjectRequest;
|
||||
use App\Http\Requests\UpdateProjectRequest;
|
||||
use App\Http\Resources\IdeaResource;
|
||||
use App\Http\Resources\ProjectResource;
|
||||
use App\Http\Resources\TaskResource;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ProjectController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Project::query();
|
||||
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->string('status'));
|
||||
}
|
||||
|
||||
if ($request->filled('name')) {
|
||||
$query->where('name', 'like', '%' . $request->string('name') . '%');
|
||||
}
|
||||
|
||||
$projects = $query->orderBy('name')->get();
|
||||
|
||||
// Eager load tasks if ?with_tasks=1
|
||||
if ($request->boolean('with_tasks')) {
|
||||
$projects->load(['tasks.comments']);
|
||||
}
|
||||
|
||||
return ProjectResource::collection($projects);
|
||||
}
|
||||
|
||||
public function store(StoreProjectRequest $request)
|
||||
{
|
||||
$project = Project::create($request->validated());
|
||||
|
||||
return (new ProjectResource($project))
|
||||
->response()
|
||||
->setStatusCode(201);
|
||||
}
|
||||
|
||||
public function show(Request $request, Project $project)
|
||||
{
|
||||
if ($request->boolean('with_tasks')) {
|
||||
$project->load(['tasks.comments']);
|
||||
}
|
||||
|
||||
return new ProjectResource($project);
|
||||
}
|
||||
|
||||
public function update(UpdateProjectRequest $request, Project $project)
|
||||
{
|
||||
$project->update($request->validated());
|
||||
|
||||
return new ProjectResource($project);
|
||||
}
|
||||
|
||||
public function destroy(Project $project)
|
||||
{
|
||||
$project->delete();
|
||||
|
||||
return response()->noContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tasks for a project.
|
||||
*/
|
||||
public function tasks(Request $request, Project $project)
|
||||
{
|
||||
$query = $project->tasks()->with(['comments']);
|
||||
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->string('status'));
|
||||
}
|
||||
|
||||
if ($request->filled('priority')) {
|
||||
$query->where('priority', $request->integer('priority'));
|
||||
}
|
||||
|
||||
$tasks = $query->orderBy('priority', 'desc')->orderBy('created_at')->get();
|
||||
|
||||
return TaskResource::collection($tasks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ideas for a project.
|
||||
*/
|
||||
public function ideas(Request $request, Project $project)
|
||||
{
|
||||
$query = $project->ideas()->with(['user']);
|
||||
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->string('status'));
|
||||
}
|
||||
|
||||
if ($request->filled('category')) {
|
||||
$query->where('category', $request->string('category'));
|
||||
}
|
||||
|
||||
if ($request->filled('priority')) {
|
||||
$query->where('priority', $request->integer('priority'));
|
||||
}
|
||||
|
||||
$ideas = $query->orderBy('priority', 'desc')->orderBy('created_at', 'desc')->get();
|
||||
|
||||
return IdeaResource::collection($ideas);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreTaskCommentRequest;
|
||||
use App\Http\Resources\TaskCommentResource;
|
||||
use App\Models\Task;
|
||||
use App\Models\TaskComment;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TaskCommentController extends Controller
|
||||
{
|
||||
/**
|
||||
* Get all comments for a task.
|
||||
*/
|
||||
public function index(Request $request, Task $task)
|
||||
{
|
||||
$comments = $task->comments()->orderBy('created_at')->get();
|
||||
|
||||
return TaskCommentResource::collection($comments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new comment for a task.
|
||||
*/
|
||||
public function store(StoreTaskCommentRequest $request, Task $task)
|
||||
{
|
||||
$comment = $task->comments()->create($request->validated());
|
||||
|
||||
return (new TaskCommentResource($comment))
|
||||
->response()
|
||||
->setStatusCode(201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a comment.
|
||||
*/
|
||||
public function update(Request $request, TaskComment $comment)
|
||||
{
|
||||
$request->validate([
|
||||
'body' => ['required', 'string'],
|
||||
]);
|
||||
|
||||
$comment->update([
|
||||
'body' => $request->string('body'),
|
||||
]);
|
||||
|
||||
return new TaskCommentResource($comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a single comment.
|
||||
*/
|
||||
public function show(TaskComment $comment)
|
||||
{
|
||||
return new TaskCommentResource($comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a comment.
|
||||
*/
|
||||
public function destroy(TaskComment $comment)
|
||||
{
|
||||
$comment->delete();
|
||||
|
||||
return response()->noContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreTaskRequest;
|
||||
use App\Http\Requests\UpdateTaskRequest;
|
||||
use App\Http\Resources\TaskResource;
|
||||
use App\Models\Task;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TaskController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Task::with(['project', 'comments']);
|
||||
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->string('status'));
|
||||
}
|
||||
|
||||
if ($request->filled('project_id')) {
|
||||
$query->where('project_id', $request->integer('project_id'));
|
||||
}
|
||||
|
||||
if ($request->filled('priority')) {
|
||||
$query->where('priority', $request->integer('priority'));
|
||||
}
|
||||
|
||||
if ($request->boolean('due_soon')) {
|
||||
$query->dueSoon($request->integer('days', 3));
|
||||
}
|
||||
|
||||
if ($request->boolean('overdue')) {
|
||||
$query->overdue();
|
||||
}
|
||||
|
||||
$tasks = $query->orderBy('priority', 'desc')->orderBy('created_at')->get();
|
||||
|
||||
return TaskResource::collection($tasks);
|
||||
}
|
||||
|
||||
public function store(StoreTaskRequest $request)
|
||||
{
|
||||
$task = Task::create($request->validated());
|
||||
|
||||
return (new TaskResource($task))
|
||||
->response()
|
||||
->setStatusCode(201);
|
||||
}
|
||||
|
||||
public function show(Request $request, Task $task)
|
||||
{
|
||||
$task->load(['project', 'comments']);
|
||||
|
||||
return new TaskResource($task);
|
||||
}
|
||||
|
||||
public function update(UpdateTaskRequest $request, Task $task)
|
||||
{
|
||||
// Handle status transitions
|
||||
$validated = $request->validated();
|
||||
|
||||
// Auto-set timestamps based on status
|
||||
if (isset($validated['status'])) {
|
||||
if ($validated['status'] === 'in_progress' && ! $task->started_at) {
|
||||
$validated['started_at'] = now();
|
||||
}
|
||||
if (in_array($validated['status'], ['done', 'completed']) && ! $task->completed_at) {
|
||||
$validated['completed_at'] = now();
|
||||
}
|
||||
if ($validated['status'] !== 'in_progress') {
|
||||
$validated['started_at'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
$task->update($validated);
|
||||
|
||||
return new TaskResource($task->fresh(['project', 'comments']));
|
||||
}
|
||||
|
||||
public function destroy(Task $task)
|
||||
{
|
||||
$task->delete();
|
||||
|
||||
return response()->noContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comments for a task.
|
||||
*/
|
||||
public function comments(Request $request, Task $task)
|
||||
{
|
||||
$comments = $task->comments()->orderBy('created_at')->get();
|
||||
|
||||
return TaskCommentResource::collection($comments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change task status.
|
||||
*/
|
||||
public function changeStatus(Request $request, Task $task)
|
||||
{
|
||||
$request->validate([
|
||||
'status' => ['required', 'string', 'in:' . implode(',', Task::STATUSES)],
|
||||
]);
|
||||
|
||||
$status = $request->string('status');
|
||||
|
||||
$updateData = ['status' => $status];
|
||||
|
||||
// Auto-set timestamps
|
||||
if ($status === 'in_progress' && ! $task->started_at) {
|
||||
$updateData['started_at'] = now();
|
||||
}
|
||||
if (in_array($status, ['done', 'completed']) && ! $task->completed_at) {
|
||||
$updateData['completed_at'] = now();
|
||||
}
|
||||
if ($status !== 'in_progress') {
|
||||
$updateData['started_at'] = null;
|
||||
}
|
||||
|
||||
$task->update($updateData);
|
||||
|
||||
return new TaskResource($task->fresh(['project', 'comments']));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user