From aed240710d3444d967c6682269cdd72046b549df Mon Sep 17 00:00:00 2001 From: Nafies Luthfi Date: Mon, 8 Mar 2021 22:16:28 +0800 Subject: [PATCH] Add user activities tab on the project detail page --- app/Entities/Users/Activity.php | 11 +++++++ .../Controllers/Projects/ActivityController.php | 24 +++++++++++++++ app/Providers/AppServiceProvider.php | 1 + resources/lang/de/activity.php | 9 ++++++ resources/lang/de/project.php | 2 ++ resources/lang/en/activity.php | 9 ++++++ resources/lang/en/project.php | 2 ++ resources/lang/id/activity.php | 9 ++++++ resources/lang/id/project.php | 2 ++ .../views/projects/activities/index.blade.php | 22 ++++++++++++++ .../views/projects/partials/nav-tabs.blade.php | 3 ++ .../users/activities/activity_list_item.blade.php | 4 +++ .../activities/projects/job_deleted.blade.php | 15 ++++++++++ .../activities/projects/project_created.blade.php | 11 +++++++ .../activities/projects/project_updated.blade.php | 22 ++++++++++++++ routes/web/projects.php | 5 ++++ tests/Unit/Models/ActivityTest.php | 34 ++++++++++++++++++++++ 17 files changed, 185 insertions(+) create mode 100644 app/Http/Controllers/Projects/ActivityController.php create mode 100644 resources/lang/de/activity.php create mode 100644 resources/lang/en/activity.php create mode 100644 resources/lang/id/activity.php create mode 100755 resources/views/projects/activities/index.blade.php create mode 100644 resources/views/users/activities/activity_list_item.blade.php create mode 100644 resources/views/users/activities/projects/job_deleted.blade.php create mode 100644 resources/views/users/activities/projects/project_created.blade.php create mode 100644 resources/views/users/activities/projects/project_updated.blade.php diff --git a/app/Entities/Users/Activity.php b/app/Entities/Users/Activity.php index 2f63cbe..9d50807 100644 --- a/app/Entities/Users/Activity.php +++ b/app/Entities/Users/Activity.php @@ -2,6 +2,7 @@ namespace App\Entities\Users; +use App\Entities\Users\User; use Illuminate\Database\Eloquent\Model; class Activity extends Model @@ -11,4 +12,14 @@ class Activity extends Model protected $fillable = ['type', 'parent_id', 'user_id', 'object_id', 'object_type', 'data']; protected $casts = ['data' => 'array']; + + public function user() + { + return $this->belongsTo(User::class)->withDefault(['name' => 'n/a']); + } + + public function object() + { + return $this->morphTo(); + } } diff --git a/app/Http/Controllers/Projects/ActivityController.php b/app/Http/Controllers/Projects/ActivityController.php new file mode 100644 index 0000000..b39416e --- /dev/null +++ b/app/Http/Controllers/Projects/ActivityController.php @@ -0,0 +1,24 @@ +where(function ($query) use ($project) { + $query->where('object_id', $project->id); + $query->where('object_type', 'projects'); + }); + + $activities = $activityQuery->latest()->paginate(); + + return view('projects.activities.index', compact('project', 'activities')); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index e62e20c..5fe772b 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -25,6 +25,7 @@ class AppServiceProvider extends ServiceProvider 'projects' => 'App\Entities\Projects\Project', 'issues' => 'App\Entities\Projects\Issue', 'jobs' => 'App\Entities\Projects\Job', + 'tasks' => 'App\Entities\Projects\Task', ]); Paginator::useBootstrap(); } diff --git a/resources/lang/de/activity.php b/resources/lang/de/activity.php new file mode 100644 index 0000000..a483ba5 --- /dev/null +++ b/resources/lang/de/activity.php @@ -0,0 +1,9 @@ + [ + 'project_created' => 'Project created: :name new :user.', + 'project_updated' => 'Project data :name updated by :user.', + 'job_deleted' => 'Job deleted by :user.', + ], +]; diff --git a/resources/lang/de/project.php b/resources/lang/de/project.php index 07898c5..28ab2d7 100644 --- a/resources/lang/de/project.php +++ b/resources/lang/de/project.php @@ -59,6 +59,7 @@ return [ 'proposal_date' => 'Datum des Angebotes', 'project_value' => 'Projektwert', 'proposal_value' => 'Angebotswert', + 'updated_at' => 'Last Update', // Relations 'files' => 'Dokumentenliste', @@ -72,6 +73,7 @@ return [ 'status' => 'Projektstatus', 'payments' => 'Zahlungen', 'issues' => 'Issues', + 'activities' => 'Activities', // Statuses 'planned' => 'geplant', diff --git a/resources/lang/en/activity.php b/resources/lang/en/activity.php new file mode 100644 index 0000000..a483ba5 --- /dev/null +++ b/resources/lang/en/activity.php @@ -0,0 +1,9 @@ + [ + 'project_created' => 'Project created: :name new :user.', + 'project_updated' => 'Project data :name updated by :user.', + 'job_deleted' => 'Job deleted by :user.', + ], +]; diff --git a/resources/lang/en/project.php b/resources/lang/en/project.php index c3a5c40..8b104f6 100644 --- a/resources/lang/en/project.php +++ b/resources/lang/en/project.php @@ -59,6 +59,7 @@ return [ 'proposal_date' => 'Proposal Date', 'project_value' => 'Project Value', 'proposal_value' => 'Proposal Value', + 'updated_at' => 'Last Update', // Relations 'files' => 'Document List', @@ -72,6 +73,7 @@ return [ 'status' => 'Project Status', 'payments' => 'Payments', 'issues' => 'Issues', + 'activities' => 'Activities', // Statuses 'planned' => 'Planned', diff --git a/resources/lang/id/activity.php b/resources/lang/id/activity.php new file mode 100644 index 0000000..19cb1ea --- /dev/null +++ b/resources/lang/id/activity.php @@ -0,0 +1,9 @@ + [ + 'project_created' => 'Input project baru: :name oleh :user.', + 'project_updated' => 'Data project :name diubah oleh :user.', + 'job_deleted' => 'Job dihapus oleh :user.', + ], +]; diff --git a/resources/lang/id/project.php b/resources/lang/id/project.php index 2ea3b04..fcea474 100644 --- a/resources/lang/id/project.php +++ b/resources/lang/id/project.php @@ -59,6 +59,7 @@ return [ 'proposal_date' => 'Tanggal Proposal', 'project_value' => 'Nilai Project', 'proposal_value' => 'Nilai Proposal', + 'updated_at' => 'Waktu Update', // Relations 'files' => 'List Dokumen', @@ -72,6 +73,7 @@ return [ 'status' => 'Status Project', 'payments' => 'Pembayaran', 'issues' => 'Issue', + 'activities' => 'Aktifitas', // Statuses 'planned' => 'Rencana', diff --git a/resources/views/projects/activities/index.blade.php b/resources/views/projects/activities/index.blade.php new file mode 100755 index 0000000..ade6096 --- /dev/null +++ b/resources/views/projects/activities/index.blade.php @@ -0,0 +1,22 @@ +@extends('layouts.project') + +@section('subtitle', __('project.activities')) + +@section('content-project') + +
+
+ {{ $activities->links() }} +
    + @foreach($activities as $activity) + @includeWhen( + view()->exists('users.activities.'.$activity->object_type.'.'.$activity->type), + 'users.activities.'.$activity->object_type.'.'.$activity->type + ) + @endforeach +
+ {{ $activities->links() }} +
+
+ +@endsection diff --git a/resources/views/projects/partials/nav-tabs.blade.php b/resources/views/projects/partials/nav-tabs.blade.php index ed2ee19..47df5b1 100644 --- a/resources/views/projects/partials/nav-tabs.blade.php +++ b/resources/views/projects/partials/nav-tabs.blade.php @@ -3,6 +3,9 @@
  • {!! link_to_route('projects.show', __('project.detail'), $project) !!}
  • +
  • + {!! link_to_route('projects.activities.index', __('project.activities'), $project) !!} +
  • @can('view-jobs', $project)
  • {!! link_to_route('projects.jobs.index', __('project.jobs').' ('.$project->jobs->count().')', $project) !!} diff --git a/resources/views/users/activities/activity_list_item.blade.php b/resources/views/users/activities/activity_list_item.blade.php new file mode 100644 index 0000000..ad295f8 --- /dev/null +++ b/resources/views/users/activities/activity_list_item.blade.php @@ -0,0 +1,4 @@ +
  • + {{ $time }} + {{ $body }} +
  • diff --git a/resources/views/users/activities/projects/job_deleted.blade.php b/resources/views/users/activities/projects/job_deleted.blade.php new file mode 100644 index 0000000..57a1fa9 --- /dev/null +++ b/resources/views/users/activities/projects/job_deleted.blade.php @@ -0,0 +1,15 @@ +@component('users.activities.activity_list_item') +@slot('time') + {{ $activity->created_at }} +@endslot +@slot('body') +

    + {!! __('activity.'.$activity->object_type.'.'.$activity->type, [ + 'user' => $activity->user->name, + ]) !!} +

    +
    {{ __('job.name') }}: {{ $activity->data['name'] }}
    +
    {{ __('job.description') }}: {{ $activity->data['description'] }}
    +
    {{ __('job.price') }}: {{ format_money($activity->data['price']) }}
    +@endslot +@endcomponent diff --git a/resources/views/users/activities/projects/project_created.blade.php b/resources/views/users/activities/projects/project_created.blade.php new file mode 100644 index 0000000..421f0c6 --- /dev/null +++ b/resources/views/users/activities/projects/project_created.blade.php @@ -0,0 +1,11 @@ +@component('users.activities.activity_list_item') +@slot('time') + {{ $activity->created_at }} +@endslot +@slot('body') + {!! __('activity.'.$activity->object_type.'.'.$activity->type, [ + 'user' => $activity->user->name, + 'name' => $activity->object->name, + ]) !!} +@endslot +@endcomponent diff --git a/resources/views/users/activities/projects/project_updated.blade.php b/resources/views/users/activities/projects/project_updated.blade.php new file mode 100644 index 0000000..0a2d36b --- /dev/null +++ b/resources/views/users/activities/projects/project_updated.blade.php @@ -0,0 +1,22 @@ +@component('users.activities.activity_list_item') +@slot('time') + {{ $activity->created_at }} +@endslot +@slot('body') +

    + {!! __('activity.'.$activity->object_type.'.'.$activity->type, [ + 'user' => $activity->user->name, + 'name' => $activity->object->name, + ]) !!} +

    + @php + $data = $activity->data; + @endphp + @foreach ($data['before'] as $key => $value) + @php + $afterValue = $data['after'][$key] ?? null; + @endphp +
    {{ __('project.'.$key) }}: {{ $value }} => {{ $afterValue }}
    + @endforeach +@endslot +@endcomponent diff --git a/routes/web/projects.php b/routes/web/projects.php index 45aefba..bc92298 100644 --- a/routes/web/projects.php +++ b/routes/web/projects.php @@ -26,6 +26,11 @@ Route::group(['middleware' => ['auth'], 'namespace' => 'Projects'], function () Route::get('projects/{project}/invoices', ['as' => 'projects.invoices', 'uses' => 'InvoicesController@index']); /* + * Project Activities Routes + */ + Route::get('projects/{project}/activities', ['as' => 'projects.activities.index', 'uses' => 'ActivityController@index']); + + /* * Project Jobs Routes */ Route::get('projects/{project}/jobs-export/{type?}', ['as' => 'projects.jobs-export', 'uses' => 'JobsController@jobsExport']); diff --git a/tests/Unit/Models/ActivityTest.php b/tests/Unit/Models/ActivityTest.php index 96bd2c8..5187594 100644 --- a/tests/Unit/Models/ActivityTest.php +++ b/tests/Unit/Models/ActivityTest.php @@ -5,6 +5,8 @@ namespace Tests\Unit\Models; use App\Entities\Projects\Job; use App\Entities\Projects\Project; use App\Entities\Projects\Task; +use App\Entities\Users\Activity; +use App\Entities\Users\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -213,4 +215,36 @@ class ActivityTest extends TestCase ]), ]); } + + /** @test */ + public function an_activity_has_belongs_to_user_relation() + { + $project = factory(Project::class)->create(); + $activity = Activity::where('object_type', 'projects') + ->where('object_id', $project->id) + ->first(); + + $this->assertInstanceOf(User::class, $activity->user); + $this->assertEquals($activity->user_id, $activity->user->id); + } + + /** @test */ + public function an_activity_has_belongs_to_object_relation() + { + $project = factory(Project::class)->create(); + $job = factory(Job::class)->create(['project_id' => $project->id]); + $task = factory(Task::class)->create(['job_id' => $job->id]); + + $projectActivity = Activity::where('object_type', 'projects')->first(); + $this->assertInstanceOf(Project::class, $projectActivity->object); + $this->assertEquals($projectActivity->object_id, $projectActivity->object->id); + + $jobActivity = Activity::where('object_type', 'jobs')->first(); + $this->assertInstanceOf(Job::class, $jobActivity->object); + $this->assertEquals($jobActivity->object_id, $jobActivity->object->id); + + $taskActivity = Activity::where('object_type', 'tasks')->first(); + $this->assertInstanceOf(Task::class, $taskActivity->object); + $this->assertEquals($taskActivity->object_id, $taskActivity->object->id); + } }