diff --git a/app/Entities/Projects/Issue.php b/app/Entities/Projects/Issue.php
index 17a31af..2ec06fc 100644
--- a/app/Entities/Projects/Issue.php
+++ b/app/Entities/Projects/Issue.php
@@ -3,6 +3,7 @@
namespace App\Entities\Projects;
use App\Entities\Users\User;
+use App\Entities\Projects\Comment;
use App\Entities\Projects\Project;
use Illuminate\Database\Eloquent\Model;
@@ -48,4 +49,14 @@ class Issue extends Model
{
return ''.$this->status.'';
}
+
+ /**
+ * Issue has many comments relation.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\MorphMany
+ */
+ public function comments()
+ {
+ return $this->morphMany(Comment::class, 'commentable');
+ }
}
diff --git a/app/Http/Controllers/Issues/CommentController.php b/app/Http/Controllers/Issues/CommentController.php
new file mode 100644
index 0000000..1b3d312
--- /dev/null
+++ b/app/Http/Controllers/Issues/CommentController.php
@@ -0,0 +1,83 @@
+authorize('comment-on', $issue);
+
+ $newComment = $request->validate([
+ 'body' => 'required|string|max:255',
+ ]);
+
+ $issue->comments()->create([
+ 'body' => $newComment['body'],
+ 'creator_id' => auth()->id(),
+ ]);
+ $issue->touch();
+
+ flash(__('comment.created'), 'success');
+
+ return back();
+ }
+
+ /**
+ * Update the specified comment.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param \App\Entities\Projects\Issue $issue
+ * @param \App\Entities\Projects\Comment $comment
+ * @return \Illuminate\Http\Response
+ */
+ public function update(Request $request, Issue $issue, Comment $comment)
+ {
+ $this->authorize('update', $comment);
+
+ $commentData = $request->validate([
+ 'body' => 'required|string|max:255',
+ ]);
+ $comment->update($commentData);
+ flash(__('comment.updated'), 'success');
+
+ return redirect()->route('projects.issues.show', [$issue->project, $issue]);
+ }
+
+ /**
+ * Remove the specified comment.
+ *
+ * @param \App\Entities\Projects\Issue $issue
+ * @param \\App\Entities\Projects\Comment $comment
+ * @return \Illuminate\Routing\Redirector
+ */
+ public function destroy(Issue $issue, Comment $comment)
+ {
+ $this->authorize('delete', $comment);
+
+ request()->validate([
+ 'comment_id' => 'required|exists:comments,id',
+ ]);
+
+ if (request('comment_id') == $comment->id && $comment->delete()) {
+ flash(__('comment.deleted'), 'warning');
+
+ return redirect()->route('projects.issues.show', [$issue->project, $issue]);
+ }
+ flash(__('comment.undeleted'), 'error');
+
+ return back();
+ }
+}
diff --git a/app/Http/Controllers/Projects/IssueController.php b/app/Http/Controllers/Projects/IssueController.php
index 1a0c603..99e4463 100644
--- a/app/Http/Controllers/Projects/IssueController.php
+++ b/app/Http/Controllers/Projects/IssueController.php
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Projects;
use App\Entities\Users\User;
use Illuminate\Http\Request;
use App\Entities\Projects\Issue;
+use App\Entities\Projects\Comment;
use App\Entities\Projects\Project;
use App\Entities\Projects\Priority;
use App\Http\Controllers\Controller;
@@ -14,7 +15,11 @@ class IssueController extends Controller
{
public function index(Project $project)
{
- $issues = $project->issues()->with(['pic', 'creator'])->get();
+ $issues = $project->issues()
+ ->orderBy('updated_at', 'desc')
+ ->with(['pic', 'creator'])
+ ->withCount(['comments'])
+ ->get();
return view('projects.issues.index', compact('project', 'issues'));
}
@@ -50,12 +55,19 @@ class IssueController extends Controller
public function show(Project $project, Issue $issue)
{
+ $editableComment = null;
$priorities = Priority::toArray();
$statuses = IssueStatus::toArray();
$users = User::pluck('name', 'id');
+ $comments = $issue->comments()->with('creator')->get();
+
+ if (request('action') == 'comment-edit' && request('comment_id') != null) {
+ $editableComment = Comment::find(request('comment_id'));
+ }
return view('projects.issues.show', compact(
- 'project', 'issue', 'users', 'statuses', 'priorities'
+ 'project', 'issue', 'users', 'statuses', 'priorities', 'comments',
+ 'editableComment'
));
}
diff --git a/app/Policies/Projects/IssuePolicy.php b/app/Policies/Projects/IssuePolicy.php
index 9bd8c8d..7fe975a 100644
--- a/app/Policies/Projects/IssuePolicy.php
+++ b/app/Policies/Projects/IssuePolicy.php
@@ -14,4 +14,16 @@ class IssuePolicy
{
return true;
}
+
+ /**
+ * Determine whether the user can add comment to an issue.
+ *
+ * @param \App\Entities\Users\User $user
+ * @param \App\Entities\Projects\Issue $issue
+ * @return bool
+ */
+ public function commentOn(User $user, Issue $issue)
+ {
+ return true;
+ }
}
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 818ae3c..d8823c2 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -22,6 +22,7 @@ class AppServiceProvider extends ServiceProvider
Relation::morphMap([
'projects' => 'App\Entities\Projects\Project',
+ 'issues' => 'App\Entities\Projects\Issue',
'jobs' => 'App\Entities\Projects\Job',
]);
}
diff --git a/resources/lang/de/app.php b/resources/lang/de/app.php
index 2547e4e..6b6d461 100644
--- a/resources/lang/de/app.php
+++ b/resources/lang/de/app.php
@@ -17,6 +17,7 @@ return [
'total' => 'gesamt',
'count' => 'Summe',
'remark' => 'Remark',
+ 'last_update' => 'Last Update',
// Action
'add' => 'Hinzufügen',
diff --git a/resources/lang/en/app.php b/resources/lang/en/app.php
index 2f55d05..2fb3794 100644
--- a/resources/lang/en/app.php
+++ b/resources/lang/en/app.php
@@ -17,6 +17,7 @@ return [
'total' => 'Total',
'count' => 'Count',
'remark' => 'Remark',
+ 'last_update' => 'Last Update',
// Action
'add' => 'Add',
diff --git a/resources/lang/id/app.php b/resources/lang/id/app.php
index 3906fd0..99fffa1 100644
--- a/resources/lang/id/app.php
+++ b/resources/lang/id/app.php
@@ -17,6 +17,7 @@ return [
'total' => 'Total',
'count' => 'Jumlah',
'remark' => 'Keterangan',
+ 'last_update' => 'Update',
// Action
'add' => 'Tambah',
diff --git a/resources/lang/id/comment.php b/resources/lang/id/comment.php
index 95a1586..b79cd50 100644
--- a/resources/lang/id/comment.php
+++ b/resources/lang/id/comment.php
@@ -12,7 +12,7 @@ return [
'created' => 'Input Komentar berhasil.',
'edit' => 'Edit Komentar',
'update' => 'Update Komentar',
- 'updated' => 'Update data Komentar telah berhasil.',
+ 'updated' => 'Update Komentar berhasil.',
'delete' => 'Hapus Komentar',
'delete_confirm' => 'Anda yakin akan menghapus Komentar ini?',
'deleted' => 'Komentar berhasil dihapus.',
diff --git a/resources/views/projects/issues/index.blade.php b/resources/views/projects/issues/index.blade.php
index a582c34..cfcfbde 100755
--- a/resources/views/projects/issues/index.blade.php
+++ b/resources/views/projects/issues/index.blade.php
@@ -19,8 +19,10 @@
{{ __('issue.title') }} |
{{ __('issue.priority') }} |
{{ __('app.status') }} |
+ {{ __('comment.comment') }} |
{{ __('issue.pic') }} |
{{ __('issue.creator') }} |
+ {{ __('app.last_update') }} |
{{ __('app.action') }} |
@@ -33,8 +35,10 @@
{{ $issue->title }} |
{!! $issue->priority_label !!} |
{!! $issue->status_label !!} |
+ {{ $issue->comments_count }} |
{{ $issue->pic->name }} |
{{ $issue->creator->name }} |
+ {{ $issue->updated_at->diffForHumans() }} |
{{ link_to_route(
'projects.issues.show',
@@ -45,7 +49,7 @@
|
@empty
- | {{ __('issue.empty') }} |
+ | {{ __('issue.empty') }} |
@endforelse
diff --git a/resources/views/projects/issues/partials/comment-section.blade.php b/resources/views/projects/issues/partials/comment-section.blade.php
new file mode 100644
index 0000000..73eec00
--- /dev/null
+++ b/resources/views/projects/issues/partials/comment-section.blade.php
@@ -0,0 +1,54 @@
+@foreach($comments as $comment)
+
+
+
+ @can('update', $comment)
+ {{ link_to_route('projects.issues.show', __('app.edit'), [$project, $issue, 'action' => 'comment-edit', 'comment_id' => $comment->id], ['id' => 'edit-comment-'.$comment->id, 'class' => 'small', 'title' => __('comment.edit')]) }}
+ @endcan
+ @can('delete', $comment)
+ {!! FormField::delete(
+ ['route' => ['issues.comments.destroy', $issue, $comment], 'class' => ''],
+ '×',
+ ['class' => 'btn-link', 'id' => 'delete-comment-'.$comment->id],
+ ['comment_id' => $comment->id]
+ ) !!}
+ @endcan
+
+ {!! nl2br($comment->body) !!}
+
+@endforeach
+
+@can('comment-on', $issue)
+{{ Form::open(['route' => ['issues.comments.store', $issue]]) }}
+{!! FormField::textarea('body', ['required' => true, 'label' => false, 'placeholder' => __('comment.create_text')]) !!}
+{{ Form::submit(__('comment.create'), ['class' => 'btn btn-success pull-right']) }}
+{{ Form::close() }}
+
+@endcan
+
+@if (Request::get('action') == 'comment-edit' && $editableComment)
+
+@endif
diff --git a/resources/views/projects/issues/show.blade.php b/resources/views/projects/issues/show.blade.php
index 42ccb12..beb3017 100755
--- a/resources/views/projects/issues/show.blade.php
+++ b/resources/views/projects/issues/show.blade.php
@@ -32,6 +32,8 @@
{{ link_to_route('projects.issues.index', __('issue.back_to_index'), [$project], ['class' => 'btn btn-default pull-right']) }}
+
+ @include('projects.issues.partials.comment-section')
{{ Form::model($issue, ['route' => ['issues.options.update', $issue], 'method' => 'patch']) }}
@@ -50,3 +52,14 @@
@endsection
+
+@section('script')
+
+@endsection
diff --git a/routes/web/projects.php b/routes/web/projects.php
index 45d403f..2f4ae02 100644
--- a/routes/web/projects.php
+++ b/routes/web/projects.php
@@ -105,3 +105,10 @@ Route::group(['middleware' => ['auth']], function () {
* Issue Options Routes
*/
Route::patch('issues/{issue}/options', 'Issues\OptionController@update')->name('issues.options.update');
+
+/**
+ * Issue Comments Routes
+ */
+Route::post('issues/{issue}/comments', 'Issues\CommentController@store')->name('issues.comments.store');
+Route::patch('issues/{issue}/comments/{comment}', 'Issues\CommentController@update')->name('issues.comments.update');
+Route::delete('issues/{issue}/comments/{comment}', 'Issues\CommentController@destroy')->name('issues.comments.destroy');
diff --git a/tests/Feature/Projects/IssueCommentsTest.php b/tests/Feature/Projects/IssueCommentsTest.php
new file mode 100644
index 0000000..a85c290
--- /dev/null
+++ b/tests/Feature/Projects/IssueCommentsTest.php
@@ -0,0 +1,106 @@
+adminUserSigningIn();
+ $issue = factory(Issue::class)->create();
+ $comment = factory(Comment::class)->create([
+ 'commentable_type' => 'issues',
+ 'commentable_id' => $issue->id,
+ 'body' => 'This is issue comment.',
+ ]);
+
+ $this->visitRoute('projects.issues.show', [$issue->project, $issue]);
+
+ $this->seeText('This is issue comment.');
+ }
+
+ /** @test */
+ public function admin_can_add_comment_to_an_issue()
+ {
+ $admin = $this->adminUserSigningIn();
+ $issue = factory(Issue::class)->create();
+
+ $this->visitRoute('projects.issues.show', [$issue->project, $issue]);
+
+ $this->submitForm(__('comment.create'), [
+ 'body' => 'First comment.',
+ ]);
+
+ $this->seePageIs(route('projects.issues.show', [$issue->project, $issue]));
+ $this->see(__('comment.created'));
+
+ $this->seeInDatabase('comments', [
+ 'commentable_type' => 'issues',
+ 'commentable_id' => $issue->id,
+ 'body' => 'First comment.',
+ 'creator_id' => $admin->id,
+ ]);
+ }
+
+ /** @test */
+ public function user_can_edit_an_issue_comment()
+ {
+ $this->adminUserSigningIn();
+ $issue = factory(Issue::class)->create();
+ $comment = factory(Comment::class)->create([
+ 'commentable_type' => 'issues',
+ 'commentable_id' => $issue->id,
+ 'body' => 'This is issue comment.',
+ ]);
+
+ $this->visitRoute('projects.issues.show', [$issue->project, $issue]);
+ $this->seeElement('a', ['id' => 'edit-comment-'.$comment->id]);
+ $this->click('edit-comment-'.$comment->id);
+ $this->seeRouteIs('projects.issues.show', [$issue->project, $issue, 'action' => 'comment-edit', 'comment_id' => $comment->id]);
+
+ $this->submitForm(__('comment.update'), [
+ 'body' => 'Edited comment.',
+ ]);
+
+ $this->seePageIs(route('projects.issues.show', [$issue->project, $issue]));
+ $this->see(__('comment.updated'));
+
+ $this->seeInDatabase('comments', [
+ 'id' => $comment->id,
+ 'commentable_type' => 'issues',
+ 'commentable_id' => $issue->id,
+ 'body' => 'Edited comment.',
+ ]);
+ }
+
+ /** @test */
+ public function user_can_delete_an_issue_comment()
+ {
+ $this->adminUserSigningIn();
+ $issue = factory(Issue::class)->create();
+ $comment = factory(Comment::class)->create([
+ 'commentable_type' => 'issues',
+ 'commentable_id' => $issue->id,
+ 'body' => 'This is issue comment.',
+ ]);
+
+ $this->visitRoute('projects.issues.show', [$issue->project, $issue]);
+ $this->seeElement('button', ['id' => 'delete-comment-'.$comment->id]);
+ $this->press('delete-comment-'.$comment->id);
+
+ $this->seePageIs(route('projects.issues.show', [$issue->project, $issue]));
+ $this->see(__('comment.deleted'));
+
+ $this->dontSeeInDatabase('comments', [
+ 'id' => $comment->id,
+ ]);
+ }
+}
diff --git a/tests/Unit/Models/IssueTest.php b/tests/Unit/Models/IssueTest.php
index 7877fdd..a43ed72 100644
--- a/tests/Unit/Models/IssueTest.php
+++ b/tests/Unit/Models/IssueTest.php
@@ -5,7 +5,9 @@ namespace Tests\Unit\Models;
use Tests\TestCase;
use App\Entities\Users\User;
use App\Entities\Projects\Issue;
+use App\Entities\Projects\Comment;
use App\Entities\Projects\Project;
+use Illuminate\Support\Collection;
use App\Entities\Projects\Priority;
use Illuminate\Foundation\Testing\RefreshDatabase;
@@ -81,4 +83,17 @@ class IssueTest extends TestCase
$this->assertEquals(''.$issue->priority.'', $issue->priority_label);
}
+
+ /** @test */
+ public function an_issue_has_many_comments_relation()
+ {
+ $issue = factory(Issue::class)->create();
+ $comment = factory(Comment::class)->create([
+ 'commentable_type' => 'issues',
+ 'commentable_id' => $issue->id,
+ ]);
+
+ $this->assertInstanceOf(Collection::class, $issue->comments);
+ $this->assertInstanceOf(Comment::class, $issue->comments->first());
+ }
}
diff --git a/tests/Unit/Policies/IssuePolicyTest.php b/tests/Unit/Policies/IssuePolicyTest.php
index 051e927..a5b1016 100644
--- a/tests/Unit/Policies/IssuePolicyTest.php
+++ b/tests/Unit/Policies/IssuePolicyTest.php
@@ -22,4 +22,13 @@ class IssuePolicyTest extends TestCase
$this->assertTrue($admin->can('create', new Issue()));
}
+
+ /** @test */
+ public function admin_can_add_comment_to_an_issue()
+ {
+ $admin = $this->createUser('admin');
+ $issue = factory(Issue::class)->create();
+
+ $this->assertTrue($admin->can('comment-on', $issue));
+ }
}