diff --git a/.env.example b/.env.example index 73273f2..4ebf478 100644 --- a/.env.example +++ b/.env.example @@ -35,4 +35,9 @@ PUSHER_APP_KEY= PUSHER_APP_SECRET= MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" -MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" \ No newline at end of file +MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" + +LEAFLET_MAP_ZOOM_LEVEL=4 +LEAFLET_MAP_DETAIL_ZOOM_LEVEL=18 +LEAFLET_MAP_CENTER_LATITUDE="-0.87887" +LEAFLET_MAP_CENTER_LONGITUDE="117.4863" \ No newline at end of file diff --git a/app/Http/Controllers/UsersController.php b/app/Http/Controllers/UsersController.php index 9152e32..f825893 100644 --- a/app/Http/Controllers/UsersController.php +++ b/app/Http/Controllers/UsersController.php @@ -6,7 +6,10 @@ use App\Couple; use App\Http\Requests\Users\UpdateRequest; use App\Jobs\Users\DeleteAndReplaceUser; use App\User; +use App\UserMetadata; use Illuminate\Http\Request; +use Illuminate\Support\Collection; +use Ramsey\Uuid\Uuid; use Storage; class UsersController extends Controller @@ -97,6 +100,21 @@ class UsersController extends Controller } /** + * Show user death info. + * + * @param \App\User $user + * @return \Illuminate\View\View + */ + public function death(User $user) + { + $mapZoomLevel = config('leaflet.detail_zoom_level'); + $mapCenterLatitude = $user->getMetadata('cemetery_location_latitude'); + $mapCenterLongitude = $user->getMetadata('cemetery_location_longitude'); + + return view('users.death', compact('user', 'mapZoomLevel', 'mapCenterLatitude', 'mapCenterLongitude')); + } + + /** * Show the form for editing the specified User. * * @param \App\User $user @@ -113,7 +131,18 @@ class UsersController extends Controller $validTabs = ['death', 'contact_address', 'login_account']; - return view('users.edit', compact('user', 'replacementUsers', 'validTabs')); + $mapZoomLevel = config('leaflet.zoom_level'); + $mapCenterLatitude = $user->getMetadata('cemetery_location_latitude'); + $mapCenterLongitude = $user->getMetadata('cemetery_location_longitude'); + if ($mapCenterLatitude && $mapCenterLongitude) { + $mapZoomLevel = config('leaflet.detail_zoom_level'); + } + $mapCenterLatitude = $mapCenterLatitude ?: config('leaflet.map_center_latitude'); + $mapCenterLongitude = $mapCenterLongitude ?: config('leaflet.map_center_longitude'); + + return view('users.edit', compact( + 'user', 'replacementUsers', 'validTabs', 'mapZoomLevel', 'mapCenterLatitude', 'mapCenterLongitude' + )); } /** @@ -125,7 +154,11 @@ class UsersController extends Controller */ public function update(UpdateRequest $request, User $user) { - $user->update($request->validated()); + $userAttributes = $request->validated(); + $user->update($userAttributes); + $userAttributes = collect($userAttributes); + + $this->updateUserMetadata($user, $userAttributes); return redirect()->route('users.show', $user->id); } @@ -232,4 +265,19 @@ class UsersController extends Controller return $allMariageList; } + + private function updateUserMetadata(User $user, Collection $userAttributes) + { + foreach (User::METADATA_KEYS as $key) { + if ($userAttributes->has($key) == false) { + continue; + } + $userMeta = UserMetadata::firstOrNew(['user_id' => $user->id, 'key' => $key]); + if (!$userMeta->exists) { + $userMeta->id = Uuid::uuid4()->toString(); + } + $userMeta->value = $userAttributes->get($key); + $userMeta->save(); + } + } } diff --git a/app/Http/Requests/Users/UpdateRequest.php b/app/Http/Requests/Users/UpdateRequest.php index ba34ff6..ad4d219 100644 --- a/app/Http/Requests/Users/UpdateRequest.php +++ b/app/Http/Requests/Users/UpdateRequest.php @@ -39,6 +39,11 @@ class UpdateRequest extends FormRequest 'email' => 'nullable|string|max:255', 'password' => 'nullable|min:6|max:15', 'birth_order' => 'nullable|numeric|min:1', + + 'cemetery_location_name' => 'nullable|string|max:255', + 'cemetery_location_address' => 'nullable|string|max:255', + 'cemetery_location_latitude' => 'required_with:cemetery_location_longitude|nullable|string|max:255', + 'cemetery_location_longitude' => 'required_with:cemetery_location_latitude|nullable|string|max:255', ]; } diff --git a/app/User.php b/app/User.php index 26bafb6..aaca300 100644 --- a/app/User.php +++ b/app/User.php @@ -11,6 +11,13 @@ class User extends Authenticatable { use Notifiable; + const METADATA_KEYS = [ + 'cemetery_location_name', + 'cemetery_location_address', + 'cemetery_location_latitude', + 'cemetery_location_longitude', + ]; + /** * Indicates if the IDs are auto-incrementing. * @@ -314,4 +321,33 @@ class User extends Authenticatable return Carbon::now()->diffInDays($this->birthday, false); } } + + public function metadata() + { + return $this->hasMany(UserMetadata::class, 'user_id', 'id'); + } + + public function getMetadata($key = null, $defaultValue = null) + { + $metadata = $this->metadata; + + if (is_null($key)) { + $metadataCollection = []; + foreach ($metadata as $metaKey => $metaValue) { + $metadataCollection[$metaKey] = $metaValue; + } + + return collect($metadataCollection); + } + + $meta = $metadata->filter(function ($meta) use ($key) { + return $meta->key == $key; + })->first(); + + if ($meta) { + return $meta->value; + } + + return $defaultValue; + } } diff --git a/app/UserMetadata.php b/app/UserMetadata.php new file mode 100644 index 0000000..29a3c87 --- /dev/null +++ b/app/UserMetadata.php @@ -0,0 +1,14 @@ + env('LEAFLET_MAP_ZOOM_LEVEL', 4), + 'detail_zoom_level' => env('LEAFLET_MAP_DETAIL_ZOOM_LEVEL', 18), + 'map_center_latitude' => env('LEAFLET_MAP_CENTER_LATITUDE', '-0.87887'), + 'map_center_longitude' => env('LEAFLET_MAP_CENTER_LONGITUDE', '117.4863'), +]; diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 4e2bf11..93db9b8 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -2,6 +2,7 @@ use App\Couple; use App\User; +use App\UserMetadata; /* |-------------------------------------------------------------------------- @@ -18,10 +19,10 @@ use App\User; $factory->define(User::class, function (Faker\Generator $faker) { $name = $faker->name; return [ - 'id' => $faker->uuid, - 'name' => $name, - 'nickname' => $name, - 'gender_id' => rand(1, 2), + 'id' => $faker->uuid, + 'name' => $name, + 'nickname' => $name, + 'gender_id' => rand(1, 2), 'manager_id' => $faker->uuid, ]; }); @@ -36,15 +37,26 @@ $factory->state(User::class, 'female', function (Faker\Generator $faker) { $factory->define(Couple::class, function (Faker\Generator $faker) { return [ - 'id' => $faker->uuid, + 'id' => $faker->uuid, 'husband_id' => function () { return factory(User::class)->states('male')->create()->id; }, - 'wife_id' => function () { + 'wife_id' => function () { return factory(User::class)->states('female')->create()->id; }, 'manager_id' => function () { return factory(User::class)->create()->id; }, ]; -}); \ No newline at end of file +}); + +$factory->define(UserMetadata::class, function (Faker\Generator $faker) { + return [ + 'id' => $faker->uuid, + 'user_id' => function () { + return factory(User::class)->create()->id; + }, + 'key' => $faker->name, + 'value' => $faker->sentence, + ]; +}); diff --git a/database/migrations/2021_04_04_215601_create_user_metadata_table.php b/database/migrations/2021_04_04_215601_create_user_metadata_table.php new file mode 100644 index 0000000..c4be9a2 --- /dev/null +++ b/database/migrations/2021_04_04_215601_create_user_metadata_table.php @@ -0,0 +1,36 @@ +uuid('id')->primary(); + $table->uuid('user_id'); + $table->string('key')->index(); + $table->text('value')->nullable(); + $table->timestamps(); + + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_metadata'); + } +} diff --git a/resources/lang/en/address.php b/resources/lang/en/address.php new file mode 100644 index 0000000..fa89ac2 --- /dev/null +++ b/resources/lang/en/address.php @@ -0,0 +1,8 @@ + 'Address', + 'location_name' => 'Location Name', + 'latitude' => 'Latitude', + 'longitude' => 'Longitude', +]; diff --git a/resources/lang/en/app.php b/resources/lang/en/app.php index 37eeb93..fb6ef17 100644 --- a/resources/lang/en/app.php +++ b/resources/lang/en/app.php @@ -39,4 +39,7 @@ return [ 'download' => 'Download', 'delete' => 'Delete', 'cancel' => 'Cancel', + + 'open_in_google_map' => 'Open in Google Map', + 'data_not_available' => 'Data not available.', ]; \ No newline at end of file diff --git a/resources/lang/en/user.php b/resources/lang/en/user.php index e612bd0..db08fb0 100644 --- a/resources/lang/en/user.php +++ b/resources/lang/en/user.php @@ -51,6 +51,8 @@ return [ 'phone' => 'Phone', 'manager' => 'Manager', + 'cemetery_location' => 'Cemetary Location', + // Photo 'reupload_photo' => 'Re-upload Photo', 'update_photo' => 'Update Photo', diff --git a/resources/lang/id/address.php b/resources/lang/id/address.php new file mode 100644 index 0000000..521a206 --- /dev/null +++ b/resources/lang/id/address.php @@ -0,0 +1,8 @@ + 'Alamat', + 'location_name' => 'Nama Lokasi', + 'latitude' => 'Latitude', + 'longitude' => 'Longitude', +]; diff --git a/resources/lang/id/app.php b/resources/lang/id/app.php index 425eeeb..cfbf474 100644 --- a/resources/lang/id/app.php +++ b/resources/lang/id/app.php @@ -39,4 +39,7 @@ return [ 'download' => 'Download', 'delete' => 'Hapus', 'cancel' => 'Batal', + + 'open_in_google_map' => 'Buka di Google Map', + 'data_not_available' => 'Data tidak tersedia.', ]; \ No newline at end of file diff --git a/resources/lang/id/user.php b/resources/lang/id/user.php index 8668553..cbde562 100644 --- a/resources/lang/id/user.php +++ b/resources/lang/id/user.php @@ -51,6 +51,8 @@ return [ 'phone' => 'Telp.', 'manager' => 'Pengelola', + 'cemetery_location' => 'Lokasi Makam', + // Photo 'reupload_photo' => 'Upload ulang Foto', 'update_photo' => 'Update Foto', diff --git a/resources/views/users/death.blade.php b/resources/views/users/death.blade.php new file mode 100644 index 0000000..2ceb4f5 --- /dev/null +++ b/resources/views/users/death.blade.php @@ -0,0 +1,91 @@ +@extends('layouts.user-profile') + +@section('subtitle', __('user.death')) + +@section('user-content') +
+
+
+
+ @can('edit', $user) + {{ link_to_route('users.edit', __('app.edit'), [$user->id, 'tab' => 'death'], ['class' => 'pull-right']) }} + @endcan +

