diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php index f2b6480..a78854d 100644 --- a/app/Http/Controllers/ProjectController.php +++ b/app/Http/Controllers/ProjectController.php @@ -6,6 +6,8 @@ use App\Http\Requests\Project\StoreProjectRequest; use App\Http\Requests\Project\UpdateProjectRequest; use App\Models\Project; use App\Models\Todo; +use Carbon\Carbon; +use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\View; @@ -30,10 +32,10 @@ class ProjectController extends Controller $todos = $user->todos() ->map(function ($todo) { - return Todo::find($todo->id); - }); + return Todo::find($todo->id); + }); - if ($request->ajax()){ + if ($request->ajax()) { $view = view('project.load-projects', compact('projects'))->render(); return Response::json([ 'view' => $view, @@ -45,7 +47,7 @@ class ProjectController extends Controller 'projects' => $projects, 'todos' => $todos->whereNull('completed_at')->values(), 'completed' => $todos->whereNotNull('completed_at') - ->whereBetween('completed_at', [strtotime('today midnight'), strtotime('today midnight + 1 day')]) + ->whereBetween('completed_at', [strtotime('today midnight'), strtotime('today midnight + 1 day')]) ->values(), ]); } @@ -63,31 +65,32 @@ class ProjectController extends Controller */ public function store(StoreProjectRequest $request): RedirectResponse { - $user = User::find(auth()->user()->id); - $data = $request->validated(); - $user->projects()->create($data); + auth()->user()->projects()->create($data); return redirect()->route('project.index') ->with('info', 'Project created!'); } /** - * TODO: Complete this method (if needed) * Display the specified resource. + * @throws AuthorizationException */ public function show(Project $project): RedirectResponse { + $this->authorize('view', $project); + return redirect()->route('project.index'); } /** - * TODO: Complete this method (if needed) * Show the form for editing the specified Project. + * @throws AuthorizationException */ - public function edit(Project $project) + public function edit(Project $project): View|\Illuminate\Foundation\Application|Factory|Application { + $this->authorize('view', $project); return view('project.edit', [ 'project' => $project, ]); @@ -98,14 +101,17 @@ class ProjectController extends Controller */ public function update(UpdateProjectRequest $request, Project $project): RedirectResponse { - $user = User::find(auth()->user()->id); - $projects = $user->projects; - $project = $projects->find($project->id); + $this->authorize('update', $project); - $data = $request->validated(); + $data = $request->validatedWithCompletedAt(); $project->update($data); + // Complete all todos in project + if ($request->has('completed_at') && $request->completed_at) { + $project->todos()->update(['completed_at' => Carbon::now()->timestamp]); + } + return back()->with('info', 'Project updated!'); } @@ -114,9 +120,7 @@ class ProjectController extends Controller */ public function destroy(Project $project) { - $user = User::find(auth()->user()->id); - $projects = $user->projects; - $project = $projects->find($project->id); + $this->authorize('delete', $project); $project->delete(); diff --git a/app/Http/Controllers/ProjectTodoController.php b/app/Http/Controllers/ProjectTodoController.php index c588223..bc54398 100644 --- a/app/Http/Controllers/ProjectTodoController.php +++ b/app/Http/Controllers/ProjectTodoController.php @@ -10,7 +10,6 @@ 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 @@ -77,8 +76,7 @@ class ProjectTodoController extends Controller $projects = $user->projects; $project = $projects->find($project_id); - if (!$project || $project->user->id !== auth()->user()->id || $todo->user()[0]->id !== auth()->user()->id) - return back()->with('error', 'Project/Todo not found'); + $this->authorize('view', [Todo::class, $project, $todo]); return view('todo.show', compact('project', 'todo')); } @@ -88,12 +86,9 @@ class ProjectTodoController extends Controller */ public function edit($project_id, Todo $todo) { - $user = User::find(auth()->user()->id); - $projects = $user->projects; + $projects = auth()->user()->projects; $project = $projects->find($project_id); - - $this->authorize('update', [Todo::class, $project, $todo]); - + $this->authorize('view', [Todo::class, $project, $todo]); return view('todo.edit', compact('project', 'todo')); } @@ -110,6 +105,15 @@ class ProjectTodoController extends Controller // Update other fields $todo->fill($request->validated()); + $todo->due_start = $request->due_start ? + strtotime(Carbon::parse($request->due_start)) : + ($todo->due_start ?: null); + + $todo->due_end = $request->due_end ? + strtotime(Carbon::parse($request->due_end)) : + ($todo->due_end ?: + ($todo->due_start ? strtotime(Carbon::parse($todo->due_start)) : null)); + $todo->save(); diff --git a/app/Http/Livewire/Dashboard/AveTodoPerProject.php b/app/Http/Livewire/Dashboard/AveTodoPerProject.php index e47a69b..398bb98 100644 --- a/app/Http/Livewire/Dashboard/AveTodoPerProject.php +++ b/app/Http/Livewire/Dashboard/AveTodoPerProject.php @@ -22,17 +22,23 @@ class AveTodoPerProject extends Component $projects = $user->projects; $project_count = $projects->count(); + if ($project_count === 0) { + $this->ave_todo_count = 0; + return; + } // Average number of todos per project $ave_todo_count = function ($projects) { $todo_count = 0; foreach ($projects as $project) { $todo_count += $project->todos->count(); } + return $todo_count / $projects->count(); }; $this->ave_todo_count = $ave_todo_count($projects); } + public function render() { return view('livewire.dashboard.ave-todo-per-project'); diff --git a/app/Http/Livewire/Dashboard/PomoCount.php b/app/Http/Livewire/Dashboard/PomoCount.php index 206a4ac..bf9b48d 100644 --- a/app/Http/Livewire/Dashboard/PomoCount.php +++ b/app/Http/Livewire/Dashboard/PomoCount.php @@ -22,7 +22,7 @@ class PomoCount extends Component $ave_pomo_count = $todos->avg(function ($todo) { return $todo->pomos->count(); }); - $this->ave_pomo_count = $ave_pomo_count; + $this->ave_pomo_count = $ave_pomo_count ?? 0; } public function render() diff --git a/app/Http/Livewire/Dashboard/PomoTime.php b/app/Http/Livewire/Dashboard/PomoTime.php index 3ae979c..e11c838 100644 --- a/app/Http/Livewire/Dashboard/PomoTime.php +++ b/app/Http/Livewire/Dashboard/PomoTime.php @@ -28,6 +28,11 @@ class PomoTime extends Component $total_pomos = $pomos->count(); + if ($total_pomos === 0) { + $this->ave_pomo_time = 0; + return; + } + $total_time = 0; foreach ($pomos as $pomo) { diff --git a/app/Http/Requests/Project/StoreTodoRequest.php b/app/Http/Requests/Project/StoreTodoRequest.php index 43585df..b56b8d9 100644 --- a/app/Http/Requests/Project/StoreTodoRequest.php +++ b/app/Http/Requests/Project/StoreTodoRequest.php @@ -4,6 +4,7 @@ namespace App\Http\Requests\Project; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Support\Carbon; class StoreTodoRequest extends FormRequest { diff --git a/app/Http/Requests/Project/UpdateProjectRequest.php b/app/Http/Requests/Project/UpdateProjectRequest.php index bb75516..0917393 100644 --- a/app/Http/Requests/Project/UpdateProjectRequest.php +++ b/app/Http/Requests/Project/UpdateProjectRequest.php @@ -2,6 +2,7 @@ namespace App\Http\Requests\Project; +use Carbon\Carbon; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; @@ -25,6 +26,29 @@ class UpdateProjectRequest extends FormRequest return [ 'name' => 'required|string|max:255', 'description' => 'nullable|string|max:255', + 'completed_at' => 'nullable' ]; } + + public function validatedWithCompletedAt(): array + { + // Return safe data merged with completed_at to unix timestamp + return array_merge( + $this->validated(), + [ + // Now or null + 'completed_at' => $this->completed_at ? Carbon::now()->timestamp : null, + ] + ); + } + + protected function passedValidation(): void + { + // Replace or add completed_at to the request, value is time now in unix format + if ($this->has('completed_at')) { + $this->request->add(['completed_at' => Carbon::now()->timestamp]); + } else { + $this->request->add(['completed_at' => null]); + } + } } diff --git a/app/Http/Requests/Project/UpdateTodoRequest.php b/app/Http/Requests/Project/UpdateTodoRequest.php index 1b0f77b..2578be3 100644 --- a/app/Http/Requests/Project/UpdateTodoRequest.php +++ b/app/Http/Requests/Project/UpdateTodoRequest.php @@ -23,10 +23,6 @@ class UpdateTodoRequest extends FormRequest { $this->merge([ 'completed_at' => $this->completed_at ? strtotime(Carbon::parse('now')) : null, - 'due_start' => $this->due_start ? strtotime(Carbon::parse($this->due_start)) : null, - 'due_end' => $this->due_end ? strtotime(Carbon::parse($this->due_end)) : - ($this->due_start ? strtotime(Carbon::parse($this->due_start)) : null), - ]); } @@ -40,8 +36,8 @@ class UpdateTodoRequest extends FormRequest return [ 'title' => 'nullable|string|max:255', 'description' => 'nullable|string|max:255', - 'due_start' => 'nullable', - 'due_end' => 'nullable', + 'due_start' => 'nullable|date', + 'due_end' => 'nullable|date|after_or_equal:due_start', 'completed_at' => 'nullable', ]; } diff --git a/app/Models/Project.php b/app/Models/Project.php index 658d524..5c37b6d 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -21,6 +21,7 @@ class Project extends Model protected $fillable = [ 'name', 'description', + 'completed_at', ]; /** diff --git a/app/Policies/ProjectPolicy.php b/app/Policies/ProjectPolicy.php new file mode 100644 index 0000000..fd2a527 --- /dev/null +++ b/app/Policies/ProjectPolicy.php @@ -0,0 +1,66 @@ +id === $project->user->id; + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return true; + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, Project $project): bool + { + return $user->id === $project->user->id; + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Project $project): bool + { + return $user->id === $project->user->id; + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, Project $project): bool + { + return $user->id === $project->user->id; + } + + /** + * Determine whether the user can permanently delete the model. + */ + public function forceDelete(User $user, Project $project): bool + { + return $user->id === $project->user->id; + } +} diff --git a/app/Policies/TodoPolicy.php b/app/Policies/TodoPolicy.php index 66b1bc9..df133d4 100644 --- a/app/Policies/TodoPolicy.php +++ b/app/Policies/TodoPolicy.php @@ -20,7 +20,7 @@ class TodoPolicy /** * Determine whether the user can view the model. */ - public function view(User $user, Todo $todo): bool + public function view(User $user, Project $project, Todo $todo): bool { return $user->id === $todo->project->user->id; } diff --git a/database/factories/ProjectFactory.php b/database/factories/ProjectFactory.php index 9a45928..1fc1f40 100644 --- a/database/factories/ProjectFactory.php +++ b/database/factories/ProjectFactory.php @@ -19,6 +19,7 @@ class ProjectFactory extends Factory return [ 'name' => $this->faker->sentence(3), 'description' => $this->faker->sentence(10), + 'completed_at' => $this->faker->boolean(20) ? $this->faker->unixTime() : null, ]; } } diff --git a/database/migrations/2023_08_11_012935_project_completion.php b/database/migrations/2023_08_11_012935_project_completion.php new file mode 100644 index 0000000..0e8208b --- /dev/null +++ b/database/migrations/2023_08_11_012935_project_completion.php @@ -0,0 +1,30 @@ +integer('completed_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // Drop Completed at column + Schema::table('projects', function (Blueprint $table) { + $table->dropColumn('completed_at'); + }); + } +}; diff --git a/resources/views/project/edit.blade.php b/resources/views/project/edit.blade.php index b1c9ba3..a68062b 100644 --- a/resources/views/project/edit.blade.php +++ b/resources/views/project/edit.blade.php @@ -36,6 +36,18 @@ @enderror + +
+ + completed_at) ? 'checked' : '' }}> + @error('completed') +
+ {{ $message }} +
+ @enderror +