diff --git a/app/Http/Controllers/Jobs/CommentsController.php b/app/Http/Controllers/Jobs/CommentsController.php new file mode 100644 index 0000000..409ae04 --- /dev/null +++ b/app/Http/Controllers/Jobs/CommentsController.php @@ -0,0 +1,82 @@ +authorize('comment-on', $job); + + $newComment = $request->validate([ + 'body' => 'required|string|max:255', + ]); + + $job->comments()->create([ + 'body' => $newComment['body'], + 'creator_id' => auth()->id(), + ]); + + flash(__('comment.created'), 'success'); + + return back(); + } + + /** + * Update the specified comment. + * + * @param \Illuminate\Http\Request $request + * @param \App\Entities\Projects\Job $job + * @param \App\Entities\Jobs\Comment $comment + * @return \Illuminate\Http\Response + */ + public function update(Request $request, Job $job, Comment $comment) + { + $this->authorize('update', $comment); + + $commentData = $request->validate([ + 'body' => 'required|string|max:255', + ]); + $comment->update($commentData); + flash(__('comment.updated'), 'success'); + + return redirect()->route('jobs.show', [$job] + request(['page'])); + } + + /** + * Remove the specified comment. + * + * @param \App\Entities\Jobs\Comment $comment + * @return \Illuminate\Routing\Redirector + */ + public function destroy(Job $job, Comment $comment) + { + $this->authorize('delete', $comment); + + request()->validate([ + 'comment_id' => 'required|exists:comments,id', + ]); + + if (request('comment_id') == $comment->id && $comment->delete()) { + $routeParam = [$job] + request(['page']); + flash(__('comment.deleted'), 'warning'); + + return redirect()->route('jobs.show', $routeParam); + } + flash(__('comment.undeleted'), 'error'); + + return back(); + } +} diff --git a/app/Http/Controllers/JobsController.php b/app/Http/Controllers/JobsController.php index f5dbe36..35fbc23 100755 --- a/app/Http/Controllers/JobsController.php +++ b/app/Http/Controllers/JobsController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Entities\Projects\Job; +use App\Entities\Projects\Comment; use App\Entities\Projects\Project; use App\Entities\Projects\JobsRepository; use App\Http\Requests\Jobs\DeleteRequest; @@ -65,6 +66,8 @@ class JobsController extends Controller $this->authorize('view', $job); $editableTask = null; + $editableComment = null; + $comments = $job->comments()->with('creator')->latest()->paginate(); if ($request->get('action') == 'task_edit' && $request->has('task_id')) { $editableTask = $this->repo->requireTaskById($request->get('task_id')); @@ -74,7 +77,11 @@ class JobsController extends Controller $editableTask = $this->repo->requireTaskById($request->get('task_id')); } - return view('jobs.show', compact('job', 'editableTask')); + if (request('action') == 'comment-edit' && request('comment_id') != null) { + $editableComment = Comment::find(request('comment_id')); + } + + return view('jobs.show', compact('job', 'editableTask', 'comments', 'editableComment')); } /** diff --git a/app/Policies/Projects/JobPolicy.php b/app/Policies/Projects/JobPolicy.php index 3806e78..5e27101 100644 --- a/app/Policies/Projects/JobPolicy.php +++ b/app/Policies/Projects/JobPolicy.php @@ -80,4 +80,31 @@ class JobPolicy { return $user->hasRole('admin'); } + + /** + * Determine whether the user can view job comments. + * + * @param \App\Entities\Users\User $user + * @param \App\Entities\Projects\Job $job + * @return bool + */ + public function viewComments(User $user, Job $job) + { + // Admin and job workers can commenting on their job. + return $user->hasRole('admin') + || ($user->hasRole('worker') && $job->worker_id == $user->id); + } + + /** + * Determine whether the user can add comment to a job. + * + * @param \App\Entities\Users\User $user + * @param \App\Entities\Projects\Job $job + * @return bool + */ + public function commentOn(User $user, Job $job) + { + // Admin and job workers can commenting on their job. + return $this->viewComments($user, $job); + } } diff --git a/resources/lang/id/comment.php b/resources/lang/id/comment.php index b20b834..95a1586 100644 --- a/resources/lang/id/comment.php +++ b/resources/lang/id/comment.php @@ -15,9 +15,9 @@ return [ 'updated' => 'Update data Komentar telah berhasil.', 'delete' => 'Hapus Komentar', 'delete_confirm' => 'Anda yakin akan menghapus Komentar ini?', - 'deleted' => 'Hapus data Komentar telah berhasil.', - 'undeleted' => 'Data Komentar gagal dihapus.', - 'undeleteable' => 'Data Komentar tidak dapat dihapus.', + 'deleted' => 'Komentar berhasil dihapus.', + 'undeleted' => 'Komentar gagal dihapus.', + 'undeleteable' => 'Komentar tidak dapat dihapus.', // Attributes 'body' => 'Komentar', diff --git a/resources/views/jobs/partials/comment-section.blade.php b/resources/views/jobs/partials/comment-section.blade.php new file mode 100644 index 0000000..6f5b042 --- /dev/null +++ b/resources/views/jobs/partials/comment-section.blade.php @@ -0,0 +1,32 @@ +@can('comment-on', $job) +{{ Form::open(['route' => ['jobs.comments.store', $job]]) }} +
+
{!! FormField::textarea('body', ['required' => true, 'label' => false, 'placeholder' => __('comment.create_text')]) !!}
+
+ {{ Form::submit(__('comment.create'), ['class' => 'btn btn-success btn-block']) }}
+
+
+{{ Form::close() }} +@endcan +@foreach($comments as $comment) +
+ + {{ $comment->time_display }} + {{ $comment->creator->name }} + +
+ @can('update', $comment) + {{ link_to_route('jobs.show', __('app.edit'), [$job, 'action' => 'comment-edit', 'comment_id' => $comment->id], ['id' => 'edit-comment-'.$comment->id, 'class' => 'small', 'title' => __('comment.edit')]) }} + @endcan + @can('delete', $comment) + {!! FormField::delete( + ['route' => ['jobs.comments.destroy', $job, $comment], 'class' => ''], + '×', + ['class' => 'btn-link', 'id' => 'delete-comment-'.$comment->id], + ['comment_id' => $comment->id, 'page' => request('page')] + ) !!} + @endcan +
+ {!! nl2br($comment->body) !!} +
+@endforeach diff --git a/resources/views/jobs/show.blade.php b/resources/views/jobs/show.blade.php index 051d4b2..e3062e6 100755 --- a/resources/views/jobs/show.blade.php +++ b/resources/views/jobs/show.blade.php @@ -29,6 +29,39 @@
@include('jobs.partials.job-tasks') + @can('view-comments', $job) +
+
+ {{ $comments->links() }} + @include('jobs.partials.comment-section') + {{ $comments->links() }} +
+
+ + @if (Request::get('action') == 'comment-edit' && $editableComment) + + @endif + @endcan
@endsection @@ -50,6 +83,7 @@ width: 8px; height: 8px; } + ul.pagination { margin-top: 0px } @endsection @@ -66,6 +100,11 @@ var ap_weight = e.currentTarget.value; $('#ap_weight').text(ap_weight); }); + + $('#commentModal').modal({ + show: true, + backdrop: 'static', + }); })(); @endsection diff --git a/routes/web.php b/routes/web.php index e7922bc..429f139 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,5 +1,5 @@ loginUsingId(1); require __DIR__.'/web/pages.php'; require __DIR__.'/web/users.php'; require __DIR__.'/web/references.php'; diff --git a/routes/web/projects.php b/routes/web/projects.php index 239b4ff..64b4a41 100644 --- a/routes/web/projects.php +++ b/routes/web/projects.php @@ -69,9 +69,6 @@ Route::group(['middleware' => ['auth']], function () { */ Route::get('jobs', ['as' => 'jobs.index', 'uses' => 'JobsController@index']); Route::get('jobs/{job}', ['as' => 'jobs.show', 'uses' => 'JobsController@show']); -}); - -Route::group(['middleware' => ['auth']], function () { /* * Job Actions Routes @@ -81,4 +78,11 @@ Route::group(['middleware' => ['auth']], function () { Route::get('jobs/{job}/delete', ['as' => 'jobs.delete', 'uses' => 'JobsController@delete']); Route::delete('jobs/{job}', ['as' => 'jobs.destroy', 'uses' => 'JobsController@destroy']); Route::post('jobs/{id}/tasks-reorder', ['as' => 'jobs.tasks-reorder', 'uses' => 'JobsController@tasksReorder']); + + /* + * Project Comments Routes + */ + Route::post('jobs/{job}/comments', 'Jobs\CommentsController@store')->name('jobs.comments.store'); + Route::patch('jobs/{job}/comments/{comment}', 'Jobs\CommentsController@update')->name('jobs.comments.update'); + Route::delete('jobs/{job}/comments/{comment}', 'Jobs\CommentsController@destroy')->name('jobs.comments.destroy'); }); diff --git a/tests/Feature/Projects/JobCommentsTest.php b/tests/Feature/Projects/JobCommentsTest.php new file mode 100644 index 0000000..8ecd4c2 --- /dev/null +++ b/tests/Feature/Projects/JobCommentsTest.php @@ -0,0 +1,104 @@ +adminUserSigningIn(); + $job = factory(Job::class)->create(); + $comment = factory(Comment::class)->create([ + 'commentable_type' => 'jobs', + 'commentable_id' => $job->id, + 'body' => 'This is job comment.', + ]); + + $this->visitRoute('jobs.show', $job); + $this->seeRouteIs('jobs.show', $job); + + $this->seeText('This is job comment.'); + } + + /** @test */ + public function admin_can_add_comment_to_a_job() + { + $admin = $this->adminUserSigningIn(); + $job = factory(Job::class)->create(); + + $this->visitRoute('jobs.show', $job); + + $this->submitForm(__('comment.create'), [ + 'body' => 'Komentar pertama.', + ]); + + $this->seePageIs(route('jobs.show', $job)); + $this->see(__('comment.created')); + + $this->seeInDatabase('comments', [ + 'commentable_type' => 'jobs', + 'commentable_id' => $job->id, + 'body' => 'Komentar pertama.', + 'creator_id' => $admin->id, + ]); + } + + /** @test */ + public function user_can_edit_comment() + { + $this->adminUserSigningIn(); + $job = factory(Job::class)->create(); + $comment = factory(Comment::class)->create([ + 'commentable_type' => 'jobs', + 'commentable_id' => $job->id, + 'body' => 'This is job comment.', + ]); + + $this->visitRoute('jobs.show', $job); + $this->seeElement('a', ['id' => 'edit-comment-'.$comment->id]); + $this->click('edit-comment-'.$comment->id); + $this->seeRouteIs('jobs.show', [$job, 'action' => 'comment-edit', 'comment_id' => $comment->id]); + + $this->submitForm(__('comment.update'), [ + 'body' => 'Komentar pertama.', + ]); + + $this->seePageIs(route('jobs.show', $job)); + $this->see(__('comment.updated')); + + $this->seeInDatabase('comments', [ + 'id' => $comment->id, + 'commentable_type' => 'jobs', + 'commentable_id' => $job->id, + 'body' => 'Komentar pertama.', + ]); + } + + /** @test */ + public function user_can_delete_comment() + { + $this->adminUserSigningIn(); + $job = factory(Job::class)->create(); + $comment = factory(Comment::class)->create([ + 'commentable_type' => 'jobs', + 'commentable_id' => $job->id, + 'body' => 'This is job comment.', + ]); + + $this->visitRoute('jobs.show', $job); + $this->seeElement('button', ['id' => 'delete-comment-'.$comment->id]); + $this->press('delete-comment-'.$comment->id); + + $this->seePageIs(route('jobs.show', $job)); + $this->see(__('comment.deleted')); + + $this->dontSeeInDatabase('comments', [ + 'id' => $comment->id, + ]); + } +} diff --git a/tests/Unit/Policies/JobPolicyTest.php b/tests/Unit/Policies/JobPolicyTest.php index 6b985a1..056b177 100644 --- a/tests/Unit/Policies/JobPolicyTest.php +++ b/tests/Unit/Policies/JobPolicyTest.php @@ -2,8 +2,8 @@ namespace Tests\Unit\Policies; +use Tests\TestCase; use App\Entities\Projects\Job; -use Tests\TestCase as TestCase; class JobPolicyTest extends TestCase { @@ -81,4 +81,32 @@ class JobPolicyTest extends TestCase $this->assertTrue($admin->can('see-pricings', $job)); $this->assertFalse($worker->can('see-pricings', $job)); } + + /** @test */ + public function admin_and_worker_view_job_comment_list() + { + $admin = $this->createUser('admin'); + $worker = $this->createUser('worker'); + + $job = factory(Job::class)->create([ + 'worker_id' => $worker->id, + ]); + + $this->assertTrue($admin->can('view-comments', $job)); + $this->assertTrue($worker->can('view-comments', $job)); + } + + /** @test */ + public function admin_and_job_workers_can_add_comment_to_job() + { + $admin = $this->createUser('admin'); + $worker = $this->createUser('worker'); + + $job = factory(Job::class)->create([ + 'worker_id' => $worker->id, + ]); + + $this->assertTrue($admin->can('comment-on', $job)); + $this->assertTrue($worker->can('comment-on', $job)); + } }