mirror of https://github.com/Devoalda/LaDo.git
feature(Project Completion):
Added Ability to mark completion for project
This commit is contained in:
parent
bc72c6c12e
commit
da2715f240
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
];
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ class Project extends Model
|
|||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'completed_at',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
|
||||
class ProjectPolicy
|
||||
{
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*/
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the model.
|
||||
*/
|
||||
public function view(User $user, Project $project): bool
|
||||
{
|
||||
return $user->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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Add Completed at column
|
||||
Schema::table('projects', function (Blueprint $table) {
|
||||
$table->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');
|
||||
});
|
||||
}
|
||||
};
|
|
@ -36,6 +36,18 @@
|
|||
@enderror
|
||||
</div>
|
||||
|
||||
<!-- Completed Checkbox -->
|
||||
<div class="mb-4">
|
||||
<label for="completed_at" class="block mb-2 font-semibold">Completed</label>
|
||||
<input type="checkbox" name="completed_at" id="completed_at"
|
||||
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-6 h-6 p-4 @error('completed') border-red-500 @enderror"
|
||||
{{ old('completed', $project->completed_at) ? 'checked' : '' }}>
|
||||
@error('completed')
|
||||
<div class="text-red-500 mt-2 text-sm">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end mt-4 space-x-4">
|
||||
|
|
Loading…
Reference in New Issue