mirror of https://github.com/Devoalda/LaDo.git
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:
parent
fcbd9e21ae
commit
9630a250e3
|
@ -2,7 +2,10 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\Project\StoreProjectRequest;
|
||||
use App\Http\Requests\Project\UpdateProjectRequest;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\User;
|
||||
|
||||
|
@ -39,18 +42,16 @@ class ProjectController extends Controller
|
|||
/**
|
||||
* Store a newly created project in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
public function store(StoreProjectRequest $request): RedirectResponse
|
||||
{
|
||||
$user = User::find(auth()->user()->id);
|
||||
|
||||
$data = $request->validate([
|
||||
'name' => 'required|unique:projects|max:255',
|
||||
'description' => 'nullable|max:255',
|
||||
]);
|
||||
$data = $request->validated();
|
||||
|
||||
$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)
|
||||
{
|
||||
//
|
||||
return view('project.edit', [
|
||||
'project' => $project,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
$projects = $user->projects;
|
||||
$project = $projects->find($project->id);
|
||||
|
||||
$data = $request->validate([
|
||||
'name' => 'required|unique:projects|max:255',
|
||||
'description' => 'nullable|max:255',
|
||||
]);
|
||||
$data = $request->validated();
|
||||
|
||||
$project->update($data);
|
||||
|
||||
return back()->with('status', 'Project updated!');
|
||||
return back()->with('info', 'Project updated!');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,6 +4,8 @@ namespace App\Http\Controllers;
|
|||
|
||||
//use App\Http\Requests\StoreTodoRequest;
|
||||
//use App\Http\Requests\UpdateTodoRequest;
|
||||
use App\Http\Requests\Project\StoreTodoRequest;
|
||||
use App\Http\Requests\Project\UpdateTodoRequest;
|
||||
use App\Models\Todo;
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
|
@ -16,6 +18,7 @@ use Illuminate\Support\Carbon;
|
|||
class ProjectTodoController extends Controller
|
||||
{
|
||||
private string $timezone = 'Asia/Singapore';
|
||||
|
||||
/**
|
||||
* Display a listing of all Todos for a Project.
|
||||
*/
|
||||
|
@ -27,7 +30,7 @@ class ProjectTodoController extends Controller
|
|||
|
||||
$todos = $project->todos;
|
||||
|
||||
return view('project.todo', [
|
||||
return view('todo.index', [
|
||||
'todos' => $todos->whereNull('completed_at')->values(),
|
||||
'completed' => $todos->whereNotNull('completed_at')->values(),
|
||||
'project' => $project,
|
||||
|
@ -39,21 +42,15 @@ class ProjectTodoController extends Controller
|
|||
*/
|
||||
public function create(): Factory|View|Application
|
||||
{
|
||||
return view('project.create');
|
||||
return view('todo.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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([
|
||||
'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',
|
||||
]);
|
||||
$validatedData = $request->validated();
|
||||
|
||||
$due_end = match (true) {
|
||||
isset($validatedData['due_end']) => strtotime($validatedData['due_end']),
|
||||
|
@ -93,7 +90,7 @@ class ProjectTodoController extends Controller
|
|||
$projects = $user->projects;
|
||||
$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;
|
||||
$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)
|
||||
{
|
||||
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']);
|
||||
|
||||
if (Request::filled('completed_at')) {
|
||||
|
@ -147,7 +152,7 @@ class ProjectTodoController extends Controller
|
|||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy($project_id, Todo $todo)
|
||||
public function destroy($project_id, Todo $todo): RedirectResponse
|
||||
{
|
||||
$todo->delete();
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
namespace App\Http\Requests\Project;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreTodoRequest extends FormRequest
|
||||
class StoreProjectRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
* @return array<string, ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string|max:255',
|
||||
];
|
||||
}
|
||||
}
|
|
@ -2,9 +2,10 @@
|
|||
|
||||
namespace App\Http\Requests\Project;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreRequest extends FormRequest
|
||||
class StoreTodoRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
* @return array<string, ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
|
@ -1,10 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
namespace App\Http\Requests\Project;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateTodoRequest extends FormRequest
|
||||
class UpdateProjectRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
* @return array<string, ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string|max:255',
|
||||
];
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ namespace App\Http\Requests\Project;
|
|||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateRequest extends FormRequest
|
||||
class UpdateTodoRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
|
@ -16,6 +16,7 @@ class Project extends Model
|
|||
use HasFactory, UuidTrait;
|
||||
|
||||
protected $dateFormat = 'U';
|
||||
protected $table = 'projects';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
|
|
|
@ -15,6 +15,7 @@ class Todo extends Model
|
|||
use HasFactory, UuidTrait;
|
||||
|
||||
protected $dateFormat = 'U';
|
||||
protected $table = 'todos';
|
||||
|
||||
protected $fillable = [
|
||||
'title',
|
||||
|
|
|
@ -16,6 +16,9 @@ class User extends Authenticatable
|
|||
{
|
||||
use HasApiTokens, HasFactory, Notifiable, UuidTrait;
|
||||
|
||||
protected $dateFormat = 'U';
|
||||
protected $table = 'users';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
|
|
|
@ -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
|
|
@ -27,6 +27,9 @@
|
|||
</header>
|
||||
@endif
|
||||
|
||||
<!-- Flash Messages -->
|
||||
@include('flashView.flashmessages')
|
||||
|
||||
<!-- Page Content -->
|
||||
<main>
|
||||
{{ $slot }}
|
||||
|
|
|
@ -1,26 +1,12 @@
|
|||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||
{{ __('Edit Todo') }}
|
||||
{{ __('Edit Project') }}
|
||||
</h2>
|
||||
</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">
|
||||
<form method="POST" action="{{ route('project.todo.update', [$project, $todo]) }}"
|
||||
id="todo-form">
|
||||
<form method="POST" action="{{ route('project.update', $project) }}" id="project-form">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<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="mb-4">
|
||||
<label for="title" class="block mb-2 font-semibold">Title</label>
|
||||
<input type="text" name="title" id="title" placeholder="Title"
|
||||
<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('title') border-red-500 @enderror"
|
||||
value="{{ old('title', $todo->title) }}">
|
||||
value="{{ old('name', $project->name) }}">
|
||||
@error('title')
|
||||
<div class="text-red-500 mt-2 text-sm">
|
||||
{{ $message }}
|
||||
|
@ -42,7 +28,7 @@
|
|||
<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', $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')
|
||||
<div class="text-red-500 mt-2 text-sm">
|
||||
{{ $message }}
|
||||
|
@ -50,29 +36,6 @@
|
|||
@enderror
|
||||
</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 class="flex justify-end mt-4 space-x-4">
|
||||
|
@ -115,7 +78,7 @@
|
|||
</div>
|
||||
|
||||
<!-- 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">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
|
@ -123,7 +86,7 @@
|
|||
|
||||
<script>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,19 +9,6 @@
|
|||
</h2>
|
||||
</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="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
|
@ -170,11 +157,12 @@
|
|||
</h2>
|
||||
<div class="space-y-4">
|
||||
@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
|
||||
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]) }}"
|
||||
<form action="{{ route('project.todo.update', [$todo->project->id, $todo->id]) }}"
|
||||
method="POST"
|
||||
class="toggle-completed-form">
|
||||
@csrf
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
||||
|
|
@ -6,7 +6,8 @@
|
|||
</x-slot>
|
||||
|
||||
<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
|
||||
@method('PUT')
|
||||
<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">
|
||||
<!-- 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">
|
||||
<svg class="w-5 h-5 mr-2" aria-hidden="true" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
|
@ -96,20 +97,16 @@
|
|||
Update
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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
|
||||
@method('DELETE')
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function confirmDelete() {
|
||||
|
|
|
@ -2,23 +2,14 @@
|
|||
|
||||
<x-slot name="header">
|
||||
<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>
|
||||
</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('todo.store') }}" id="todo-form">
|
||||
<!-- Same width and design as the bottom todo listing -->
|
||||
<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">
|
||||
<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">
|
||||
|
@ -30,7 +21,7 @@
|
|||
<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('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"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
|
@ -51,27 +42,27 @@
|
|||
</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">
|
||||
Not Completed ({{ $todos->count() }})
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
@foreach ($todos as $todo)
|
||||
@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
|
||||
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('todo.update', $todo->id) }}" method="POST"
|
||||
<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' : ''
|
||||
}}>
|
||||
onclick="this.form.submit()" {{ $todo->completed_at ? 'checked' : '' }}>
|
||||
<span class="ml-2 text-sm text-gray-700"></span>
|
||||
</label>
|
||||
</form>
|
||||
|
@ -117,18 +108,20 @@
|
|||
</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">
|
||||
Completed Today
|
||||
({{ $completed->count() }})
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
@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
|
||||
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('todo.update', $todo->id) }}" method="POST">
|
||||
<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">
|
||||
|
|
Loading…
Reference in New Issue