{{ __('user.death') }}

+
+ + + + + + + + + + + + + + + + + + + +
{{ __('address.location_name') }}{{ $user->getMetadata('cemetery_location_name') }}
{{ __('address.address') }}{{ $user->getMetadata('cemetery_location_address') }}
{{ __('user.dod') }}{{ $user->dod ?: $user->yod }}
{{ __('user.age') }} + @if ($user->age) + {!! $user->age_string !!} + @endif +
+
+
+
+
+

{{ __('user.cemetery_location') }}

+ @if ($mapCenterLatitude && $mapCenterLongitude) +
+ + @else +
{{ __('app.data_not_available') }}
+ @endif +
+
+
+@endsection + +@if ($mapCenterLatitude && $mapCenterLongitude) + @section('ext_css') + + + + @endsection + + @section('script') + + + + @endsection +@endif \ No newline at end of file diff --git a/resources/views/users/edit.blade.php b/resources/views/users/edit.blade.php index ab80a45..334f9a7 100644 --- a/resources/views/users/edit.blade.php +++ b/resources/views/users/edit.blade.php @@ -34,6 +34,9 @@ {{ Form::close() }}
@includeWhen(request('tab') == null || !in_array(request('tab'), $validTabs), 'users.partials.update_photo') + @if (request('tab') == 'death') +
+ @endif
@@ -43,10 +46,26 @@ @section('ext_css') + +@if (request('tab') == 'death') + + + +@endif @endsection @section('script') +@if (request('tab') == 'death') + +@endif + @endsection diff --git a/resources/views/users/partials/action-buttons.blade.php b/resources/views/users/partials/action-buttons.blade.php index 4b78ac1..691370a 100644 --- a/resources/views/users/partials/action-buttons.blade.php +++ b/resources/views/users/partials/action-buttons.blade.php @@ -6,4 +6,7 @@ {{ link_to_route('users.chart', trans('app.show_family_chart'), [$user->id], ['class' => Request::segment(3) == 'chart' ? 'btn btn-default active' : 'btn btn-default']) }} {{ link_to_route('users.tree', trans('app.show_family_tree'), [$user->id], ['class' => Request::segment(3) == 'tree' ? 'btn btn-default active' : 'btn btn-default']) }} {{ link_to_route('users.marriages', trans('app.show_marriages'), [$user->id], ['class' => Request::segment(3) == 'marriages' ? 'btn btn-default active' : 'btn btn-default']) }} - \ No newline at end of file + @if ($user->yod) + {{ link_to_route('users.death', trans('user.death'), [$user->id], ['class' => Request::segment(3) == 'death' ? 'btn btn-default active' : 'btn btn-default']) }} + @endif + diff --git a/resources/views/users/partials/edit_death.blade.php b/resources/views/users/partials/edit_death.blade.php index 6e8ff6e..fff0c56 100644 --- a/resources/views/users/partials/edit_death.blade.php +++ b/resources/views/users/partials/edit_death.blade.php @@ -2,3 +2,13 @@
{!! FormField::text('yod', ['label' => __('user.yod'), 'placeholder' => __('app.example').' 2003']) !!}
{!! FormField::text('dod', ['label' => __('user.dod'), 'placeholder' => __('app.example').' 2003-10-17']) !!}
+ +
+ {{ __('user.cemetery_location') }} + {!! FormField::text('cemetery_location_name', ['label' => __('address.location_name'), 'value' => old('cemetery_location_name', $user->getMetadata('cemetery_location_name'))]) !!} + {!! FormField::textarea('cemetery_location_address', ['label' => __('address.address'), 'value' => old('cemetery_location_address', $user->getMetadata('cemetery_location_address'))]) !!} +
+
{!! FormField::text('cemetery_location_latitude', ['label' => __('address.latitude'), 'value' => old('cemetery_location_latitude', $user->getMetadata('cemetery_location_latitude'))]) !!}
+
{!! FormField::text('cemetery_location_longitude', ['label' => __('address.longitude'), 'value' => old('cemetery_location_longitude', $user->getMetadata('cemetery_location_longitude'))]) !!}
+
+
diff --git a/routes/web.php b/routes/web.php index 7403c45..6f9bfd6 100644 --- a/routes/web.php +++ b/routes/web.php @@ -35,6 +35,7 @@ Route::get('users/{user}/edit', 'UsersController@edit')->name('users.edit'); Route::patch('users/{user}', 'UsersController@update')->name('users.update'); Route::get('users/{user}/chart', 'UsersController@chart')->name('users.chart'); Route::get('users/{user}/tree', 'UsersController@tree')->name('users.tree'); +Route::get('users/{user}/death', 'UsersController@death')->name('users.death'); Route::patch('users/{user}/photo-upload', 'UsersController@photoUpload')->name('users.photo-upload'); Route::delete('users/{user}', 'UsersController@destroy')->name('users.destroy'); diff --git a/tests/Feature/UsersProfileTest.php b/tests/Feature/UsersProfileTest.php index 420c427..219680f 100644 --- a/tests/Feature/UsersProfileTest.php +++ b/tests/Feature/UsersProfileTest.php @@ -4,6 +4,8 @@ namespace Tests\Feature; use App\User; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\DB; +use Ramsey\Uuid\Uuid; use Storage; use Tests\TestCase; @@ -168,6 +170,72 @@ class UsersProfileTest extends TestCase } /** @test */ + public function user_can_update_died_person_cemetary_location() + { + $user = $this->loginAsUser(); + $this->visit(route('users.edit', [$user->id, 'tab' => 'death'])); + $this->seePageIs(route('users.edit', [$user->id, 'tab' => 'death'])); + + $this->submitForm(trans('app.update'), [ + 'dod' => '', + 'yod' => '2003', + 'cemetery_location_name' => 'Some name', + 'cemetery_location_address' => 'Some address', + 'cemetery_location_latitude' => '-3.333333', + 'cemetery_location_longitude' => '114.583333', + ]); + + $this->seeInDatabase('users', [ + 'id' => $user->id, + 'dod' => null, + 'yod' => '2003', + ]); + + $this->seeInDatabase('user_metadata', [ + 'user_id' => $user->id, + 'key' => 'cemetery_location_name', + 'value' => 'Some name', + ]); + + $this->seeInDatabase('user_metadata', [ + 'user_id' => $user->id, + 'key' => 'cemetery_location_address', + 'value' => 'Some address', + ]); + + $this->seeInDatabase('user_metadata', [ + 'user_id' => $user->id, + 'key' => 'cemetery_location_latitude', + 'value' => '-3.333333', + ]); + + $this->seeInDatabase('user_metadata', [ + 'user_id' => $user->id, + 'key' => 'cemetery_location_longitude', + 'value' => '114.583333', + ]); + } + + /** @test */ + public function user_metadata_can_be_prefilled_on_the_edit_form() + { + $user = $this->loginAsUser(); + DB::table('user_metadata')->insert([ + 'id' => Uuid::uuid4()->toString(), + 'user_id' => $user->id, + 'key' => 'cemetery_location_name', + 'value' => 'Some place name', + ]); + + $this->visit(route('users.edit', [$user->id, 'tab' => 'death'])); + $this->seePageIs(route('users.edit', [$user->id, 'tab' => 'death'])); + $this->seeElement('input', [ + 'name' => 'cemetery_location_name', + 'value' => 'Some place name', + ]); + } + + /** @test */ public function manager_can_add_login_account_on_a_user() { $manager = $this->loginAsUser(); diff --git a/tests/Unit/UserTest.php b/tests/Unit/UserTest.php index 000dba1..5ead72a 100644 --- a/tests/Unit/UserTest.php +++ b/tests/Unit/UserTest.php @@ -4,9 +4,12 @@ namespace Tests\Unit; use App\Couple; use App\User; +use App\UserMetadata; use Carbon\Carbon; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Foundation\Testing\RefreshDatabase; -use Illuminate\Support\Collection; +use Illuminate\Support\Facades\DB; +use Ramsey\Uuid\Nonstandard\Uuid; use Tests\TestCase; class UserTest extends TestCase @@ -110,6 +113,70 @@ class UserTest extends TestCase } /** @test */ + public function a_user_has_many_metadata_relation() + { + $user = factory(User::class)->create(); + $metadata = factory(UserMetadata::class)->create(['user_id' => $user->id]); + + $this->assertInstanceOf(Collection::class, $user->metadata); + $this->assertInstanceOf(UserMetadata::class, $user->metadata->first()); + } + + /** @test */ + public function user_model_has_get_metadata_method() + { + $user = factory(User::class)->create(); + + $this->assertNull($user->getMetadata('cemetery_location_address')); + + DB::table('user_metadata')->insert([ + 'id' => Uuid::uuid4()->toString(), + 'user_id' => $user->id, + 'key' => 'cemetery_location_address', + 'value' => 'Some address', + ]); + $user = $user->fresh(); + + $this->assertEquals('Some address', $user->getMetadata('cemetery_location_address')); + } + + /** @test */ + public function user_model_get_metadata_method_returns_all_metadata_if_key_is_null() + { + $user = factory(User::class)->create(); + + $this->assertEmpty($user->getMetadata()); + + DB::table('user_metadata')->insert([ + 'id' => Uuid::uuid4()->toString(), + 'user_id' => $user->id, + 'key' => 'cemetery_location_address', + 'value' => 'Some address', + ]); + $user = $user->fresh(); + + $this->assertCount(1, $user->getMetadata()); + } + + /** @test */ + public function user_model_get_metadata_method_accepts_a_default_value() + { + $user = factory(User::class)->create(); + + $this->assertEquals('Default value', $user->getMetadata('some_missing_key', 'Default value')); + + DB::table('user_metadata')->insert([ + 'id' => Uuid::uuid4()->toString(), + 'user_id' => $user->id, + 'key' => 'some_missing_key', + 'value' => 'Some value', + ]); + $user = $user->fresh(); + + $this->assertEquals('Some value', $user->getMetadata('some_missing_key', 'Default value')); + } + + /** @test */ public function user_have_mother_link_method() { $mother = factory(User::class)->create();