Skip to content

Commit

Permalink
close #5, close #16: add money-related Settings
Browse files Browse the repository at this point in the history
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 bestguy/sveltestrap#582)
  • Loading branch information
igorsantos07 committed Dec 10, 2023
1 parent 402133e commit 49a43e1
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 21 deletions.
40 changes: 34 additions & 6 deletions src/cmp/Linput.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,38 @@
<script>
import { Input, Label } from 'sveltestrap'
import { FormGroup, FormText, Input, InputGroup, InputGroupText, Label } from 'sveltestrap'
export let value = ''
export let label = ''
export let id = 'input-'+(Math.random() * 1_000_000)
export let value = ''
export let label = ''
export let prefix = ''
export let suffix = ''
export let help = ''
export let id = 'input-' + (Math.random() * 1_000_000)
export let plainText = false
export let fullWidth = false
export let size = ''
//FIXME use class:fullWidth and etc instead of that mess
let classes = `${$$props.class || ''} ${(fullWidth ? '' : 'w-auto')}`
if (size == 'sm' || size == 'lg') {
classes += ` form-control-${size}`
}
</script>

{#if label}<Label for={id}>{label}</Label>{/if}
<Input bind:value={value} {...$$props} {id}/>
<FormGroup>
{#if label}<Label for={id} disabled={$$props.disabled}>{label}</Label>{/if}
<InputGroup>
{#if prefix}<InputGroupText>{prefix}</InputGroupText>{/if}
{#if plainText} <!-- https://github.com/bestguy/sveltestrap/issues/582 -->
<input on:input on:change bind:value={value} {...$$restProps} {id} class={`form-control-plaintext ${classes}`} readonly/>
{:else}
<Input on:input on:change bind:value={value} {...$$restProps} {id} class={classes}/>
{/if}
{#if suffix}<InputGroupText>{suffix}</InputGroupText>{/if}
</InputGroup>
{#if $$slots.help}
<FormText disabled={$$props.disabled}><slot name="help"/></FormText>
{:else if help}
<FormText disabled={$$props.disabled}>{help}</FormText>
{/if}
</FormGroup>
10 changes: 7 additions & 3 deletions src/data/Settings.js
Original file line number Diff line number Diff line change
@@ -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,
}
}
6 changes: 3 additions & 3 deletions src/data/_store.js
Original file line number Diff line number Diff line change
Expand Up @@ -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> */
settings: modelPersistance('settings', new Settings()),
/** @type SvelteStore<?User> */
user: modelPersistance('user', undefined),
}
45 changes: 38 additions & 7 deletions src/lib/fmt.js
Original file line number Diff line number Diff line change
@@ -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)
}

Expand Down
4 changes: 2 additions & 2 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
<Icon name="calendar"/> Periods
</NavLink>
</NavItem>
<NavItem> <!--TODO-->
<NavLink disabled={true || !$isAuth} href="/settings">
<NavItem>
<NavLink disabled={!$isAuth} href="/settings">
<Icon name="tools"/> Settings
</NavLink>
</NavItem>
Expand Down
58 changes: 58 additions & 0 deletions src/routes/settings/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<script>
import { Card, CardBody, CardHeader, CardTitle, Col, FormGroup, Input, Label, Row } from 'sveltestrap'
import { _store } from '$data/_store'
import Linput from '$cmp/Linput.svelte'
import { mfd2, moneyLong } from '$lib/fmt'
import { writable } from 'svelte/store'
import Title from '$cmp/Title.svelte'
const settings = _store.settings
let rate = writable(mfd2($settings.exchange.rate))
function changeRate() {
$settings.exchange.rate = parseFloat($rate) //saves the raw number...
$rate = mfd2($rate) //...but displays the leading zero
}
$: disableEx = $settings.currency == 'USD'
$: actualRate = moneyLong($settings.exchange.rate * (1 - ($settings.exchange.fee / 100)))
</script>

<Title page="Settings"/>
<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>
10 changes: 10 additions & 0 deletions src/styles/_bs_overrides.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/styles/_vars.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand All @@ -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?

0 comments on commit 49a43e1

Please sign in to comment.