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)
+
+
+
+ @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));
+ }
}