-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: manage number format (monicahq/chandler#56)
- Loading branch information
Showing
13 changed files
with
484 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
<?php | ||
|
||
namespace App\Helpers; | ||
|
||
use App\Models\User; | ||
use Illuminate\Support\Str; | ||
|
||
class MonetaryNumberHelper | ||
{ | ||
/** | ||
* Format the number according to the user preferences. | ||
* We don't use the number_format PHP function because it rounds the number. | ||
* We also don't use the Money library because it requires the intl PHP | ||
* extension and we want to keep the software as far away as dependencies | ||
* as possible. | ||
* | ||
* @param User $user | ||
* @param int $number | ||
* @return string | ||
*/ | ||
public static function format(User $user, int $number): string | ||
{ | ||
$formattedNumber = ''; | ||
$numberAsString = (string) $number; | ||
$length = Str::length($numberAsString); | ||
$thousands = Str::substr($numberAsString, 0, $length - 2); | ||
$lengthThousands = Str::length($thousands); | ||
$decimals = Str::substr($numberAsString, $length - 2, 2); | ||
|
||
switch ($user->number_format) { | ||
// 1,234.56 | ||
case User::NUMBER_FORMAT_TYPE_COMMA_THOUSANDS_DOT_DECIMAL: | ||
for ($i = 0; $i < $lengthThousands; $i++) { | ||
if (($i % 3 == 0) && $i) { | ||
$formattedNumber = ','.$formattedNumber; | ||
} | ||
$formattedNumber = $thousands[$lengthThousands - $i - 1].$formattedNumber; | ||
} | ||
$formattedNumber = $formattedNumber.'.'.$decimals; | ||
break; | ||
|
||
// 1 234,56 | ||
case User::NUMBER_FORMAT_TYPE_SPACE_THOUSANDS_COMMA_DECIMAL: | ||
for ($i = 0; $i < $lengthThousands; $i++) { | ||
if (($i % 3 == 0) && $i) { | ||
$formattedNumber = ' '.$formattedNumber; | ||
} | ||
$formattedNumber = $thousands[$lengthThousands - $i - 1].$formattedNumber; | ||
} | ||
$formattedNumber = $formattedNumber.','.$decimals; | ||
break; | ||
|
||
// 1234.56 | ||
case User::NUMBER_FORMAT_TYPE_NO_SPACE_DOT_DECIMAL: | ||
for ($i = 0; $i < $lengthThousands; $i++) { | ||
$formattedNumber = $thousands[$lengthThousands - $i - 1].$formattedNumber; | ||
} | ||
$formattedNumber = $formattedNumber.'.'.$decimals; | ||
break; | ||
|
||
default: | ||
$formattedNumber = ''; | ||
break; | ||
} | ||
|
||
return $formattedNumber; | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
app/Http/Controllers/Settings/Preferences/PreferencesNumberFormatController.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?php | ||
|
||
namespace App\Http\Controllers\Settings\Preferences; | ||
|
||
use Illuminate\Http\Request; | ||
use App\Http\Controllers\Controller; | ||
use Illuminate\Support\Facades\Auth; | ||
use App\Services\User\Preferences\StoreNumberFormatPreference; | ||
use App\Http\Controllers\Settings\Preferences\ViewHelpers\PreferencesIndexViewHelper; | ||
|
||
class PreferencesNumberFormatController extends Controller | ||
{ | ||
public function store(Request $request) | ||
{ | ||
$data = [ | ||
'account_id' => Auth::user()->account_id, | ||
'author_id' => Auth::user()->id, | ||
'number_format' => $request->input('numberFormat'), | ||
]; | ||
|
||
$user = (new StoreNumberFormatPreference)->execute($data); | ||
|
||
return response()->json([ | ||
'data' => PreferencesIndexViewHelper::dtoNumberFormat($user), | ||
], 200); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
app/Services/User/Preferences/StoreNumberFormatPreference.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
<?php | ||
|
||
namespace App\Services\User\Preferences; | ||
|
||
use App\Models\User; | ||
use App\Services\BaseService; | ||
use App\Interfaces\ServiceInterface; | ||
|
||
class StoreNumberFormatPreference extends BaseService implements ServiceInterface | ||
{ | ||
private array $data; | ||
|
||
/** | ||
* Get the validation rules that apply to the service. | ||
* | ||
* @return array | ||
*/ | ||
public function rules(): array | ||
{ | ||
return [ | ||
'account_id' => 'required|integer|exists:accounts,id', | ||
'author_id' => 'required|integer|exists:users,id', | ||
'number_format' => 'required|string|max:255', | ||
]; | ||
} | ||
|
||
/** | ||
* Get the permissions that apply to the user calling the service. | ||
* | ||
* @return array | ||
*/ | ||
public function permissions(): array | ||
{ | ||
return [ | ||
'author_must_belong_to_account', | ||
]; | ||
} | ||
|
||
/** | ||
* Store date format preferences for the given user. | ||
* | ||
* @param array $data | ||
* @return User | ||
*/ | ||
public function execute(array $data): User | ||
{ | ||
$this->data = $data; | ||
|
||
$this->validateRules($data); | ||
$this->updateUser(); | ||
|
||
return $this->author; | ||
} | ||
|
||
private function updateUser(): void | ||
{ | ||
$this->author->number_format = $this->data['number_format']; | ||
$this->author->save(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
resources/js/Pages/Settings/Preferences/Partials/NumberFormat.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
<style lang="scss" scoped> | ||
pre { | ||
background-color: #1f2937; | ||
color: #c9ef78; | ||
} | ||
.example { | ||
border-bottom-left-radius: 9px; | ||
border-bottom-right-radius: 9px; | ||
} | ||
</style> | ||
|
||
<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 sm:mb-0"><span class="mr-1">💵</span> How should we display monetary values</h3> | ||
<pretty-button v-if="!editMode" :text="'Edit'" @click="enableEditMode" /> | ||
</div> | ||
|
||
<!-- normal mode --> | ||
<div v-if="!editMode" class="mb-6 rounded-lg border border-gray-200 bg-white"> | ||
<p class="px-5 py-2"> | ||
<span class="mb-2 block">Current way of displaying numbers:</span> | ||
<span class="mb-2 block rounded bg-slate-100 px-5 py-2 text-sm">{{ localNumberFormat }}</span> | ||
</p> | ||
</div> | ||
|
||
<!-- edit mode --> | ||
<form v-if="editMode" class="mb-6 rounded-lg border border-gray-200 bg-white" @submit.prevent="submit()"> | ||
<div class="border-b border-gray-200 px-5 py-2"> | ||
<errors :errors="form.errors" /> | ||
|
||
<div v-for="numberFormat in data.numbers" :key="numberFormat.id" class="mb-2 flex items-center"> | ||
<input | ||
:id="'input' + numberFormat.id" | ||
v-model="form.numberFormat" | ||
:value="numberFormat.format" | ||
name="date-format" | ||
type="radio" | ||
class="h-4 w-4 border-gray-300 text-sky-500" /> | ||
<label :for="'input' + numberFormat.id" class="ml-3 block cursor-pointer text-sm font-medium text-gray-700"> | ||
{{ numberFormat.value }} | ||
</label> | ||
</div> | ||
</div> | ||
|
||
<!-- actions --> | ||
<div class="flex justify-between p-5"> | ||
<pretty-link :text="'Cancel'" :classes="'mr-3'" @click="editMode = false" /> | ||
<pretty-button :text="'Save'" :state="loadingState" :icon="'check'" :classes="'save'" /> | ||
</div> | ||
</form> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
import PrettyButton from '@/Shared/Form/PrettyButton'; | ||
import PrettyLink from '@/Shared/Form/PrettyLink'; | ||
import Errors from '@/Shared/Form/Errors'; | ||
export default { | ||
components: { | ||
PrettyButton, | ||
PrettyLink, | ||
Errors, | ||
}, | ||
props: { | ||
data: { | ||
type: Object, | ||
default: null, | ||
}, | ||
}, | ||
data() { | ||
return { | ||
loadingState: '', | ||
editMode: false, | ||
localNumberFormat: '', | ||
form: { | ||
numberFormat: '', | ||
errors: [], | ||
}, | ||
}; | ||
}, | ||
mounted() { | ||
this.localNumberFormat = this.data.number_format; | ||
this.form.numberFormat = this.data.number_format; | ||
}, | ||
methods: { | ||
enableEditMode() { | ||
this.editMode = true; | ||
}, | ||
submit() { | ||
this.loadingState = 'loading'; | ||
axios | ||
.post(this.data.url.store, this.form) | ||
.then((response) => { | ||
this.flash('Changes saved', 'success'); | ||
this.localNumberFormat = this.form.numberFormat; | ||
this.editMode = false; | ||
this.loadingState = null; | ||
}) | ||
.catch((error) => { | ||
this.loadingState = null; | ||
this.form.errors = error.response.data; | ||
}); | ||
}, | ||
}, | ||
}; | ||
</script> |
Oops, something went wrong.