mirror of https://github.com/Devoalda/LaDo.git
feature(Project):
Modified: - Relationships between tables (forward and backward) - TodoController is deprecated - Views for project index - Navigation for project instead of todo
This commit is contained in:
parent
f7d86460f6
commit
0bda828140
|
@ -4,6 +4,8 @@ namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
|
||||||
class ProjectController extends Controller
|
class ProjectController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -12,8 +14,17 @@ class ProjectController extends Controller
|
||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
|
$user = User::find(auth()->user()->id);
|
||||||
|
$projects = $user->projects;
|
||||||
|
// Aggregate all todos for all projects
|
||||||
|
$todos = $projects->map(function ($project) {
|
||||||
|
return $project->todos;
|
||||||
|
})->flatten();
|
||||||
|
|
||||||
return view('project.index', [
|
return view('project.index', [
|
||||||
'projects' => Project::where('user_id', auth()->user()->id)->get()
|
'projects' => $projects,
|
||||||
|
'todos' => $todos->whereNull('completed_at')->values(),
|
||||||
|
'completed' => $todos->whereNotNull('completed_at')->values(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,11 +33,12 @@ class ProjectController extends Controller
|
||||||
*/
|
*/
|
||||||
public function create()
|
public function create()
|
||||||
{
|
{
|
||||||
//
|
return view('project.create');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store a newly created resource in storage.
|
* TODO: Complete this method
|
||||||
|
* Store a newly created project in storage.
|
||||||
*/
|
*/
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
|
@ -34,6 +46,7 @@ class ProjectController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* TODO: Complete this method (if needed)
|
||||||
* Display the specified resource.
|
* Display the specified resource.
|
||||||
*/
|
*/
|
||||||
public function show(Project $project)
|
public function show(Project $project)
|
||||||
|
@ -42,7 +55,8 @@ class ProjectController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the form for editing the specified resource.
|
* TODO: Complete this method (if needed)
|
||||||
|
* Show the form for editing the specified Project.
|
||||||
*/
|
*/
|
||||||
public function edit(Project $project)
|
public function edit(Project $project)
|
||||||
{
|
{
|
||||||
|
@ -50,7 +64,7 @@ class ProjectController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the specified resource in storage.
|
* Update the specified Project in storage.
|
||||||
*/
|
*/
|
||||||
public function update(Request $request, Project $project)
|
public function update(Request $request, Project $project)
|
||||||
{
|
{
|
||||||
|
@ -58,10 +72,16 @@ class ProjectController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the specified resource from storage.
|
* Remove the specified Project from storage.
|
||||||
*/
|
*/
|
||||||
public function destroy(Project $project)
|
public function destroy(Project $project)
|
||||||
{
|
{
|
||||||
//
|
$user = User::find(auth()->user()->id);
|
||||||
|
$projects = $user->projects;
|
||||||
|
$project = $projects->find($project->id);
|
||||||
|
|
||||||
|
$project->delete();
|
||||||
|
|
||||||
|
return redirect()->route('project.index');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
//use App\Http\Requests\StoreTodoRequest;
|
||||||
|
//use App\Http\Requests\UpdateTodoRequest;
|
||||||
|
use App\Models\Todo;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Contracts\View\Factory;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\Foundation\Application;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Support\Facades\Request;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
|
class ProjectTodoController extends Controller
|
||||||
|
{
|
||||||
|
private string $timezone = 'Asia/Singapore';
|
||||||
|
/**
|
||||||
|
* Display a listing of all Todos for a Project.
|
||||||
|
*/
|
||||||
|
public function index($project_id)
|
||||||
|
{
|
||||||
|
$user = User::find(auth()->user()->id);
|
||||||
|
$projects = $user->projects;
|
||||||
|
$project = $projects->find($project_id);
|
||||||
|
|
||||||
|
$todos = $project->todos;
|
||||||
|
|
||||||
|
return view('project.todo', [
|
||||||
|
'todos' => $todos->whereNull('completed_at')->values(),
|
||||||
|
'completed' => $todos->whereNotNull('completed_at')->values(),
|
||||||
|
'project' => $project,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating a new resource.
|
||||||
|
*/
|
||||||
|
public function create(): Factory|View|Application
|
||||||
|
{
|
||||||
|
return view('project.create');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created Todo in storage.
|
||||||
|
*/
|
||||||
|
public function store($project_id, Request $request)
|
||||||
|
{
|
||||||
|
$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',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$due_end = match (true) {
|
||||||
|
isset($validatedData['due_end']) => strtotime($validatedData['due_end']),
|
||||||
|
isset($validatedData['due_start']) => strtotime($validatedData['due_start']),
|
||||||
|
default => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
$validatedData = array_merge($validatedData, [
|
||||||
|
// due_end = due_start if due_end is not provided and due_start is provided or null
|
||||||
|
'due_end' => $due_end,
|
||||||
|
'user_id' => auth()->user()->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Modify all dates to unix timestamp
|
||||||
|
if (isset($validatedData['due_start']))
|
||||||
|
$validatedData['due_start'] = strtotime($validatedData['due_start']);
|
||||||
|
if (isset($validatedData['due_end']))
|
||||||
|
$validatedData['due_end'] = strtotime($validatedData['due_end']);
|
||||||
|
|
||||||
|
$todo = new Todo($validatedData);
|
||||||
|
|
||||||
|
$user = User::find(auth()->user()->id);
|
||||||
|
$project = $user->projects->find($project_id);
|
||||||
|
$project->todos()->save($todo);
|
||||||
|
|
||||||
|
return redirect()->route('project.todo.index', $project_id)
|
||||||
|
->with('success', 'Todo created successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*/
|
||||||
|
public function show($project_id, Todo $todo)
|
||||||
|
{
|
||||||
|
$user = User::find(auth()->user()->id);
|
||||||
|
$projects = $user->projects;
|
||||||
|
$project = $projects->find($project_id);
|
||||||
|
|
||||||
|
return view('project.todo.show', compact('project', 'todo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified resource.
|
||||||
|
*/
|
||||||
|
public function edit($project_id, Todo $todo)
|
||||||
|
{
|
||||||
|
$user = User::find(auth()->user()->id);
|
||||||
|
$projects = $user->projects;
|
||||||
|
$project = $projects->find($project_id);
|
||||||
|
|
||||||
|
return view('project.edit', compact('project', 'todo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*/
|
||||||
|
public function update($project_id, Request $request, Todo $todo)
|
||||||
|
{
|
||||||
|
$data = Request::only(['title', 'description', 'due_start', 'due_end', 'completed_at']);
|
||||||
|
|
||||||
|
if (Request::filled('completed_at')) {
|
||||||
|
$todo->completed_at = Request::input('completed_at') === 'on' ? strtotime(now($this->timezone)) : null;
|
||||||
|
$todo->save();
|
||||||
|
return back()->with('success', 'Todo updated successfully');
|
||||||
|
} else {
|
||||||
|
// If 'completed_at' is not provided, toggle its value (only if the request is empty)
|
||||||
|
if (empty($data))
|
||||||
|
$todo->completed_at = $todo->completed_at ? null : strtotime(now($this->timezone));
|
||||||
|
else
|
||||||
|
// Continue to update other fields
|
||||||
|
unset($data['completed_at']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Request::filled('due_start')) {
|
||||||
|
$data['due_start'] = strtotime(Request::input('due_start'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Request::filled('due_end')) {
|
||||||
|
$data['due_end'] = strtotime(Request::input('due_end'));
|
||||||
|
} elseif (isset($data['due_start'])) {
|
||||||
|
// If 'due_end' is not provided, set it to 'due_start' value
|
||||||
|
$data['due_end'] = strtotime(Request::input('due_start'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$todo->update($data);
|
||||||
|
|
||||||
|
return redirect()->route('project.todo.index', $project_id)
|
||||||
|
->with('success', 'Todo updated successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*/
|
||||||
|
public function destroy($project_id, Todo $todo)
|
||||||
|
{
|
||||||
|
$todo->delete();
|
||||||
|
|
||||||
|
return redirect()->route('project.todo.index', $project_id)
|
||||||
|
->with('success', 'Todo deleted successfully');
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,13 +26,13 @@ class TodoController extends Controller
|
||||||
|
|
||||||
$projects = $user->projects;
|
$projects = $user->projects;
|
||||||
|
|
||||||
$allTodos = [];
|
$todos = collect();
|
||||||
$allProjects = [];
|
|
||||||
|
|
||||||
foreach ($projects as $project) {
|
foreach ($projects as $project) {
|
||||||
$todos = $project->todos;
|
$todos = $todos->merge($project->todos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return view('todo.index', [
|
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(),
|
||||||
|
@ -81,10 +81,9 @@ class TodoController extends Controller
|
||||||
|
|
||||||
$todo = new Todo($validatedData);
|
$todo = new Todo($validatedData);
|
||||||
|
|
||||||
|
|
||||||
$user = User::find(auth()->user()->id);
|
$user = User::find(auth()->user()->id);
|
||||||
$project = $user->projects->first();
|
$project = $user->projects->first();
|
||||||
$project->todo()->save($todo);
|
$project->todos()->save($todo);
|
||||||
|
|
||||||
// Set flash message
|
// Set flash message
|
||||||
session()->flash('success', 'Todo created successfully.');
|
session()->flash('success', 'Todo created successfully.');
|
||||||
|
@ -97,9 +96,13 @@ class TodoController extends Controller
|
||||||
*/
|
*/
|
||||||
public function show(Todo $todo): Factory|View|Application
|
public function show(Todo $todo): Factory|View|Application
|
||||||
{
|
{
|
||||||
$todo = Todo::where('user_id', auth()->user()->id)
|
$user = User::find(auth()->user()->id);
|
||||||
|
$projects = $user->projects;
|
||||||
|
$todo = $projects->first()
|
||||||
|
->todos
|
||||||
->where('id', $todo->id)
|
->where('id', $todo->id)
|
||||||
->firstOrFail();
|
->first();
|
||||||
|
|
||||||
return view('todo.show', compact('todo'));
|
return view('todo.show', compact('todo'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,9 +111,13 @@ class TodoController extends Controller
|
||||||
*/
|
*/
|
||||||
public function edit(Todo $todo): Factory|View|Application
|
public function edit(Todo $todo): Factory|View|Application
|
||||||
{
|
{
|
||||||
$todo = Todo::where('user_id', auth()->user()->id)
|
$user = User::find(auth()->user()->id);
|
||||||
|
$projects = $user->projects;
|
||||||
|
$todo = $projects->first()
|
||||||
|
->todos
|
||||||
->where('id', $todo->id)
|
->where('id', $todo->id)
|
||||||
->firstOrFail();
|
->first();
|
||||||
|
|
||||||
return view('todo.edit', compact('todo'));
|
return view('todo.edit', compact('todo'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +154,7 @@ class TodoController extends Controller
|
||||||
$data['due_end'] = strtotime(Request::input('due_end'));
|
$data['due_end'] = strtotime(Request::input('due_end'));
|
||||||
} elseif (isset($data['due_start'])) {
|
} elseif (isset($data['due_start'])) {
|
||||||
// If 'due_end' is not provided, set it to 'due_start' value
|
// If 'due_end' is not provided, set it to 'due_start' value
|
||||||
$data['due_end'] = $data['due_start'];
|
$data['due_end'] = strtotime(Request::input('due_start'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$todo->update($data);
|
$todo->update($data);
|
||||||
|
|
|
@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||||
|
|
||||||
class Project extends Model
|
class Project extends Model
|
||||||
{
|
{
|
||||||
|
@ -21,12 +22,12 @@ class Project extends Model
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relationship with Todo model
|
* Relationship with Todo model (one to many)
|
||||||
* @return BelongsToMany
|
* @return BelongsToMany
|
||||||
*/
|
*/
|
||||||
public function todos(): belongsToMany
|
public function todos(): HasManyThrough
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(Todo::class);
|
return $this->hasManyThrough(Todo::class, projectTodo::class, 'project_id', 'id', 'id', 'todo_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function user(): BelongsTo
|
public function user(): BelongsTo
|
||||||
|
|
|
@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use App\Traits\UuidTrait;
|
use App\Traits\UuidTrait;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||||
|
|
||||||
class Todo extends Model
|
class Todo extends Model
|
||||||
|
@ -36,9 +37,9 @@ class Todo extends Model
|
||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function project(): BelongsTo
|
public function project(): HasOneThrough
|
||||||
{
|
{
|
||||||
return $this->belongsTo(projectTodo::class, 'project_todo', 'todo_id', 'project_id');
|
return $this->hasOneThrough(Project::class, ProjectTodo::class, 'todo_id', 'id', 'id', 'project_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,7 @@ namespace App\Models;
|
||||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
use App\Traits\UuidTrait;
|
use App\Traits\UuidTrait;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
@ -46,8 +45,13 @@ class User extends Authenticatable
|
||||||
'password' => 'hashed',
|
'password' => 'hashed',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function projects(): belongsToMany
|
public function projects(): HasManyThrough
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(Project::class);
|
return $this->hasManyThrough(Project::class, projectUser::class, 'user_id', 'id', 'id', 'project_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function todos(): HasManyThrough
|
||||||
|
{
|
||||||
|
return $this->hasManyThrough(Todo::class, projectUser::class, 'user_id', 'id', 'id', 'project_id');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class projectTodo extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'project_todo';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'project_id',
|
||||||
|
'todo_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function project(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Project::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function todo(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Todo::class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class projectUser extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'project_user';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'project_id',
|
||||||
|
'user_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function project(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Project::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Project>
|
||||||
|
*/
|
||||||
|
class ProjectFactory extends Factory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Define the model's default state.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => $this->faker->sentence(3),
|
||||||
|
'description' => $this->faker->sentence(10),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Model>
|
||||||
|
*/
|
||||||
|
class ProjectTodoFactory extends Factory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Define the model's default state.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'project_id' => $this->faker->uuid,
|
||||||
|
'todo_id' => $this->faker->uuid,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Model>
|
||||||
|
*/
|
||||||
|
class ProjectUserFactory extends Factory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Define the model's default state.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'project_id' => $this->faker->uuid,
|
||||||
|
'user_id' => $this->faker->uuid,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,11 +21,9 @@ class TodoFactory extends Factory
|
||||||
'description' => fake()->paragraph(),
|
'description' => fake()->paragraph(),
|
||||||
'due_start' => fake()->unixTime(),
|
'due_start' => fake()->unixTime(),
|
||||||
'due_end' => fake()->unixTime(),
|
'due_end' => fake()->unixTime(),
|
||||||
'user_id' => fake()->uuid(),
|
'completed_at' => fake()->optional()->unixTime(),
|
||||||
'completed_at' => null,
|
|
||||||
'created_at' => fake()->unixTime(),
|
'created_at' => fake()->unixTime(),
|
||||||
'updated_at' => fake()->unixTime(),
|
'updated_at' => fake()->unixTime(),
|
||||||
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,8 @@
|
||||||
|
|
||||||
<!-- Todo List -->
|
<!-- Todo List -->
|
||||||
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
|
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
|
||||||
<x-nav-link :href="route('todo.index')" :active="request()->routeIs('todo.index')">
|
<x-nav-link :href="route('project.index')" :active="request()->routeIs('project.index')">
|
||||||
{{ __('Todo List') }}
|
{{ __('Projects') }}
|
||||||
</x-nav-link>
|
</x-nav-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -45,8 +45,8 @@
|
||||||
{{ __('Profile') }}
|
{{ __('Profile') }}
|
||||||
</x-dropdown-link>
|
</x-dropdown-link>
|
||||||
|
|
||||||
<x-dropdown-link :href="route('todo.index')">
|
<x-dropdown-link :href="route('project.index')">
|
||||||
{{ __('Todo List') }}
|
{{ __('Projects') }}
|
||||||
</x-dropdown-link>
|
</x-dropdown-link>
|
||||||
|
|
||||||
<!-- Authentication -->
|
<!-- Authentication -->
|
||||||
|
@ -82,8 +82,8 @@
|
||||||
{{ __('Dashboard') }}
|
{{ __('Dashboard') }}
|
||||||
</x-responsive-nav-link>
|
</x-responsive-nav-link>
|
||||||
|
|
||||||
<x-responsive-nav-link :href="route('todo.index')" :active="request()->routeIs('todo.index')">
|
<x-responsive-nav-link :href="route('project.index')" :active="request()->routeIs('project.index')">
|
||||||
{{ __('Todo List') }}
|
{{ __('Projects') }}
|
||||||
</x-responsive-nav-link>
|
</x-responsive-nav-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
<x-app-layout>
|
||||||
|
<x-slot name="header">
|
||||||
|
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||||
|
{{ __('Edit Todo') }}
|
||||||
|
</h2>
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
<div class="py-4">
|
||||||
|
<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">
|
||||||
|
<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="title" class="block mb-2 font-semibold">Title</label>
|
||||||
|
<input type="text" name="title" id="title" placeholder="Title"
|
||||||
|
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) }}">
|
||||||
|
@error('title')
|
||||||
|
<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', $todo->description) }}</textarea>
|
||||||
|
@error('description')
|
||||||
|
<div class="text-red-500 mt-2 text-sm">
|
||||||
|
{{ $message }}
|
||||||
|
</div>
|
||||||
|
@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">
|
||||||
|
<!-- Cancel Button (GET request to index route) -->
|
||||||
|
<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">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M15 19l-7-7 7-7"></path>
|
||||||
|
</svg>
|
||||||
|
Cancel
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Delete Button -->
|
||||||
|
<button type="button"
|
||||||
|
class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-red-600 rounded hover:bg-red-800 focus:z-10 focus:ring-2 focus:ring-red-500 border border-grey-900 dark:border-white dark:text-white dark:hover:text-white dark:hover:bg-red-700 dark:focus:bg-red-700"
|
||||||
|
onclick="confirmDelete()">
|
||||||
|
<svg class="w-5 h-5 mr-2" aria-hidden="true" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"></path>
|
||||||
|
</svg>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Update Button -->
|
||||||
|
<button type="submit"
|
||||||
|
class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-green-600 rounded-r-md hover:bg-green-800 focus:z-10 focus:ring-2 focus:ring-green-500 border border-grey-900 dark:border-white dark:text-white dark:hover:text-white dark:hover:bg-green-700 dark:focus:bg-green-700">
|
||||||
|
<svg class="w-5 h-5 mr-2" aria-hidden="true" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M5 13l4 4L19 7"></path>
|
||||||
|
</svg>
|
||||||
|
Update
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Hidden Delete Form -->
|
||||||
|
<form id="delete-form" action="{{ route('project.todo.destroy', [$project, $todo]) }}" method="POST"
|
||||||
|
class="hidden">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function confirmDelete() {
|
||||||
|
if (confirm('Are you sure you want to delete this item?')) {
|
||||||
|
document.getElementById('delete-form').submit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</x-app-layout>
|
|
@ -0,0 +1,189 @@
|
||||||
|
<x-app-layout>
|
||||||
|
<x-slot name="header">
|
||||||
|
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||||
|
{{ __('Project List') }}
|
||||||
|
<a href="{{ route('project.create') }}"
|
||||||
|
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded float-right">
|
||||||
|
Create Project
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
|
||||||
|
<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">
|
||||||
|
@foreach($projects as $project)
|
||||||
|
<div class="relative">
|
||||||
|
<a href="{{ route('project.todo.index', $project) }}" class="card-link">
|
||||||
|
<div
|
||||||
|
class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6 hover:shadow-md transition duration-300 ease-in-out transform hover:-translate-y-1">
|
||||||
|
<div class="text-gray-800 dark:text-gray-100">
|
||||||
|
<div class="mb-4">
|
||||||
|
<h3 class="font-semibold text-lg mb-2">{{ $project->name }}</h3>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">{{ $project->description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<form action="{{ route('project.destroy', $project) }}" method="POST"
|
||||||
|
class="delete-project-form absolute top-1 right-1">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button type="button"
|
||||||
|
class="delete-button text-red-600 hover:text-red-800 transition duration-300 ease-in-out">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="modal hidden">
|
||||||
|
<!-- Small Popover, with a background that is visible when modal is open -->
|
||||||
|
<div class="popover popover-sm bg-white dark:bg-gray-800 shadow-lg rounded-lg p-6">
|
||||||
|
<p class="mb-4">Are you sure you want to delete this project?</p>
|
||||||
|
<button type="submit"
|
||||||
|
class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded mr-2">
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
class="cancel-button bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const deleteButtons = document.querySelectorAll('.delete-button');
|
||||||
|
const cancelButtons = document.querySelectorAll('.cancel-button');
|
||||||
|
const modals = document.querySelectorAll('.modal');
|
||||||
|
|
||||||
|
deleteButtons.forEach((deleteButton, index) => {
|
||||||
|
deleteButton.addEventListener('click', () => {
|
||||||
|
modals[index].classList.remove('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
cancelButtons[index].addEventListener('click', () => {
|
||||||
|
modals[index].classList.add('hidden');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<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('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 }}
|
||||||
|
<!-- Project name and link to project beside todo title as a badge with a blue background -->
|
||||||
|
<span
|
||||||
|
class="ml-2 text-sm font-semibold text-blue-600 bg-blue-100 rounded-full px-2 py-1">{{ $todo->project->name }}</span>
|
||||||
|
</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="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('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,155 @@
|
||||||
|
<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>
|
|
@ -41,7 +41,7 @@
|
||||||
<label for="due_start" class="block mb-2 font-semibold">Due Start</label>
|
<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"
|
<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"
|
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', strtotime($todo->due_start)) : '') }}">
|
value="{{ old('due_start', $todo->due_start ? date('Y-m-d\TH:i', $todo->due_start) : '') }}">
|
||||||
@error('due_start')
|
@error('due_start')
|
||||||
<div class="text-red-500 mt-2 text-sm">
|
<div class="text-red-500 mt-2 text-sm">
|
||||||
{{ $message }}
|
{{ $message }}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\ProfileController;
|
use App\Http\Controllers\ProfileController;
|
||||||
|
use App\Http\Controllers\ProjectController;
|
||||||
|
use App\Http\Controllers\ProjectTodoController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use App\Http\Controllers\TodoController;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
@ -16,7 +17,7 @@ use App\Http\Controllers\TodoController;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Route::get('/', function () {
|
Route::get('/', function () {
|
||||||
return redirect(route('todo.index'));
|
return redirect(route('project.index'));
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::get('/dashboard', function () {
|
Route::get('/dashboard', function () {
|
||||||
|
@ -24,19 +25,19 @@ Route::get('/dashboard', function () {
|
||||||
})->middleware(['auth', 'verified'])
|
})->middleware(['auth', 'verified'])
|
||||||
->name('dashboard');
|
->name('dashboard');
|
||||||
|
|
||||||
// Show dashboard after login, get user data from TodoController
|
Route::resource('project.todo', ProjectTodoController::class)
|
||||||
//Route::get('/dashboard', [TodoController::class, 'index'])
|
|
||||||
// ->middleware(['auth', 'verified'])
|
|
||||||
// ->name('dashboard');
|
|
||||||
|
|
||||||
// todo resource route
|
|
||||||
Route::resource('todo', TodoController::class)
|
|
||||||
->middleware([
|
->middleware([
|
||||||
'auth',
|
'auth',
|
||||||
'verified',
|
'verified',
|
||||||
'web'
|
'web'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
Route::resource('project', ProjectController::class)
|
||||||
|
->middleware([
|
||||||
|
'auth',
|
||||||
|
'verified',
|
||||||
|
'web'
|
||||||
|
]);
|
||||||
|
|
||||||
Route::middleware('auth')->group(function () {
|
Route::middleware('auth')->group(function () {
|
||||||
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
|
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
|
||||||
|
|
Loading…
Reference in New Issue