From 49a43e19de7dab004b8c05328f812b4da4b90ff5 Mon Sep 17 00:00:00 2001 From: Igor Santos <532299+igorsantos07@users.noreply.github.com> Date: Wed, 6 Dec 2023 04:18:56 -0300 Subject: [PATCH] close #5, close #16: add money-related Settings Also: - adds some missing Bootstrap variables/configs, needed for a more complex form such as the Settings page - adds a LOT of options to Linput - affixes, size, fullWidth, help prop/slot, and plainText (see https://github.com/bestguy/sveltestrap/issues/582) --- src/cmp/Linput.svelte | 40 ++++++++++++++++++---- src/data/Settings.js | 10 ++++-- src/data/_store.js | 6 ++-- src/lib/fmt.js | 45 +++++++++++++++++++++---- src/routes/+layout.svelte | 4 +-- src/routes/settings/+page.svelte | 58 ++++++++++++++++++++++++++++++++ src/styles/_bs_overrides.scss | 10 ++++++ src/styles/_vars.scss | 7 ++++ 8 files changed, 159 insertions(+), 21 deletions(-) create mode 100644 src/routes/settings/+page.svelte diff --git a/src/cmp/Linput.svelte b/src/cmp/Linput.svelte index 8d98dae..0fffec8 100644 --- a/src/cmp/Linput.svelte +++ b/src/cmp/Linput.svelte @@ -1,10 +1,38 @@ -{#if label}{/if} - + + {#if label}{/if} + + {#if prefix}{prefix}{/if} + {#if plainText} + + {:else} + + {/if} + {#if suffix}{suffix}{/if} + + {#if $$slots.help} + + {:else if help} + {help} + {/if} + diff --git a/src/data/Settings.js b/src/data/Settings.js index 81f06e4..37cbbde 100644 --- a/src/data/Settings.js +++ b/src/data/Settings.js @@ -1,8 +1,12 @@ import Model from './Model' -/** - * @property {boolean} hideMoney - */ export default class Settings extends Model { + hideMoney = true + currency = 'USD' + hourlyRate = 1 + exchange = { + rate: 1, + fee : 0, + } } diff --git a/src/data/_store.js b/src/data/_store.js index f45bac8..3832c98 100644 --- a/src/data/_store.js +++ b/src/data/_store.js @@ -43,8 +43,8 @@ const modelPersistance = (key, initial) => basePersisted(key, initial, { export const _store = { API_KEY : generalPersistance('API_KEY', ''), loading : memoryPersistance(false), - /** @type Settings */ - settings: modelPersistance('settings', new Settings({ hideMoney: true })), - /** @type User */ + /** @type SvelteStore */ + settings: modelPersistance('settings', new Settings()), + /** @type SvelteStore */ user: modelPersistance('user', undefined), } diff --git a/src/lib/fmt.js b/src/lib/fmt.js index d92fb70..b8903d6 100644 --- a/src/lib/fmt.js +++ b/src/lib/fmt.js @@ -1,20 +1,51 @@ import { s2h } from '$lib/date' +import { _store } from '../data/_store' -const NUM_LOCALE = 'en-US' +const CURRENCY_LOCALES = { + 'BRL': 'pt-BR', + 'USD': 'en-US', + 'GBP': 'en-GB', + 'EUR': 'es-ES' //FIXME +} + +let MFD2, CURRENCY, CURRENCY_LG, PERCENT, NUM_LOCALE, HOURLY_RATE, EXCHANGE_RATE +_store.settings.subscribe(({ hourlyRate, currency, exchange }) => { + NUM_LOCALE = CURRENCY_LOCALES[currency] || 'en-US' + MFD2 = new Intl.NumberFormat('en-US', { style: 'decimal', minimumFractionDigits: 2 }) //forced to EN, so it fits input[type=number] + CURRENCY = new Intl.NumberFormat(NUM_LOCALE, { style: 'currency', currency }) + CURRENCY_LG = new Intl.NumberFormat(NUM_LOCALE, { style: 'currency', currency, minimumFractionDigits: 4 }) + PERCENT = new Intl.NumberFormat(NUM_LOCALE, { style: 'percent', maximumFractionDigits: 3 }) + //TODO create a percent() + + HOURLY_RATE = hourlyRate + EXCHANGE_RATE = currency == 'USD'? 1 : (exchange.rate * (1 - (exchange.fee / 100))) +}) -const CURRENCY = new Intl.NumberFormat(NUM_LOCALE, { style: 'currency', currency: 'USD' }) -const PERCENT = new Intl.NumberFormat(NUM_LOCALE, { style: 'percent', maximumFractionDigits: 3 }) +/** + * Formats the number with "Minimum Fraction Digits = 2" + * @param {number|string} number + * @returns {string} + */ +export const mfd2 = number => MFD2.format(number) /** - * @param {number} number + * Formats the number in the selected currency (from {@link Settings} and its related locale, as per {@link CURRENCY_LOCALES} + * @see moneyLong() + * @param {number|string} number * @returns {string} */ export const money = number => CURRENCY.format(number) +/** + * Formats the number in the selected currency (from {@link Settings} with 4 fraction digits. + * @see money() + * @param {number|string} number + * @returns {string} + */ +export const moneyLong = number => CURRENCY_LG.format(number) + export function s2$(seconds) { - //FIXME #5 - const rate = 48 - const ratePerSecond = rate / 60 / 60 + const ratePerSecond = HOURLY_RATE / 60 / 60 * EXCHANGE_RATE return money(seconds * ratePerSecond) } diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 476f102..6b413ef 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -46,8 +46,8 @@ Periods - - + + Settings diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/+page.svelte new file mode 100644 index 0000000..724494d --- /dev/null +++ b/src/routes/settings/+page.svelte @@ -0,0 +1,58 @@ + + + +<Card> + <CardHeader> + <CardTitle>πŸ’° Cash</CardTitle> + </CardHeader> + <CardBody> + <Linput label="Your hourly rate, in dollars" prefix="$" type="number" bind:value={$settings.hourlyRate} required/> + <FormGroup> + <Label>Your local currency</Label> + <Input type="select" bind:value={$settings.currency} required class="w-auto"> + <option value="USD">πŸ‡ΊπŸ‡Έ USD</option> + <option value="BRL">πŸ‡§πŸ‡· BRL</option> + <option value="EUR">πŸ‡ͺπŸ‡Ί EUR</option> + <option value="GBP">πŸ‡¬πŸ‡§ GBP</option> + </Input> + </FormGroup> + <Row> + <Col sm="12" md="5"> + <Linput label="Exchange rate" disabled={disableEx} + prefix={disableEx? '' : `1 USD = ${$settings.currency}`} type="number" + bind:value={$rate} on:change={changeRate} required step="0.01" + help="This is the market rate - you can get it from Google, for instance."/> + </Col> + <Col sm="12" md="5"> + <Linput label="Exchange fee" disabled={disableEx} suffix="%" type="number" + bind:value={$settings.exchange.fee} required step="0.01" + help='Also known as "spread" - in finance, that is difference between the market rate and the rate you actually get.'/> + </Col> + {#key disableEx} + <Col sm="12" md="2" class="mb-n3 {disableEx? 'invisible' : 'visible'}"> + <!-- n3 counteracts FormGroup's margin, since it's at the bottom of a Column, not a CardBody --> + <Linput label="πŸ’± Actual rate" value={actualRate} plainText sizing="lg" class="fw-bold"/> + </Col> + {/key} + </Row> + </CardBody> +</Card> diff --git a/src/styles/_bs_overrides.scss b/src/styles/_bs_overrides.scss index 5c80acd..50a94cb 100644 --- a/src/styles/_bs_overrides.scss +++ b/src/styles/_bs_overrides.scss @@ -37,6 +37,11 @@ } } } + &-body { + > .mb-3:last-child { //surprisingly, on BS5 they REMOVED form-group and replaced with a plain mb-3!!! god, why. + margin-bottom: 0 !important; + } + } &-footer { background-color: $card-bg; @@ -55,6 +60,11 @@ } } +.form-control:disabled, +.form-label[disabled=true], .form-text[disabled=true] /* these only happens on Linput */ { + opacity: 0.6; //originally from Clockify (viewable on the Timesheet screen) + cursor: not-allowed; +} input.w-auto { flex-grow: 0 !important; @include media-breakpoint-down(sm) { //reverts to the standard, so it's full-width on narrow screens diff --git a/src/styles/_vars.scss b/src/styles/_vars.scss index cabaeb1..0a09049 100644 --- a/src/styles/_vars.scss +++ b/src/styles/_vars.scss @@ -32,6 +32,8 @@ $title-color : $body-color; //$font-weight-semibold : 500; //default for titles and... maybe something else? but 500 doesn't work unless, I guess, we import this specific font weight file? $border-color : $dark; $border-radius : 0; +$border-radius-sm : 0; +$border-radius-lg : 0; $modal-backdrop-bg : rgba(255, 255, 255, 0.2); $offcanvas-backdrop-bg : $modal-backdrop-bg; @@ -42,6 +44,9 @@ $btn-font-size : 0.75rem; $btn-padding-x : 0.5rem; $btn-padding-y : math.div($btn-padding-x, 2); +//disabled inputs have the same colors as working ones, but get 60% of opacity at _bs_overrides.scss +$input-disabled-bg : var(--bs-body-bg); + $card-cap-bg : $dark; $card-title-color : $title-color; $card-title-spacer-y: 0; @@ -56,6 +61,8 @@ $tooltip-opacity : 1; $tooltip-border-radius: 0.5rem; $tooltip-color : $body-secondary-color; +$form-select-indicator-color: $body-color; + @import "~/bootstrap/scss/functions"; @import "~/bootstrap/scss/variables"; //@import "~/bootstrap/scss/variables-dark"; //hm... what does it do?