feat: add distance to life events (monicahq/chandler#431)
This commit is contained in:
@@ -39,7 +39,8 @@ class CreateLifeEvent extends BaseService implements ServiceInterface
|
||||
'currency_id' => 'nullable|integer|exists:currencies,id',
|
||||
'paid_by_contact_id' => 'nullable|integer|exists:contacts,id',
|
||||
'duration_in_minutes' => 'nullable|integer',
|
||||
'distance_in_km' => 'nullable|integer',
|
||||
'distance' => 'nullable|integer',
|
||||
'distance_unit' => 'nullable|string|max:255',
|
||||
'from_place' => 'nullable|string|max:255',
|
||||
'to_place' => 'nullable|string|max:255',
|
||||
'place' => 'nullable|string|max:255',
|
||||
@@ -129,7 +130,8 @@ class CreateLifeEvent extends BaseService implements ServiceInterface
|
||||
'currency_id' => $this->valueOrNull($this->data, 'currency_id'),
|
||||
'paid_by_contact_id' => $this->valueOrNull($this->data, 'paid_by_contact_id'),
|
||||
'duration_in_minutes' => $this->valueOrNull($this->data, 'duration_in_minutes'),
|
||||
'distance_in_km' => $this->valueOrNull($this->data, 'distance_in_km'),
|
||||
'distance' => $this->valueOrNull($this->data, 'distance'),
|
||||
'distance_unit' => $this->valueOrNull($this->data, 'distance_unit'),
|
||||
'from_place' => $this->valueOrNull($this->data, 'from_place'),
|
||||
'to_place' => $this->valueOrNull($this->data, 'to_place'),
|
||||
'place' => $this->valueOrNull($this->data, 'place'),
|
||||
|
||||
@@ -40,7 +40,8 @@ class UpdateLifeEvent extends BaseService implements ServiceInterface
|
||||
'currency_id' => 'nullable|integer|exists:currencies,id',
|
||||
'paid_by_contact_id' => 'nullable|integer|exists:contacts,id',
|
||||
'duration_in_minutes' => 'nullable|integer',
|
||||
'distance_in_km' => 'nullable|integer',
|
||||
'distance' => 'nullable|integer',
|
||||
'distance_unit' => 'nullable|string|max:255',
|
||||
'from_place' => 'nullable|string|max:255',
|
||||
'to_place' => 'nullable|string|max:255',
|
||||
'place' => 'nullable|string|max:255',
|
||||
@@ -114,7 +115,8 @@ class UpdateLifeEvent extends BaseService implements ServiceInterface
|
||||
$this->lifeEvent->currency_id = $this->valueOrNull($this->data, 'currency_id');
|
||||
$this->lifeEvent->paid_by_contact_id = $this->valueOrNull($this->data, 'paid_by_contact_id');
|
||||
$this->lifeEvent->duration_in_minutes = $this->valueOrNull($this->data, 'duration_in_minutes');
|
||||
$this->lifeEvent->distance_in_km = $this->valueOrNull($this->data, 'distance_in_km');
|
||||
$this->lifeEvent->distance = $this->valueOrNull($this->data, 'distance');
|
||||
$this->lifeEvent->distance_unit = $this->valueOrNull($this->data, 'distance_unit');
|
||||
$this->lifeEvent->from_place = $this->valueOrNull($this->data, 'from_place');
|
||||
$this->lifeEvent->to_place = $this->valueOrNull($this->data, 'to_place');
|
||||
$this->lifeEvent->place = $this->valueOrNull($this->data, 'place');
|
||||
|
||||
+2
-1
@@ -40,7 +40,8 @@ class ContactModuleLifeEventController extends Controller
|
||||
'currency_id' => $request->input('currency_id'),
|
||||
'paid_by_contact_id' => $request->input('paid_by_contact_id'),
|
||||
'duration_in_minutes' => $request->input('duration_in_minutes'),
|
||||
'distance_in_km' => $request->input('distance_in_km'),
|
||||
'distance' => $request->input('distance'),
|
||||
'distance_unit' => $request->input('distance_unit'),
|
||||
'from_place' => $request->input('from_place'),
|
||||
'to_place' => $request->input('to_place'),
|
||||
'place' => $request->input('place'),
|
||||
|
||||
+2
-1
@@ -67,7 +67,8 @@ class ContactModuleTimelineEventController extends Controller
|
||||
'currency_id' => $request->input('currency_id'),
|
||||
'paid_by_contact_id' => $request->input('paid_by_contact_id'),
|
||||
'duration_in_minutes' => $request->input('duration_in_minutes'),
|
||||
'distance_in_km' => $request->input('distance_in_km'),
|
||||
'distance' => $request->input('distance'),
|
||||
'distance_unit' => $request->input('distance_unit'),
|
||||
'from_place' => $request->input('from_place'),
|
||||
'to_place' => $request->input('to_place'),
|
||||
'place' => $request->input('place'),
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Domains\Contact\ManageLifeEvents\Web\ViewHelpers;
|
||||
|
||||
use App\Helpers\ContactCardHelper;
|
||||
use App\Helpers\DateHelper;
|
||||
use App\Helpers\DistanceHelper;
|
||||
use App\Models\Contact;
|
||||
use App\Models\LifeEvent;
|
||||
use App\Models\LifeEventCategory;
|
||||
@@ -113,7 +114,8 @@ class ModuleLifeEventViewHelper
|
||||
'currency_id' => $lifeEvent->currency_id,
|
||||
'paid_by_contact_id' => $lifeEvent->paid_by_contact_id,
|
||||
'duration_in_minutes' => $lifeEvent->duration_in_minutes,
|
||||
'distance_in_km' => $lifeEvent->distance_in_km,
|
||||
'distance' => $lifeEvent->distance && $lifeEvent->distance_unit ? DistanceHelper::format($user, $lifeEvent->distance, $lifeEvent->distance_unit) : null,
|
||||
'distance_unit' => $lifeEvent->distance_unit,
|
||||
'from_place' => $lifeEvent->from_place,
|
||||
'to_place' => $lifeEvent->to_place,
|
||||
'place' => $lifeEvent->place,
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Settings\ManageUserPreferences\Services;
|
||||
|
||||
use App\Interfaces\ServiceInterface;
|
||||
use App\Models\User;
|
||||
use App\Services\BaseService;
|
||||
|
||||
class StoreDistanceFormatPreference extends BaseService implements ServiceInterface
|
||||
{
|
||||
private array $data;
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the service.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'account_id' => 'required|integer|exists:accounts,id',
|
||||
'author_id' => 'required|integer|exists:users,id',
|
||||
'distance_format' => 'required|string|max:255',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the permissions that apply to the user calling the service.
|
||||
*/
|
||||
public function permissions(): array
|
||||
{
|
||||
return [
|
||||
'author_must_belong_to_account',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Store distance format preferences for the given user.
|
||||
*/
|
||||
public function execute(array $data): User
|
||||
{
|
||||
$this->data = $data;
|
||||
|
||||
$this->validateRules($data);
|
||||
$this->updateUser();
|
||||
|
||||
return $this->author;
|
||||
}
|
||||
|
||||
private function updateUser(): void
|
||||
{
|
||||
$this->author->distance_format = $this->data['distance_format'];
|
||||
$this->author->save();
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Settings\ManageUserPreferences\Web\Controllers;
|
||||
|
||||
use App\Domains\Settings\ManageUserPreferences\Services\StoreDistanceFormatPreference;
|
||||
use App\Domains\Settings\ManageUserPreferences\Web\ViewHelpers\UserPreferencesIndexViewHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class PreferencesDistanceFormatController extends Controller
|
||||
{
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = [
|
||||
'account_id' => Auth::user()->account_id,
|
||||
'author_id' => Auth::id(),
|
||||
'distance_format' => $request->input('distanceFormat'),
|
||||
];
|
||||
|
||||
$user = (new StoreDistanceFormatPreference())->execute($data);
|
||||
|
||||
return response()->json([
|
||||
'data' => UserPreferencesIndexViewHelper::dtoDistanceFormat($user),
|
||||
], 200);
|
||||
}
|
||||
}
|
||||
+11
@@ -17,6 +17,7 @@ class UserPreferencesIndexViewHelper
|
||||
'date_format' => self::dtoDateFormat($user),
|
||||
'timezone' => self::dtoTimezone($user),
|
||||
'number_format' => self::dtoNumberFormat($user),
|
||||
'distance_format' => self::dtoDistanceFormat($user),
|
||||
'maps' => self::dtoMapsPreferences($user),
|
||||
'locale' => self::dtoLocale($user),
|
||||
'url' => [
|
||||
@@ -131,6 +132,16 @@ class UserPreferencesIndexViewHelper
|
||||
];
|
||||
}
|
||||
|
||||
public static function dtoDistanceFormat(User $user): array
|
||||
{
|
||||
return [
|
||||
'number_format' => $user->distance_format,
|
||||
'url' => [
|
||||
'store' => route('settings.preferences.distance.store'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public static function dtoMapsPreferences(User $user): array
|
||||
{
|
||||
$collection = collect();
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class DistanceHelper
|
||||
{
|
||||
/**
|
||||
* Format the distance according to the preferences of the user.
|
||||
*/
|
||||
public static function format(User $user, int $distance, string $unit): string
|
||||
{
|
||||
if ($user->distance_format === User::DISTANCE_UNIT_KM && $unit === User::DISTANCE_UNIT_MILES) {
|
||||
$distance = round($distance * 1.609344, 2);
|
||||
} elseif ($user->distance_format === User::DISTANCE_UNIT_MILES && $unit === User::DISTANCE_UNIT_KM) {
|
||||
$distance = round($distance / 1.609344, 2);
|
||||
}
|
||||
|
||||
return trans('app.distance_format_'.$user->distance_format, ['distance' => $distance]);
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,8 @@ class LifeEvent extends Model
|
||||
'currency_id',
|
||||
'paid_by_contact_id',
|
||||
'duration_in_minutes',
|
||||
'distance_in_km',
|
||||
'distance',
|
||||
'distance_unit',
|
||||
'from_place',
|
||||
'to_place',
|
||||
'place',
|
||||
|
||||
@@ -40,6 +40,13 @@ class User extends Authenticatable implements MustVerifyEmail, HasLocalePreferen
|
||||
|
||||
public const MAPS_SITE_OPEN_STREET_MAPS = 'open_street_maps';
|
||||
|
||||
/**
|
||||
* Possible distance unit.
|
||||
*/
|
||||
public const DISTANCE_UNIT_MILES = 'mi';
|
||||
|
||||
public const DISTANCE_UNIT_KM = 'km';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
@@ -58,6 +65,7 @@ class User extends Authenticatable implements MustVerifyEmail, HasLocalePreferen
|
||||
'name_order',
|
||||
'date_format',
|
||||
'number_format',
|
||||
'distance_format',
|
||||
'timezone',
|
||||
'default_map_site',
|
||||
'locale',
|
||||
|
||||
@@ -32,7 +32,7 @@ class LifeEventFactory extends Factory
|
||||
'currency_id' => null,
|
||||
'paid_by_contact_id' => Contact::factory(),
|
||||
'duration_in_minutes' => $this->faker->randomNumber(),
|
||||
'distance_in_km' => $this->faker->randomNumber(),
|
||||
'distance' => $this->faker->randomNumber(),
|
||||
'from_place' => $this->faker->city(),
|
||||
'to_place' => $this->faker->city(),
|
||||
'place' => $this->faker->city(),
|
||||
|
||||
@@ -25,6 +25,7 @@ return new class() extends Migration
|
||||
$table->string('timezone')->nullable();
|
||||
$table->string('number_format')->default(User::NUMBER_FORMAT_TYPE_COMMA_THOUSANDS_DOT_DECIMAL);
|
||||
$table->string('default_map_site')->default(User::MAPS_SITE_OPEN_STREET_MAPS);
|
||||
$table->string('distance_format')->default(User::DISTANCE_UNIT_MILES);
|
||||
$table->string('password')->nullable();
|
||||
$table->boolean('is_account_administrator')->default(false);
|
||||
$table->boolean('help_shown')->default(true);
|
||||
|
||||
@@ -74,7 +74,8 @@ return new class() extends Migration
|
||||
$table->unsignedBigInteger('currency_id')->nullable();
|
||||
$table->unsignedBigInteger('paid_by_contact_id')->nullable();
|
||||
$table->integer('duration_in_minutes')->nullable();
|
||||
$table->integer('distance_in_km')->nullable();
|
||||
$table->integer('distance')->nullable();
|
||||
$table->char('distance_unit', 2)->nullable();
|
||||
$table->string('from_place')->nullable();
|
||||
$table->string('to_place')->nullable();
|
||||
$table->string('place')->nullable();
|
||||
|
||||
@@ -133,4 +133,7 @@ return [
|
||||
|
||||
'min_read' => '{count} min read',
|
||||
'word_count' => '{count} words',
|
||||
|
||||
'distance_format_km' => ':distance km',
|
||||
'distance_format_mi' => ':distance miles',
|
||||
];
|
||||
|
||||
@@ -39,6 +39,11 @@ return [
|
||||
'user_preferences_number_format_title' => 'How should we display numerical values',
|
||||
'user_preferences_number_format_description' => 'Current way of displaying numbers:',
|
||||
|
||||
'user_preferences_distance_format_title' => 'How should we display distance values',
|
||||
'user_preferences_distance_format_description' => 'Current way of displaying distances:',
|
||||
'user_preferences_distance_format_km' => 'kilometers (km)',
|
||||
'user_preferences_distance_format_miles' => 'miles (mi)',
|
||||
|
||||
'user_preferences_timezone_title' => 'Timezone',
|
||||
'user_preferences_timezone_description' => 'Regardless of where you are located in the world, have dates displayed in your own timezone.',
|
||||
'user_preferences_timezone_current' => 'Current timezone:',
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
|
||||
<number-format :data="data.number_format" />
|
||||
|
||||
<distance-format :data="data.distance_format" />
|
||||
|
||||
<timezone :data="data.timezone" />
|
||||
|
||||
<maps :data="data.maps" />
|
||||
@@ -56,6 +58,7 @@ import Layout from '@/Shared/Layout.vue';
|
||||
import NameOrder from '@/Pages/Settings/Preferences/Partials/NameOrder.vue';
|
||||
import DateFormat from '@/Pages/Settings/Preferences/Partials/DateFormat.vue';
|
||||
import NumberFormat from '@/Pages/Settings/Preferences/Partials/NumberFormat.vue';
|
||||
import DistanceFormat from '@/Pages/Settings/Preferences/Partials/DistanceFormat.vue';
|
||||
import Timezone from '@/Pages/Settings/Preferences/Partials/Timezone.vue';
|
||||
import Maps from '@/Pages/Settings/Preferences/Partials/Maps.vue';
|
||||
import Locale from '@/Pages/Settings/Preferences/Partials/Locale.vue';
|
||||
@@ -68,6 +71,7 @@ export default {
|
||||
DateFormat,
|
||||
Timezone,
|
||||
NumberFormat,
|
||||
DistanceFormat,
|
||||
Maps,
|
||||
Locale,
|
||||
HelpPreference,
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<div class="mb-16">
|
||||
<!-- title + cta -->
|
||||
<div class="mb-3 mt-8 items-center justify-between sm:mt-0 sm:flex">
|
||||
<h3 class="mb-4 flex font-semibold sm:mb-0">
|
||||
<span class="mr-1"> ✈️ </span>
|
||||
<span class="mr-2">
|
||||
{{ $t('settings.user_preferences_distance_format_title') }}
|
||||
</span>
|
||||
|
||||
<help :url="$page.props.help_links.settings_preferences_numerical_format" :top="'5px'" />
|
||||
</h3>
|
||||
<pretty-button v-if="!editMode" :text="$t('app.edit')" @click="enableEditMode" />
|
||||
</div>
|
||||
|
||||
<!-- normal mode -->
|
||||
<div v-if="!editMode" class="mb-6 rounded-lg border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-900">
|
||||
<p class="px-5 py-2">
|
||||
<span class="mb-2 block">{{ $t('settings.user_preferences_number_format_description') }}</span>
|
||||
<span class="mb-2 block rounded bg-slate-100 px-5 py-2 text-sm dark:bg-slate-900">{{
|
||||
localDistanceFormat
|
||||
}}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- edit mode -->
|
||||
<form
|
||||
v-if="editMode"
|
||||
class="bg-form mb-6 rounded-lg border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-900"
|
||||
@submit.prevent="submit()">
|
||||
<div class="border-b border-gray-200 px-5 py-2 dark:border-gray-700">
|
||||
<errors :errors="form.errors" />
|
||||
|
||||
<div class="mt-2 mb-2 flex items-center">
|
||||
<input
|
||||
id="km"
|
||||
v-model="form.distanceFormat"
|
||||
value="km"
|
||||
name="date-format"
|
||||
type="radio"
|
||||
class="h-4 w-4 border-gray-300 text-sky-500 dark:border-gray-700" />
|
||||
<label for="km" class="ml-3 block cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ $t('settings.user_preferences_distance_format_km') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-2 flex items-center">
|
||||
<input
|
||||
id="miles"
|
||||
v-model="form.distanceFormat"
|
||||
value="miles"
|
||||
name="date-format"
|
||||
type="radio"
|
||||
class="h-4 w-4 border-gray-300 text-sky-500 dark:border-gray-700" />
|
||||
<label for="miles" class="ml-3 block cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ $t('settings.user_preferences_distance_format_miles') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- actions -->
|
||||
<div class="flex justify-between p-5">
|
||||
<pretty-link :text="$t('app.cancel')" :classes="'mr-3'" @click="editMode = false" />
|
||||
<pretty-button :text="$t('app.save')" :state="loadingState" :icon="'check'" :classes="'save'" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PrettyButton from '@/Shared/Form/PrettyButton.vue';
|
||||
import PrettyLink from '@/Shared/Form/PrettyLink.vue';
|
||||
import Errors from '@/Shared/Form/Errors.vue';
|
||||
import Help from '@/Shared/Help.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PrettyButton,
|
||||
PrettyLink,
|
||||
Errors,
|
||||
Help,
|
||||
},
|
||||
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
loadingState: '',
|
||||
editMode: false,
|
||||
localDistanceFormat: '',
|
||||
form: {
|
||||
distanceFormat: '',
|
||||
errors: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.localDistanceFormat = this.data.number_format;
|
||||
this.form.distanceFormat = this.data.number_format;
|
||||
},
|
||||
|
||||
methods: {
|
||||
enableEditMode() {
|
||||
this.editMode = true;
|
||||
},
|
||||
|
||||
submit() {
|
||||
this.loadingState = 'loading';
|
||||
|
||||
axios
|
||||
.post(this.data.url.store, this.form)
|
||||
.then(() => {
|
||||
this.localDistanceFormat = this.form.distanceFormat;
|
||||
this.editMode = false;
|
||||
this.loadingState = null;
|
||||
})
|
||||
.catch((error) => {
|
||||
this.loadingState = null;
|
||||
this.form.errors = error.response.data;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
pre {
|
||||
background-color: #1f2937;
|
||||
color: #c9ef78;
|
||||
}
|
||||
|
||||
.example {
|
||||
border-bottom-left-radius: 9px;
|
||||
border-bottom-right-radius: 9px;
|
||||
}
|
||||
</style>
|
||||
@@ -130,7 +130,7 @@ defineProps({
|
||||
<!-- right -->
|
||||
<div class="p-3 sm:p-0">
|
||||
<!-- cta -->
|
||||
<div class="mb-6 flex justify-center">
|
||||
<div class="mb-8 flex justify-center">
|
||||
<pretty-link
|
||||
v-if="layoutData.vault.permission.at_least_editor"
|
||||
:href="data.url.create"
|
||||
@@ -150,6 +150,14 @@ defineProps({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- no slices of life yet -->
|
||||
<div
|
||||
v-if="data.slices.length == 0"
|
||||
class="mb-6 rounded-lg border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-900">
|
||||
<img src="/img/journal_slice_of_life_blank.svg" :alt="$t('Journal')" class="mx-auto mt-4 h-14 w-14" />
|
||||
<p class="px-5 pb-5 pt-2 text-center">Group journal entries together with slices of life.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<inertia-link :href="data.url.slice_index" class="text-sm text-blue-500 hover:underline"
|
||||
>View all slices</inertia-link
|
||||
|
||||
@@ -25,6 +25,8 @@ const form = useForm({
|
||||
participants: [],
|
||||
summary: null,
|
||||
description: null,
|
||||
distance: 0,
|
||||
distance_unit: 'km',
|
||||
});
|
||||
|
||||
const loadingState = ref(false);
|
||||
@@ -34,8 +36,10 @@ const editDate = ref(false);
|
||||
const modalShown = ref(false);
|
||||
const addSummaryFieldShown = ref(false);
|
||||
const addDescriptionFieldShown = ref(false);
|
||||
const addDistanceFieldShown = ref(false);
|
||||
const summaryField = ref(null);
|
||||
const descriptionField = ref(null);
|
||||
const distanceField = ref(null);
|
||||
|
||||
watch(
|
||||
() => props.openModal,
|
||||
@@ -55,8 +59,10 @@ const resetModal = () => {
|
||||
|
||||
form.summary = null;
|
||||
form.description = null;
|
||||
form.distance = 0;
|
||||
addSummaryFieldShown.value = false;
|
||||
addDescriptionFieldShown.value = false;
|
||||
addDistanceFieldShown.value = false;
|
||||
};
|
||||
|
||||
const loadTypes = (category) => {
|
||||
@@ -92,6 +98,15 @@ const showAddDescriptionField = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const showAddDistanceField = () => {
|
||||
form.distance = null;
|
||||
addDistanceFieldShown.value = true;
|
||||
|
||||
nextTick(() => {
|
||||
distanceField.value.focus();
|
||||
});
|
||||
};
|
||||
|
||||
const store = () => {
|
||||
loadingState.value = 'loading';
|
||||
|
||||
@@ -278,6 +293,57 @@ const store = () => {
|
||||
:textarea-class="'block w-full'" />
|
||||
</div>
|
||||
|
||||
<!-- description -->
|
||||
<div
|
||||
v-if="selectedLifeEventType && addDistanceFieldShown"
|
||||
class="flex items-center border-b border-gray-200 pt-3 pb-1 pr-3 pl-3 dark:border-gray-700">
|
||||
<text-input
|
||||
ref="distanceField"
|
||||
v-model="form.distance"
|
||||
:label="'Distance'"
|
||||
:type="'number'"
|
||||
:autofocus="true"
|
||||
:input-class="'mr-2'"
|
||||
:required="false"
|
||||
:autocomplete="false"
|
||||
:help="'Enter a number from 0 to 100000. No decimals.'"
|
||||
:min="0"
|
||||
:max="100000"
|
||||
@esc-key-pressed="addDistanceFieldShown = false" />
|
||||
|
||||
<ul>
|
||||
<li class="mr-5 inline-block">
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="km"
|
||||
v-model="form.distance_unit"
|
||||
value="km"
|
||||
name="distance_unit"
|
||||
type="radio"
|
||||
class="h-4 w-4 border-gray-300 text-sky-500 dark:border-gray-700" />
|
||||
<label for="km" class="ml-1 block cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
km
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="inline-block">
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="miles"
|
||||
v-model="form.distance_unit"
|
||||
value="miles"
|
||||
name="distance_unit"
|
||||
type="radio"
|
||||
class="h-4 w-4 border-gray-300 text-sky-500 dark:border-gray-700" />
|
||||
<label for="miles" class="ml-1 block cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
miles
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- options -->
|
||||
<div v-if="selectedLifeEventType" class="flex flex-wrap border-b border-gray-200 p-3 dark:border-gray-700">
|
||||
<!-- summary -->
|
||||
@@ -297,6 +363,15 @@ const store = () => {
|
||||
>+ description
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- distance -->
|
||||
<div v-if="!addDistanceFieldShown">
|
||||
<span
|
||||
class="mr-2 mb-2 cursor-pointer rounded-lg border bg-slate-200 px-1 py-1 text-sm hover:bg-slate-300 dark:text-gray-900"
|
||||
@click="showAddDistanceField"
|
||||
>+ distance
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between p-5">
|
||||
<pretty-span :text="$t('app.cancel')" :classes="'mr-3'" @click="$emit('closeModal')" />
|
||||
|
||||
@@ -261,22 +261,50 @@ const toggleLifeEventVisibility = (lifeEvent) => {
|
||||
{{ lifeEvent.description }}
|
||||
</div>
|
||||
|
||||
<!-- date of life event -->
|
||||
<!-- date of life event | distance -->
|
||||
<div v-if="!lifeEvent.collapsed" class="flex items-center border-b border-gray-200 px-3 py-2 text-sm">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="mr-1 h-4 w-4 text-gray-500">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<!-- date -->
|
||||
<div class="mr-4 flex items-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="mr-2 h-4 w-4 text-gray-500">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
|
||||
{{ lifeEvent.happened_at }}
|
||||
{{ lifeEvent.happened_at }}
|
||||
</div>
|
||||
|
||||
<!-- distance -->
|
||||
<div v-if="lifeEvent.distance" class="flex items-center">
|
||||
<svg
|
||||
class="mr-2 h-6 w-6 text-gray-500"
|
||||
viewBox="0 0 64 64"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
stroke-width="3"
|
||||
stroke="#000000"
|
||||
fill="none">
|
||||
<path
|
||||
d="M17.94,54.81a.1.1,0,0,1-.14,0c-1-1.11-11.69-13.23-11.69-21.26,0-9.94,6.5-12.24,11.76-12.24,4.84,0,11.06,2.6,11.06,12.24C28.93,41.84,18.87,53.72,17.94,54.81Z" />
|
||||
<circle cx="17.52" cy="31.38" r="4.75" />
|
||||
<path
|
||||
d="M49.58,34.77a.11.11,0,0,1-.15,0c-.87-1-9.19-10.45-9.19-16.74,0-7.84,5.12-9.65,9.27-9.65,3.81,0,8.71,2,8.71,9.65C58.22,24.52,50.4,33.81,49.58,34.77Z" />
|
||||
<circle cx="49.23" cy="17.32" r="3.75" />
|
||||
<path d="M17.87,54.89a28.73,28.73,0,0,0,3.9.89" />
|
||||
<path
|
||||
d="M24.68,56.07c2.79.12,5.85-.28,7.9-2.08,5.8-5.09,2.89-11.25,6.75-14.71a16.72,16.72,0,0,1,4.93-3"
|
||||
stroke-dasharray="7.8 2.92" />
|
||||
<path d="M45.63,35.8a23,23,0,0,1,3.88-.95" />
|
||||
</svg>
|
||||
|
||||
<span>{{ lifeEvent.distance }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- participants -->
|
||||
|
||||
@@ -85,6 +85,7 @@ use App\Domains\Settings\ManageTemplates\Web\Controllers\PersonalizeTemplatePage
|
||||
use App\Domains\Settings\ManageTemplates\Web\Controllers\PersonalizeTemplatesController;
|
||||
use App\Domains\Settings\ManageUserPreferences\Web\Controllers\PreferencesController;
|
||||
use App\Domains\Settings\ManageUserPreferences\Web\Controllers\PreferencesDateFormatController;
|
||||
use App\Domains\Settings\ManageUserPreferences\Web\Controllers\PreferencesDistanceFormatController;
|
||||
use App\Domains\Settings\ManageUserPreferences\Web\Controllers\PreferencesHelpController;
|
||||
use App\Domains\Settings\ManageUserPreferences\Web\Controllers\PreferencesLocaleController;
|
||||
use App\Domains\Settings\ManageUserPreferences\Web\Controllers\PreferencesMapsPreferenceController;
|
||||
@@ -511,6 +512,7 @@ Route::middleware([
|
||||
Route::post('date', [PreferencesDateFormatController::class, 'store'])->name('date.store');
|
||||
Route::post('timezone', [PreferencesTimezoneController::class, 'store'])->name('timezone.store');
|
||||
Route::post('number', [PreferencesNumberFormatController::class, 'store'])->name('number.store');
|
||||
Route::post('distance', [PreferencesDistanceFormatController::class, 'store'])->name('distance.store');
|
||||
Route::post('maps', [PreferencesMapsPreferenceController::class, 'store'])->name('maps.store');
|
||||
Route::post('locale', [PreferencesLocaleController::class, 'store'])->name('locale.store');
|
||||
Route::post('help', [PreferencesHelpController::class, 'store'])->name('help.store');
|
||||
|
||||
@@ -158,7 +158,8 @@ class CreateLifeEventTest extends TestCase
|
||||
'currency_id' => null,
|
||||
'paid_by_contact_id' => null,
|
||||
'duration_in_minutes' => null,
|
||||
'distance_in_km' => null,
|
||||
'distance' => null,
|
||||
'distance_unit' => null,
|
||||
'from_place' => null,
|
||||
'to_place' => null,
|
||||
'place' => null,
|
||||
|
||||
@@ -196,7 +196,8 @@ class UpdateLifeEventTest extends TestCase
|
||||
'currency_id' => null,
|
||||
'paid_by_contact_id' => null,
|
||||
'duration_in_minutes' => null,
|
||||
'distance_in_km' => null,
|
||||
'distance' => null,
|
||||
'distance_unit' => null,
|
||||
'from_place' => null,
|
||||
'to_place' => null,
|
||||
'place' => null,
|
||||
|
||||
+3
-2
@@ -193,7 +193,7 @@ class ModuleLifeEventViewHelperTest extends TestCase
|
||||
$array = ModuleLifeEventViewHelper::dtoLifeEvent($lifeEvent, $user, $contact);
|
||||
|
||||
$this->assertEquals(
|
||||
18,
|
||||
19,
|
||||
count($array)
|
||||
);
|
||||
|
||||
@@ -207,7 +207,8 @@ class ModuleLifeEventViewHelperTest extends TestCase
|
||||
$this->assertArrayHasKey('currency_id', $array);
|
||||
$this->assertArrayHasKey('paid_by_contact_id', $array);
|
||||
$this->assertArrayHasKey('duration_in_minutes', $array);
|
||||
$this->assertArrayHasKey('distance_in_km', $array);
|
||||
$this->assertArrayHasKey('distance', $array);
|
||||
$this->assertArrayHasKey('distance_unit', $array);
|
||||
$this->assertArrayHasKey('from_place', $array);
|
||||
$this->assertArrayHasKey('to_place', $array);
|
||||
$this->assertArrayHasKey('place', $array);
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Domains\Settings\ManageUserPreferences\Services;
|
||||
|
||||
use App\Domains\Settings\ManageUserPreferences\Services\StoreDistanceFormatPreference;
|
||||
use App\Models\Account;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Tests\TestCase;
|
||||
|
||||
class StoreDistanceFormatPreferenceTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
/** @test */
|
||||
public function it_stores_the_distance_format_preference(): void
|
||||
{
|
||||
$ross = $this->createUser();
|
||||
$this->executeService($ross, $ross->account);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_fails_if_wrong_parameters_are_given(): void
|
||||
{
|
||||
$request = [
|
||||
'title' => 'Ross',
|
||||
];
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
(new StoreDistanceFormatPreference())->execute($request);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_fails_if_user_doesnt_belong_to_account(): void
|
||||
{
|
||||
$this->expectException(ModelNotFoundException::class);
|
||||
|
||||
$ross = $this->createAdministrator();
|
||||
$account = $this->createAccount();
|
||||
$this->executeService($ross, $account);
|
||||
}
|
||||
|
||||
private function executeService(User $author, Account $account): void
|
||||
{
|
||||
Queue::fake();
|
||||
|
||||
$request = [
|
||||
'account_id' => $account->id,
|
||||
'author_id' => $author->id,
|
||||
'distance_format' => 'km',
|
||||
];
|
||||
|
||||
$user = (new StoreDistanceFormatPreference())->execute($request);
|
||||
|
||||
$this->assertDatabaseHas('users', [
|
||||
'id' => $user->id,
|
||||
'account_id' => $account->id,
|
||||
'distance_format' => 'km',
|
||||
]);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
User::class,
|
||||
$user
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -24,7 +24,7 @@ class UserPreferencesIndexViewHelperTest extends TestCase
|
||||
$array = UserPreferencesIndexViewHelper::data($user);
|
||||
|
||||
$this->assertEquals(
|
||||
8,
|
||||
9,
|
||||
count($array)
|
||||
);
|
||||
|
||||
@@ -34,6 +34,7 @@ class UserPreferencesIndexViewHelperTest extends TestCase
|
||||
$this->assertArrayHasKey('timezone', $array);
|
||||
$this->assertArrayHasKey('url', $array);
|
||||
$this->assertArrayHasKey('number_format', $array);
|
||||
$this->assertArrayHasKey('distance_format', $array);
|
||||
$this->assertArrayHasKey('maps', $array);
|
||||
$this->assertArrayHasKey('locale', $array);
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Helpers;
|
||||
|
||||
use App\Helpers\DistanceHelper;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Tests\TestCase;
|
||||
|
||||
class DistanceHelperTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
/** @test */
|
||||
public function it_gets_the_distance_according_to_the_user_preference(): void
|
||||
{
|
||||
$distance = 134;
|
||||
$unit = User::DISTANCE_UNIT_KM;
|
||||
$user = User::factory()->create([
|
||||
'distance_format' => User::DISTANCE_UNIT_KM,
|
||||
]);
|
||||
$this->assertEquals(
|
||||
'134 km',
|
||||
DistanceHelper::format($user, $distance, $unit)
|
||||
);
|
||||
|
||||
$distance = 134;
|
||||
$unit = User::DISTANCE_UNIT_MILES;
|
||||
$user = User::factory()->create([
|
||||
'distance_format' => User::DISTANCE_UNIT_KM,
|
||||
]);
|
||||
$this->assertEquals(
|
||||
'215.65 km',
|
||||
DistanceHelper::format($user, $distance, $unit)
|
||||
);
|
||||
|
||||
$distance = 134;
|
||||
$unit = User::DISTANCE_UNIT_MILES;
|
||||
$user = User::factory()->create([
|
||||
'distance_format' => User::DISTANCE_UNIT_MILES,
|
||||
]);
|
||||
$this->assertEquals(
|
||||
'134 miles',
|
||||
DistanceHelper::format($user, $distance, $unit)
|
||||
);
|
||||
|
||||
$distance = 134;
|
||||
$unit = User::DISTANCE_UNIT_KM;
|
||||
$user = User::factory()->create([
|
||||
'distance_format' => User::DISTANCE_UNIT_MILES,
|
||||
]);
|
||||
$this->assertEquals(
|
||||
'83.26 miles',
|
||||
DistanceHelper::format($user, $distance, $unit)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user