feature(Project Update):

Added:
- Ability to Update Project
- Response Validation classes
- Flash Messages stub

Refactored:
- Views for Todo and Project
This commit is contained in:
devoalda 2023-08-07 09:45:21 +08:00
parent fcbd9e21ae
commit 9630a250e3
17 changed files with 187 additions and 285 deletions

View File

@ -2,7 +2,10 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Requests\Project\StoreProjectRequest;
use App\Http\Requests\Project\UpdateProjectRequest;
use App\Models\Project; use App\Models\Project;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\User; use App\Models\User;
@ -39,18 +42,16 @@ class ProjectController extends Controller
/** /**
* Store a newly created project in storage. * Store a newly created project in storage.
*/ */
public function store(Request $request) public function store(StoreProjectRequest $request): RedirectResponse
{ {
$user = User::find(auth()->user()->id); $user = User::find(auth()->user()->id);
$data = $request->validate([ $data = $request->validated();
'name' => 'required|unique:projects|max:255',
'description' => 'nullable|max:255',
]);
$user->projects()->save(new Project($data)); $user->projects()->save(new Project($data));
return redirect()->route('project.index'); return redirect()->route('project.index')
->with('info', 'Project created!');
} }
/** /**
@ -68,26 +69,25 @@ class ProjectController extends Controller
*/ */
public function edit(Project $project) public function edit(Project $project)
{ {
// return view('project.edit', [
'project' => $project,
]);
} }
/** /**
* Update the specified Project in storage. * Update the specified Project in storage.
*/ */
public function update(Request $request, Project $project) public function update(UpdateProjectRequest $request, Project $project): RedirectResponse
{ {
$user = User::find(auth()->user()->id); $user = User::find(auth()->user()->id);
$projects = $user->projects; $projects = $user->projects;
$project = $projects->find($project->id); $project = $projects->find($project->id);
$data = $request->validate([ $data = $request->validated();
'name' => 'required|unique:projects|max:255',
'description' => 'nullable|max:255',
]);
$project->update($data); $project->update($data);
return back()->with('status', 'Project updated!'); return back()->with('info', 'Project updated!');
} }
/** /**

View File

@ -4,6 +4,8 @@ namespace App\Http\Controllers;
//use App\Http\Requests\StoreTodoRequest; //use App\Http\Requests\StoreTodoRequest;
//use App\Http\Requests\UpdateTodoRequest; //use App\Http\Requests\UpdateTodoRequest;
use App\Http\Requests\Project\StoreTodoRequest;
use App\Http\Requests\Project\UpdateTodoRequest;
use App\Models\Todo; use App\Models\Todo;
use App\Models\User; use App\Models\User;
use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\Factory;
@ -16,6 +18,7 @@ use Illuminate\Support\Carbon;
class ProjectTodoController extends Controller class ProjectTodoController extends Controller
{ {
private string $timezone = 'Asia/Singapore'; private string $timezone = 'Asia/Singapore';
/** /**
* Display a listing of all Todos for a Project. * Display a listing of all Todos for a Project.
*/ */
@ -27,7 +30,7 @@ class ProjectTodoController extends Controller
$todos = $project->todos; $todos = $project->todos;
return view('project.todo', [ return view('todo.index', [
'todos' => $todos->whereNull('completed_at')->values(), 'todos' => $todos->whereNull('completed_at')->values(),
'completed' => $todos->whereNotNull('completed_at')->values(), 'completed' => $todos->whereNotNull('completed_at')->values(),
'project' => $project, 'project' => $project,
@ -39,21 +42,15 @@ class ProjectTodoController extends Controller
*/ */
public function create(): Factory|View|Application public function create(): Factory|View|Application
{ {
return view('project.create'); return view('todo.create');
} }
/** /**
* Store a newly created Todo in storage. * Store a newly created Todo in storage.
*/ */
public function store($project_id, Request $request) public function store($project_id, StoreTodoRequest $request): RedirectResponse
{ {
$validatedData = Request::validate([ $validatedData = $request->validated();
'title' => 'required|max:255',
'description' => 'nullable|max:255',
'due_start' => 'nullable|date',
// due_end is not required, but if it is provided, it must be after due_start
'due_end' => 'nullable|after:due_start',
]);
$due_end = match (true) { $due_end = match (true) {
isset($validatedData['due_end']) => strtotime($validatedData['due_end']), isset($validatedData['due_end']) => strtotime($validatedData['due_end']),
@ -93,7 +90,7 @@ class ProjectTodoController extends Controller
$projects = $user->projects; $projects = $user->projects;
$project = $projects->find($project_id); $project = $projects->find($project_id);
return view('project.todo.show', compact('project', 'todo')); return view('todo.show', compact('project', 'todo'));
} }
/** /**
@ -105,14 +102,22 @@ class ProjectTodoController extends Controller
$projects = $user->projects; $projects = $user->projects;
$project = $projects->find($project_id); $project = $projects->find($project_id);
return view('project.edit', compact('project', 'todo')); // Check if the given todo is in the given project (Reverse find with todo's project_id)
if ($todo->project->id !== $project_id)
return back()->with('error', 'Todo not found in the given project');
return view('todo.edit', compact('project', 'todo'));
} }
/** /**
* Update the specified resource in storage. * Update Todo in storage based on the given project
*/ */
public function update($project_id, Request $request, Todo $todo) public function update($project_id, Request $request, Todo $todo)
{ {
if ($todo->project->id !== $project_id) {
return back()->with('error', 'Todo not found in the given project');
}
$data = Request::only(['title', 'description', 'due_start', 'due_end', 'completed_at']); $data = Request::only(['title', 'description', 'due_start', 'due_end', 'completed_at']);
if (Request::filled('completed_at')) { if (Request::filled('completed_at')) {
@ -147,7 +152,7 @@ class ProjectTodoController extends Controller
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
*/ */
public function destroy($project_id, Todo $todo) public function destroy($project_id, Todo $todo): RedirectResponse
{ {
$todo->delete(); $todo->delete();

View File

@ -1,10 +1,11 @@
<?php <?php
namespace App\Http\Requests; namespace App\Http\Requests\Project;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
class StoreTodoRequest extends FormRequest class StoreProjectRequest extends FormRequest
{ {
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
@ -17,12 +18,13 @@ class StoreTodoRequest extends FormRequest
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string> * @return array<string, ValidationRule|array|string>
*/ */
public function rules(): array public function rules(): array
{ {
return [ return [
// 'name' => 'required|string|max:255',
'description' => 'nullable|string|max:255',
]; ];
} }
} }

View File

@ -2,9 +2,10 @@
namespace App\Http\Requests\Project; namespace App\Http\Requests\Project;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
class StoreRequest extends FormRequest class StoreTodoRequest extends FormRequest
{ {
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
@ -17,7 +18,7 @@ class StoreRequest extends FormRequest
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string> * @return array<string, ValidationRule|array|string>
*/ */
public function rules(): array public function rules(): array
{ {

View File

@ -1,10 +1,11 @@
<?php <?php
namespace App\Http\Requests; namespace App\Http\Requests\Project;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
class UpdateTodoRequest extends FormRequest class UpdateProjectRequest extends FormRequest
{ {
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
@ -17,12 +18,13 @@ class UpdateTodoRequest extends FormRequest
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string> * @return array<string, ValidationRule|array|string>
*/ */
public function rules(): array public function rules(): array
{ {
return [ return [
// 'name' => 'required|string|max:255',
'description' => 'nullable|string|max:255',
]; ];
} }
} }

View File

@ -5,7 +5,7 @@ namespace App\Http\Requests\Project;
use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
class UpdateRequest extends FormRequest class UpdateTodoRequest extends FormRequest
{ {
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.

View File

@ -16,6 +16,7 @@ class Project extends Model
use HasFactory, UuidTrait; use HasFactory, UuidTrait;
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $table = 'projects';
protected $fillable = [ protected $fillable = [
'name', 'name',

View File

@ -15,6 +15,7 @@ class Todo extends Model
use HasFactory, UuidTrait; use HasFactory, UuidTrait;
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $table = 'todos';
protected $fillable = [ protected $fillable = [
'title', 'title',

View File

@ -16,6 +16,9 @@ class User extends Authenticatable
{ {
use HasApiTokens, HasFactory, Notifiable, UuidTrait; use HasApiTokens, HasFactory, Notifiable, UuidTrait;
protected $dateFormat = 'U';
protected $table = 'users';
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
* *

View File

@ -0,0 +1,43 @@
@if ($message = Session::get('success'))
<div class="bg-green-500 text-white p-4 rounded-lg mb-6 text-center">
<strong>{{ $message }}</strong>
</div>
@endif
@if ($message = Session::get('error'))
<div class="bg-red-500 text-white p-4 rounded-lg mb-6 text-center">
<strong>{{ $message }}</strong>
</div>
@endif
@if ($message = Session::get('warning'))
<div class="bg-yellow-500 text-white p-4 rounded-lg mb-6 text-center">
<strong>{{ $message }}</strong>
</div>
@endif
@if ($message = Session::get('info'))
<div class="bg-blue-500 text-white p-4 rounded-lg mb-6 text-center">
<strong>{{ $message }}</strong>
</div>
@endif
@if ($message = Session::get('status'))
<div class="bg-blue-500 text-white p-4 rounded-lg mb-6 text-center">
<strong>{{ $message }}</strong>
</div>
@endif
@if ($errors->any())
<div class="bg-red-500 text-white p-4 rounded-lg mb-6 text-center">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif

View File

@ -27,6 +27,9 @@
</header> </header>
@endif @endif
<!-- Flash Messages -->
@include('flashView.flashmessages')
<!-- Page Content --> <!-- Page Content -->
<main> <main>
{{ $slot }} {{ $slot }}

View File

@ -1,26 +1,12 @@
<x-app-layout> <x-app-layout>
<x-slot name="header"> <x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight"> <h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Edit Todo') }} {{ __('Edit Project') }}
</h2> </h2>
</x-slot> </x-slot>
<!-- Success/Error Message in green/red session('success') or session('error') -->
@if(session('success'))
<div class="bg-green-500 text-white p-4 rounded-lg mb-6 text-center">
{{ session('success') }}
</div>
@endif
@if(session('error'))
<div class="bg-red-500 text-white p-4 rounded-lg mb-6 text-center">
{{ session('error') }}
</div>
@endif
<div class="py-4"> <div class="py-4">
<form method="POST" action="{{ route('project.todo.update', [$project, $todo]) }}" <form method="POST" action="{{ route('project.update', $project) }}" id="project-form">
id="todo-form">
@csrf @csrf
@method('PUT') @method('PUT')
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
@ -28,10 +14,10 @@
<div class="text-gray-800 dark:text-gray-100"> <div class="text-gray-800 dark:text-gray-100">
<div class="mb-4"> <div class="mb-4">
<label for="title" class="block mb-2 font-semibold">Title</label> <label for="name" class="block mb-2 font-semibold">Name</label>
<input type="text" name="title" id="title" placeholder="Title" <input type="text" name="name" id="name" placeholder="Name"
class="bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-700 focus:ring-2 focus:ring-blue-500 rounded-lg w-full p-4 @error('title') border-red-500 @enderror" class="bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-700 focus:ring-2 focus:ring-blue-500 rounded-lg w-full p-4 @error('title') border-red-500 @enderror"
value="{{ old('title', $todo->title) }}"> value="{{ old('name', $project->name) }}">
@error('title') @error('title')
<div class="text-red-500 mt-2 text-sm"> <div class="text-red-500 mt-2 text-sm">
{{ $message }} {{ $message }}
@ -42,7 +28,7 @@
<div class="mb-4"> <div class="mb-4">
<label for="description" class="block mb-2 font-semibold">Description</label> <label for="description" class="block mb-2 font-semibold">Description</label>
<textarea name="description" id="description" placeholder="Description" <textarea name="description" id="description" placeholder="Description"
class="bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-700 focus:ring-2 focus:ring-blue-500 rounded-lg w-full p-4 @error('description') border-red-500 @enderror">{{ old('description', $todo->description) }}</textarea> class="bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-700 focus:ring-2 focus:ring-blue-500 rounded-lg w-full p-4 @error('description') border-red-500 @enderror">{{ old('description', $project->description) }}</textarea>
@error('description') @error('description')
<div class="text-red-500 mt-2 text-sm"> <div class="text-red-500 mt-2 text-sm">
{{ $message }} {{ $message }}
@ -50,29 +36,6 @@
@enderror @enderror
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="due_start" class="block mb-2 font-semibold">Due Start</label>
<input type="datetime-local" name="due_start" id="due_start" placeholder="Due Start"
class="bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-700 focus:ring-2 focus:ring-blue-500 rounded-lg w-full p-4 @error('due_start') border-red-500 @enderror"
value="{{ old('due_start', $todo->due_start ? date('Y-m-d\TH:i', $todo->due_start) : '') }}">
@error('due_start')
<div class="text-red-500 mt-2 text-sm">
{{ $message }}
</div>
@enderror
</div>
<div>
<label for="due_end" class="block mb-2 font-semibold">Due End</label>
<input type="datetime-local" name="due_end" id="due_end" placeholder="Due End"
class="bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-700 focus:ring-2 focus:ring-blue-500 rounded-lg w-full p-4 @error('due_end') border-red-500 @enderror"
value="{{ old('due_end', $todo->due_end ? date('Y-m-d\TH:i', $todo->due_end) : '') }}">
@error('due_end')
<div class="text-red-900 mt-2 text-sm">
{{ $message }}
</div>
@enderror
</div>
</div> </div>
<div class="flex justify-end mt-4 space-x-4"> <div class="flex justify-end mt-4 space-x-4">
@ -115,7 +78,7 @@
</div> </div>
<!-- Hidden Delete Form --> <!-- Hidden Delete Form -->
<form id="delete-form" action="{{ route('project.todo.destroy', [$project, $todo]) }}" method="POST" <form id="delete-form" action="{{ route('project.destroy', $project) }}" method="POST"
class="hidden"> class="hidden">
@csrf @csrf
@method('DELETE') @method('DELETE')
@ -123,7 +86,7 @@
<script> <script>
function confirmDelete() { function confirmDelete() {
if (confirm('Are you sure you want to delete this item?')) { if (confirm('Are you sure you want to delete this Project?')) {
document.getElementById('delete-form').submit(); document.getElementById('delete-form').submit();
} }
} }

View File

@ -9,19 +9,6 @@
</h2> </h2>
</x-slot> </x-slot>
<!-- Success/Error Message in green/red session('success') or session('error') -->
@if(session('success'))
<div class="bg-green-500 text-white p-4 rounded-lg mb-6 text-center">
{{ session('success') }}
</div>
@endif
@if(session('error'))
<div class="bg-red-500 text-white p-4 rounded-lg mb-6 text-center">
{{ session('error') }}
</div>
@endif
<div class="py-4"> <div class="py-4">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
@ -170,11 +157,12 @@
</h2> </h2>
<div class="space-y-4"> <div class="space-y-4">
@foreach ($completed as $todo) @foreach ($completed as $todo)
<a href="{{ route('project.todo.edit', [$project->id, $todo->id]) }}" class="block"> <a href="{{ route('project.todo.edit', [$todo->project->id, $todo->id]) }}"
class="block">
<div <div
class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6 flex items-center justify-between"> class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6 flex items-center justify-between">
<div class="flex items-center"> <div class="flex items-center">
<form action="{{ route('project.todo.update', [$project->id, $todo->id]) }}" <form action="{{ route('project.todo.update', [$todo->project->id, $todo->id]) }}"
method="POST" method="POST"
class="toggle-completed-form"> class="toggle-completed-form">
@csrf @csrf

View File

@ -1,155 +0,0 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Todo List for Project:') }} <span class="font-bold text-blue-500 dark:text-blue-400"
>{{ $project->name }}</span>
</h2>
</x-slot>
<!-- Flash data for success and error messages -->
@if(session()->has('success'))
<div class="bg-green-500 text-white p-4 rounded-lg mb-6 text-center">
{{ session('success') }}
</div>
@elseif(session()->has('error'))
<div class="bg-red-500 text-white p-4 rounded-lg mb-6 text-center">
{{ session('error') }}
</div>
@endif
<div class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6 mb-3">
<form method="POST" action="{{ route('project.todo.store', $project->id) }}">
@csrf
<div class="text-gray-800 dark:text-gray-100">
<div class="mb-4 flex items-center">
<input type="text" name="title" id="title" placeholder="Your Awesome Todo"
class="bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-700 focus:ring-2 focus:ring-blue-500 text-gray-900 dark:text-gray-100 rounded-lg w-full p-4 @error('title') border-red-500 @enderror"
value="{{ old('title') }}" required autofocus>
<!-- Plus button to submit the form or go to todo.create if title is empty -->
<button type="submit"
class="ml-2 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
onclick="event.preventDefault();
document.getElementById('title').value.trim() === '' ? window.location.href = '{{ route('project.todo.create', $project->id) }}' : this.form.submit();">
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
class="w-6 h-full inline-block">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
</svg>
</button>
</div>
@error('title')
<div class="text-red-500 mt-2 text-sm">
{{ $message }}
</div>
@enderror
</div>
</form>
</div>
<div class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6 mb-3">
<h2 class="text-2xl font-semibold mb-4 text-red-500 dark:text-red-400">
Not Completed ({{ $todos->count() }})
</h2>
<div class="space-y-4">
@foreach ($todos as $todo)
@if (!$todo->completed_at)
<a href="{{ route('project.todo.edit', [$project->id, $todo->id]) }}" class="block">:
<div
class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6 flex items-center justify-between">
<div class="flex items-center">
<!-- Checkbox to toggle completed at -->
<form action="{{ route('project.todo.update', [$project->id, $todo->id]) }}"
method="POST"
class="toggle-completed-form">
@csrf
@method('PUT')
<label class="flex items-center cursor-pointer">
<input type="checkbox" name="completed_at"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
onclick="this.form.submit()" {{ $todo->completed_at ? 'checked' : '' }}>
<span class="ml-2 text-sm text-gray-700"></span>
</label>
</form>
<span class="ml-2 text-xl font-bold text-gray-800 dark:text-gray-100">{{ $todo->title }}</span>
</div>
<div>
@php
if ($todo->due_start && $todo->due_end) {
$due = \Carbon\Carbon::parse($todo->due_end);
$now = now();
$timeRemaining = $due->isFuture() ? $now->diffForHumans($due, true) : $due->diffForHumans($now,
true);
} elseif ($todo->due_start) {
$due = \Carbon\Carbon::parse($todo->due_start);
$now = now();
$timeRemaining = $due->isFuture() ? $now->diffForHumans($due, true) : $due->diffForHumans($now,
true);
} elseif ($todo->due_end) {
$due = \Carbon\Carbon::parse($todo->due_end);
$now = now();
$timeRemaining = $due->isFuture() ? $now->diffForHumans($due, true) : $due->diffForHumans($now,
true);
} else {
// If there is no due_start or due_end, set $timeRemaining to null
$timeRemaining = null;
}
@endphp
@if ($timeRemaining !== null)
@if ($due->isFuture())
<p class="text-sm text-green-600">{{ $timeRemaining }} remaining</p>
@else
<p class="text-sm text-red-600">{{ $timeRemaining }} ago</p>
@endif
@endif
</div>
</div>
</a>
@endif
@endforeach
</div>
</div>
<div class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6">
<h2 class="text-2xl font-semibold mb-4 text-green-500 dark:text-green-400">
Completed Today
({{ $completed->count() }})
</h2>
<div class="space-y-4">
@foreach ($completed as $todo)
<a href="{{ route('project.todo.edit', [$project->id, $todo->id]) }}" class="block">
<div
class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6 flex items-center justify-between">
<div class="flex items-center">
<form action="{{ route('project.todo.update', [$project->id, $todo->id]) }}"
method="POST"
class="toggle-completed-form">
@csrf
@method('PUT')
<label class="flex items-center cursor-pointer">
<input type="checkbox" name="completed_at" id="completed_at_{{ $todo->id }}"
class="w-4 h-4 text-green-600 bg-gray-100 border-gray-300 rounded focus:ring-green-500 dark:focus:ring-green-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
onchange="this.form.submit()" {{ $todo->completed_at ? 'checked' : ''
}}>
<span class="ml-2 text-sm text-gray-700"></span>
</label>
</form>
<span
class="ml-2 text-2xl font-bold text-gray-800 dark:text-gray-100">
{{ $todo->title }}
</span>
</div>
</div>
</a>
@endforeach
</div>
</div>
</x-app-layout>

View File

@ -0,0 +1,55 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Create Project') }}
</h2>
</x-slot>
<!-- Create Project Form (Name, Description) -->
<div class="py-4">
<form method="POST" action="{{ route('project.store') }}" id="project-form">
@csrf
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6">
<div class="text-gray-800 dark:text-gray-100">
<div class="mb-4">
<label for="name" class="block mb-2 font-semibold">Name</label>
<input type="text" name="name" id="name" placeholder="Name"
class="bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-700 focus:ring-2 focus:ring-blue-500 rounded-lg w-full p-4 @error('name') border-red-500 @enderror"
value="{{ old('name') }}">
@error('name')
<div class="text-red-500 mt-2 text-sm">
{{ $message }}
</div>
@enderror
</div>
<div class="mb-4">
<label for="description" class="block mb-2 font-semibold">Description</label>
<textarea name="description" id="description" placeholder="Description"
class="bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-700 focus:ring-2 focus:ring-blue-500 rounded-lg w-full p-4 @error('description') border-red-500 @enderror">{{ old('description') }}</textarea>
@error('description')
<div class="text-red-500 mt-2 text-sm">
{{ $message }}
</div>
@enderror
</div>
<div>
<button type="submit"
class="bg-blue-500 hover:bg-blue-600 text-white font-semibold px-4 py-2 rounded-lg">
Create Project
</button>
</div>
</div>
</div>
</div>
</form>
</div>
</x-app-layout>

View File

@ -6,7 +6,8 @@
</x-slot> </x-slot>
<div class="py-4"> <div class="py-4">
<form method="POST" action="{{ route('todo.update', $todo) }}" id="todo-form"> <form method="POST" action="{{ route('project.todo.update', [$project, $todo]) }}"
id="todo-form">
@csrf @csrf
@method('PUT') @method('PUT')
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
@ -63,7 +64,7 @@
<div class="flex justify-end mt-4 space-x-4"> <div class="flex justify-end mt-4 space-x-4">
<!-- Cancel Button (GET request to index route) --> <!-- Cancel Button (GET request to index route) -->
<a href="{{ route('todo.index') }}" <a href="{{ route('project.todo.index', $project) }}"
class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-900 bg-transparent border border-gray-900 rounded-l-lg hover:bg-gray-900 hover:text-white focus:z-10 focus:ring-2 focus:ring-gray-500 focus:bg-gray-900 focus:text-white dark:border-white dark:text-white dark:hover:text-white dark:hover:bg-gray-700 dark:focus:bg-gray-700"> class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-900 bg-transparent border border-gray-900 rounded-l-lg hover:bg-gray-900 hover:text-white focus:z-10 focus:ring-2 focus:ring-gray-500 focus:bg-gray-900 focus:text-white dark:border-white dark:text-white dark:hover:text-white dark:hover:bg-gray-700 dark:focus:bg-gray-700">
<svg class="w-5 h-5 mr-2" aria-hidden="true" fill="none" viewBox="0 0 24 24" <svg class="w-5 h-5 mr-2" aria-hidden="true" fill="none" viewBox="0 0 24 24"
stroke="currentColor"> stroke="currentColor">
@ -96,20 +97,16 @@
Update Update
</button> </button>
</div> </div>
</form>
</div>
</div>
</div>
</div> </div>
</form>
<!-- Hidden Delete Form --> <!-- Hidden Delete Form -->
<form id="delete-form" action="{{ route('todo.destroy', $todo) }}" method="POST"> <form id="delete-form" action="{{ route('project.todo.destroy', [$project, $todo]) }}" method="POST"
class="hidden">
@csrf @csrf
@method('DELETE') @method('DELETE')
</form> </form>
</div>
<script> <script>
function confirmDelete() { function confirmDelete() {

View File

@ -2,23 +2,14 @@
<x-slot name="header"> <x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight"> <h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Todo List') }} {{ __('Todo List for Project:') }} <span class="font-bold text-blue-500 dark:text-blue-400"
>{{ $project->name }}</span>
</h2> </h2>
</x-slot> </x-slot>
<!-- Flash data for success and error messages --> <!-- Same width and design as the bottom todo listing -->
@if(session()->has('success')) <div class="max-w-7xl mx-auto sm:px-6 lg:px-8 bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6 mt-4">
<div class="bg-green-500 text-white p-4 rounded-lg mb-6 text-center"> <form method="POST" action="{{ route('project.todo.store', $project->id) }}">
{{ session('success') }}
</div>
@elseif(session()->has('error'))
<div class="bg-red-500 text-white p-4 rounded-lg mb-6 text-center">
{{ session('error') }}
</div>
@endif
<div class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6 mb-3">
<form method="POST" action="{{ route('todo.store') }}" id="todo-form">
@csrf @csrf
<div class="text-gray-800 dark:text-gray-100"> <div class="text-gray-800 dark:text-gray-100">
<div class="mb-4 flex items-center"> <div class="mb-4 flex items-center">
@ -30,7 +21,7 @@
<button type="submit" <button type="submit"
class="ml-2 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" class="ml-2 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
onclick="event.preventDefault(); onclick="event.preventDefault();
document.getElementById('title').value.trim() === '' ? window.location.href = '{{ route('todo.create') }}' : document.getElementById('todo-form').submit();"> document.getElementById('title').value.trim() === '' ? window.location.href = '{{ route('project.todo.create', $project->id) }}' : this.form.submit();">
<svg xmlns="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@ -51,27 +42,27 @@
</div> </div>
<div class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6 mb-3"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8 bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6 mt-4">
<h2 class="text-2xl font-semibold mb-4 text-red-500 dark:text-red-400"> <h2 class="text-2xl font-semibold mb-4 text-red-500 dark:text-red-400">
Not Completed ({{ $todos->count() }}) Not Completed ({{ $todos->count() }})
</h2> </h2>
<div class="space-y-4"> <div class="space-y-4">
@foreach ($todos as $todo) @foreach ($todos as $todo)
@if (!$todo->completed_at) @if (!$todo->completed_at)
<a href="{{ route('todo.edit', $todo->id) }}" class="block"> <a href="{{ route('project.todo.edit', [$project->id, $todo->id]) }}" class="block">:
<div <div
class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6 flex items-center justify-between"> class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6 flex items-center justify-between">
<div class="flex items-center"> <div class="flex items-center">
<!-- Checkbox to toggle completed at --> <!-- Checkbox to toggle completed at -->
<form action="{{ route('todo.update', $todo->id) }}" method="POST" <form action="{{ route('project.todo.update', [$project->id, $todo->id]) }}"
method="POST"
class="toggle-completed-form"> class="toggle-completed-form">
@csrf @csrf
@method('PUT') @method('PUT')
<label class="flex items-center cursor-pointer"> <label class="flex items-center cursor-pointer">
<input type="checkbox" name="completed_at" <input type="checkbox" name="completed_at"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
onclick="this.form.submit()" {{ $todo->completed_at ? 'checked' : '' onclick="this.form.submit()" {{ $todo->completed_at ? 'checked' : '' }}>
}}>
<span class="ml-2 text-sm text-gray-700"></span> <span class="ml-2 text-sm text-gray-700"></span>
</label> </label>
</form> </form>
@ -117,18 +108,20 @@
</div> </div>
<div class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8 bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6 mt-4">
<h2 class="text-2xl font-semibold mb-4 text-green-500 dark:text-green-400"> <h2 class="text-2xl font-semibold mb-4 text-green-500 dark:text-green-400">
Completed Today Completed Today
({{ $completed->count() }}) ({{ $completed->count() }})
</h2> </h2>
<div class="space-y-4"> <div class="space-y-4">
@foreach ($completed as $todo) @foreach ($completed as $todo)
<a href="{{ route('todo.edit', $todo->id) }}" class="block"> <a href="{{ route('project.todo.edit', [$project->id, $todo->id]) }}" class="block">
<div <div
class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6 flex items-center justify-between"> class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6 flex items-center justify-between">
<div class="flex items-center"> <div class="flex items-center">
<form action="{{ route('todo.update', $todo->id) }}" method="POST"> <form action="{{ route('project.todo.update', [$project->id, $todo->id]) }}"
method="POST"
class="toggle-completed-form">
@csrf @csrf
@method('PUT') @method('PUT')
<label class="flex items-center cursor-pointer"> <label class="flex items-center cursor-pointer">