Feat: Adds multi currency support (#88)
This commit is contained in:
@@ -31,7 +31,7 @@
|
||||
<x-input id="password_confirmation" label="{{ __('Confirm Password') }}" class="block mt-1 w-full" type="password" name="password_confirmation" required autocomplete="new-password" />
|
||||
</div>
|
||||
|
||||
@if (Laravel\Jetstream\Jetstream::hasTermsAndPrivacyPolicyFeature())
|
||||
@if (! config('investbrain.self_hosted'))
|
||||
<div class="mt-4">
|
||||
<label>
|
||||
<div class="flex items-center">
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ new class extends Component {
|
||||
'model' => config('openai.model'),
|
||||
'messages' => [
|
||||
['role' => 'system', 'content' => "Today's date is "
|
||||
.now()->format('Y-m-d')
|
||||
.now()->toDateString()
|
||||
.".\n\n".$this->system_prompt],
|
||||
...array_slice($this->messages, -10)
|
||||
],
|
||||
@@ -1,18 +1,18 @@
|
||||
<span
|
||||
class=""
|
||||
style="width:90em;overflow: hidden; white-space: nowrap;"
|
||||
title="{{ Number::currency($low ?? 0) }} - {{ Number::currency($high ?? 0) }}"
|
||||
title="{{ Number::currency($marketData->fifty_two_week_low ?? 0, $marketData->currency) }} - {{ Number::currency($marketData->fifty_two_week_high ?? 0, $marketData->currency) }}"
|
||||
>
|
||||
|
||||
@php
|
||||
// 52-week low must be a non-zero
|
||||
if (empty($low)) {
|
||||
$low = 1;
|
||||
if (empty($marketData->fifty_two_week_low)) {
|
||||
$marketData->fifty_two_week_low = 1;
|
||||
}
|
||||
@endphp
|
||||
|
||||
@for ($x = 0; $x < 10; $x++)
|
||||
@if ((($current - $low) * 100) / ($high - $low) > ($x * 10))
|
||||
@if ((($marketData->market_value - $marketData->fifty_two_week_low) * 100) / ($marketData->fifty_two_week_high - $marketData->fifty_two_week_low) > ($x * 10))
|
||||
|
||||
●
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
}
|
||||
|
||||
this.data.yaxis.labels.formatter = function (value) {
|
||||
return `$${value}`
|
||||
return `{{ Number::currencySymbol(auth()->user()->getCurrency()) }}${value}`
|
||||
}
|
||||
|
||||
this.data.tooltip = {
|
||||
@@ -103,7 +103,7 @@
|
||||
formatter: (value, { series, seriesIndex, dataPointIndex, w }) => {
|
||||
const firstDataPoint = this.data.series[seriesIndex].data[0][1]
|
||||
const percentageChange = ((value - firstDataPoint) / firstDataPoint) * 100;
|
||||
return `$${parseFloat(value.toFixed(2))} (${percentageChange.toFixed(2)}%)`;
|
||||
return `${parseFloat(value.toFixed(2))} (${percentageChange.toFixed(2)}%)`;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
@props([
|
||||
'sidebar' => null,
|
||||
'content' => null,
|
||||
'footer' => null,
|
||||
'fullWidth' => false,
|
||||
'withNav' => false,
|
||||
'collapseText' => 'Collapse',
|
||||
'collapseIcon' => 'o-bars-3-bottom-right',
|
||||
'collapsible' => false,
|
||||
'url' => route('mary.toogle-sidebar', absolute: false),
|
||||
])
|
||||
|
||||
<main class="{{ !$fullWidth ? 'max-w-screen-2xl' : '' }} w-full mx-auto">
|
||||
<div class="drawer {{ $sidebar?->attributes['right'] ? 'drawer-end' : '' }} lg:drawer-open">
|
||||
<input id="{{ $sidebar?->attributes['drawer'] }}" type="checkbox" class="drawer-toggle" />
|
||||
|
||||
<div {{ $content->attributes->class(["drawer-content w-full mx-auto p-5 lg:px-10 lg:py-5"]) }}>
|
||||
{{-- MAIN CONTENT --}}
|
||||
{{ $content }}
|
||||
</div>
|
||||
|
||||
{{-- SIDEBAR --}}
|
||||
@if($sidebar)
|
||||
<div
|
||||
x-data="{
|
||||
collapsed: {{ session('mary-sidebar-collapsed', 'false') }},
|
||||
collapseText: '{{ $collapseText }}',
|
||||
toggle() {
|
||||
this.collapsed = !this.collapsed;
|
||||
fetch('{{ $url }}?collapsed=' + this.collapsed);
|
||||
this.$dispatch('sidebar-toggled', this.collapsed);
|
||||
}
|
||||
}"
|
||||
@menu-sub-clicked="if(collapsed) { toggle() }"
|
||||
@class(["drawer-side z-20 lg:z-auto", "top-0 lg:top-[73px] lg:h-[calc(100vh-73px)]" => $withNav])
|
||||
>
|
||||
<label for="{{ $sidebar?->attributes['drawer'] }}" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||
{{-- SIDEBAR CONTENT --}}
|
||||
<div>
|
||||
|
||||
{{ $sidebar }}
|
||||
|
||||
{{-- SIDEBAR COLLAPSE --}}
|
||||
@if($sidebar->attributes['collapsible'])
|
||||
<x-mary-menu class="hidden !bg-inherit lg:block">
|
||||
<x-mary-menu-item
|
||||
@click="toggle"
|
||||
icon="{{ $sidebar->attributes['collapse-icon'] ?? $collapseIcon }}"
|
||||
title="{{ $sidebar->attributes['collapse-text'] ?? $collapseText }}" />
|
||||
</x-mary-menu>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
{{-- END SIDEBAR--}}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{{-- FOOTER --}}
|
||||
@if($footer)
|
||||
<footer {{ $footer?->attributes->class(["mx-auto w-full", "max-w-screen-2xl" => !$fullWidth ]) }}>
|
||||
{{ $footer }}
|
||||
</footer>
|
||||
@endif
|
||||
@@ -1,4 +1,23 @@
|
||||
<?php
|
||||
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component
|
||||
{
|
||||
// props
|
||||
|
||||
/**
|
||||
* The component's listeners.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $listeners = [
|
||||
'refresh-navigation-menu' => '$refresh',
|
||||
];
|
||||
|
||||
// methods
|
||||
|
||||
}; ?>
|
||||
<div class="bg-base-100 border-base-300 border-b sticky top-0 z-10">
|
||||
<div class="flex justify-between items-center px-7 py-3 gap-4 mx-auto">
|
||||
<div class="flex flex-0 items-center">
|
||||
|
||||
@@ -1,54 +1,94 @@
|
||||
<x-menu activate-by-route>
|
||||
<?php
|
||||
|
||||
<x-menu-item title="{{ __('Dashboard') }}" icon="o-home" link="{{ route('dashboard') }}" />
|
||||
<x-menu-sub title="{{ __('Portfolios') }}" icon="o-document-duplicate">
|
||||
@foreach (auth()->user()->portfolios as $portfolio)
|
||||
<x-menu-item icon="o-document" link="{{ route('portfolio.show', ['portfolio' => $portfolio->id ]) }}" >
|
||||
<x-slot:title>
|
||||
{{ $portfolio->title }}
|
||||
@if($portfolio->wishlist)
|
||||
<x-badge value="{{ __('Wishlist') }}" class="badge-secondary badge-sm ml-2" />
|
||||
@endif
|
||||
</x-slot:title>
|
||||
|
||||
</x-menu-item>
|
||||
@endforeach
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
<x-menu-item title="{{ __('Create Portfolio') }}" icon="o-document-plus" link="{{ route('portfolio.create') }}" />
|
||||
</x-menu-sub>
|
||||
<x-menu-item title="{{ __('Transactions') }}" icon="o-banknotes" link="{{ route('transaction.index') }}" />
|
||||
{{-- <x-menu-item title="{{ __('Reporting') }}" icon="o-chart-bar-square" link="####" /> --}}
|
||||
new class extends Component
|
||||
{
|
||||
// props
|
||||
|
||||
</x-menu>
|
||||
/**
|
||||
* The component's listeners.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $listeners = [
|
||||
'refresh-navigation-menu' => '$refresh',
|
||||
];
|
||||
|
||||
</div>
|
||||
<div class="px-3">
|
||||
// methods
|
||||
|
||||
<x-section-border />
|
||||
}; ?>
|
||||
|
||||
@php
|
||||
$user = auth()->user();
|
||||
@endphp
|
||||
<div class="
|
||||
flex
|
||||
flex-col
|
||||
!transition-all
|
||||
!duration-100
|
||||
ease-out
|
||||
overflow-x-hidden
|
||||
overflow-y-auto
|
||||
h-screen
|
||||
lg:h-[calc(100vh-73px)]
|
||||
bg-base-100
|
||||
lg:bg-inherit
|
||||
{{ session('mary-sidebar-collapsed') == 'true' ? 'w-[70px] [&>*_summary::after]:hidden [&_.mary-hideable]:hidden [&_.display-when-collapsed]:block [&_.hidden-when-collapsed]:hidden' : null }}
|
||||
{{ session('mary-sidebar-collapsed') != 'true' ? 'w-[270px] [&>*_summary::after]:block [&_.mary-hideable]:block [&_.hidden-when-collapsed]:block [&_.display-when-collapsed]:hidden' : null }}
|
||||
">
|
||||
<div class="flex-1">
|
||||
<x-menu activate-by-route>
|
||||
|
||||
<x-list-item :item="$user" avatar="profile_photo_url" value="name" sub-value="email" no-separator no-hover class="mb-3 !-mt-3 rounded">
|
||||
<x-slot:actions>
|
||||
<x-dropdown>
|
||||
<x-slot:trigger>
|
||||
<x-button icon="o-cog-6-tooth" class="btn-circle btn-ghost btn-xs" />
|
||||
</x-slot:trigger>
|
||||
|
||||
<x-menu-item title="{{ __('Manage Profile') }}" icon="o-user" link="{{ @route('profile.show') }}" />
|
||||
<x-menu-item title="{{ __('API Tokens') }}" icon="o-command-line" link="{{ @route('api-tokens.index') }}" />
|
||||
<x-menu-item title="{{ __('Import / Export Data') }}" icon="o-cloud-arrow-down" link="{{ @route('import-export') }}" />
|
||||
<x-menu-item title="{{ __('Dashboard') }}" icon="o-home" link="{{ route('dashboard') }}" />
|
||||
<x-menu-sub title="{{ __('Portfolios') }}" icon="o-document-duplicate">
|
||||
@foreach (auth()->user()->portfolios as $portfolio)
|
||||
<x-menu-item icon="o-document" link="{{ route('portfolio.show', ['portfolio' => $portfolio->id ]) }}" >
|
||||
<x-slot:title>
|
||||
{{ $portfolio->title }}
|
||||
@if($portfolio->wishlist)
|
||||
<x-badge value="{{ __('Wishlist') }}" class="badge-secondary badge-sm ml-2" />
|
||||
@endif
|
||||
</x-slot:title>
|
||||
|
||||
</x-menu-item>
|
||||
@endforeach
|
||||
|
||||
<x-section-border class="py-1" />
|
||||
<x-menu-item title="{{ __('Create Portfolio') }}" icon="o-document-plus" link="{{ route('portfolio.create') }}" />
|
||||
</x-menu-sub>
|
||||
<x-menu-item title="{{ __('Transactions') }}" icon="o-banknotes" link="{{ route('transaction.index') }}" />
|
||||
{{-- <x-menu-item title="{{ __('Reporting') }}" icon="o-chart-bar-square" link="####" /> --}}
|
||||
|
||||
<x-menu-item title="{{ __('Log Out') }}" icon="o-power" onclick="event.preventDefault(); document.getElementById('logout').submit();" />
|
||||
<form id="logout" action="{{ route('logout') }}" method="POST" style="display: none;">
|
||||
{{ csrf_field() }}
|
||||
</form>
|
||||
</x-menu>
|
||||
|
||||
</x-dropdown>
|
||||
|
||||
</x-slot:actions>
|
||||
</x-list-item>
|
||||
</div>
|
||||
|
||||
<div class="px-3">
|
||||
|
||||
<x-section-border />
|
||||
|
||||
@php
|
||||
$user = auth()->user();
|
||||
@endphp
|
||||
|
||||
<x-list-item :item="$user" avatar="profile_photo_url" value="name" sub-value="email" no-separator no-hover class="mb-3 !-mt-3 rounded">
|
||||
<x-slot:actions>
|
||||
<x-dropdown>
|
||||
<x-slot:trigger>
|
||||
<x-button icon="o-cog-6-tooth" class="btn-circle btn-ghost btn-xs" />
|
||||
</x-slot:trigger>
|
||||
|
||||
<x-menu-item title="{{ __('Manage Profile') }}" icon="o-user" link="{{ @route('profile.show') }}" />
|
||||
<x-menu-item title="{{ __('API Tokens') }}" icon="o-command-line" link="{{ @route('api-tokens.index') }}" />
|
||||
<x-menu-item title="{{ __('Import / Export Data') }}" icon="o-cloud-arrow-down" link="{{ @route('import-export') }}" />
|
||||
|
||||
<x-section-border class="py-1" />
|
||||
|
||||
<x-menu-item title="{{ __('Log Out') }}" icon="o-power" onclick="event.preventDefault(); document.getElementById('logout').submit();" />
|
||||
<form id="logout" action="{{ route('logout') }}" method="POST" style="display: none;">
|
||||
{{ csrf_field() }}
|
||||
</form>
|
||||
|
||||
</x-dropdown>
|
||||
|
||||
</x-slot:actions>
|
||||
</x-list-item>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,3 +1,5 @@
|
||||
@use('App\Models\Currency')
|
||||
|
||||
<x-app-layout>
|
||||
|
||||
@livewire('portfolio-performance-chart', [
|
||||
@@ -7,27 +9,27 @@
|
||||
<div class="grid sm:grid-cols-5 gap-5">
|
||||
<x-card class="col-span-5 sm:col-span-1 bg-slate-100 dark:bg-base-200 rounded-lg">
|
||||
<div class="text-sm text-gray-400 whitespace-nowrap truncate">{{ __('Market Gain/Loss') }}</div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->total_gain_dollars) }} </div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->get('total_gain_dollars', 0)) }} </div>
|
||||
</x-card>
|
||||
|
||||
<x-card class="col-span-5 sm:col-span-1 bg-slate-100 dark:bg-base-200 rounded-lg">
|
||||
<div class="text-sm text-gray-400 whitespace-nowrap truncate">{{ __('Total Cost Basis') }}</div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->total_cost_basis) }} </div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->get('total_cost_basis', 0)) }} </div>
|
||||
</x-card>
|
||||
|
||||
<x-card class="col-span-5 sm:col-span-1 bg-slate-100 dark:bg-base-200 rounded-lg">
|
||||
<div class="text-sm text-gray-400 whitespace-nowrap truncate">{{ __('Total Market Value') }}</div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->total_market_value) }} </div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->get('total_market_value', 0)) }} </div>
|
||||
</x-card>
|
||||
|
||||
<x-card class="col-span-5 sm:col-span-1 bg-slate-100 dark:bg-base-200 rounded-lg">
|
||||
<div class="text-sm text-gray-400 whitespace-nowrap truncate">{{ __('Realized Gain/Loss') }}</div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->realized_gain_dollars) }} </div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->get('realized_gain_dollars', 0)) }} </div>
|
||||
</x-card>
|
||||
|
||||
<x-card class="col-span-5 sm:col-span-1 bg-slate-100 dark:bg-base-200 rounded-lg">
|
||||
<div class="text-sm text-gray-400 whitespace-nowrap truncate">{{ __('Dividends Earned') }}</div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->total_dividends_earned) }} </div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->get('total_dividends_earned', 0)) }} </div>
|
||||
</x-card>
|
||||
|
||||
</div>
|
||||
|
||||
+2
-2
@@ -27,9 +27,9 @@ new class extends Component
|
||||
$owned = ($dividend->purchased - $dividend->sold);
|
||||
@endphp
|
||||
|
||||
{{ Number::currency($dividend->dividend_amount) }}
|
||||
{{ Number::currency($dividend->dividend_amount, $holding->market_data->currency) }}
|
||||
x {{ $owned }}
|
||||
= {{ Number::currency($owned * $dividend->dividend_amount) }}
|
||||
= {{ Number::currency($owned * $dividend->dividend_amount, $holding->market_data->currency) }}
|
||||
|
||||
</x-slot:value>
|
||||
<x-slot:sub-value>
|
||||
+10
-10
@@ -3,27 +3,27 @@
|
||||
use App\Models\Holding;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component {
|
||||
|
||||
new class extends Component
|
||||
{
|
||||
// props
|
||||
public Holding $holding;
|
||||
|
||||
protected $listeners = [
|
||||
'transaction-updated' => '$refresh',
|
||||
'transaction-saved' => '$refresh'
|
||||
'transaction-saved' => '$refresh',
|
||||
];
|
||||
|
||||
|
||||
// methods
|
||||
|
||||
}; ?>
|
||||
|
||||
<div>
|
||||
<div class="font-bold text-2xl py-1 flex items-center">
|
||||
{{ Number::currency($holding->market_data->market_value ?? 0) }}
|
||||
{{ Number::currency($holding->market_data->market_value ?? 0, $holding->market_data->currency) }}
|
||||
|
||||
<x-gain-loss-arrow-badge
|
||||
:cost-basis="$holding->average_cost_basis"
|
||||
:market-value="$holding->market_data->market_value"
|
||||
:market-value="$holding->market_data->market_value_base"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -34,22 +34,22 @@ new class extends Component {
|
||||
|
||||
<p>
|
||||
<span class="font-bold">{{ __('Average Cost Basis') }}: </span>
|
||||
{{ Number::currency($holding->average_cost_basis ?? 0) }}
|
||||
{{ Number::currency($holding->average_cost_basis ?? 0, $holding->market_data->currency) }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="font-bold">{{ __('Total Cost Basis') }}: </span>
|
||||
{{ Number::currency($holding->total_cost_basis ?? 0) }}
|
||||
{{ Number::currency($holding->total_cost_basis ?? 0, $holding->market_data->currency) }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="font-bold">{{ __('Realized Gain/Loss') }}: </span>
|
||||
{{ Number::currency($holding->realized_gain_dollars ?? 0) }}
|
||||
{{ Number::currency($holding->realized_gain_dollars ?? 0, $holding->market_data->currency) }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="font-bold">{{ __('Dividends Earned') }}: </span>
|
||||
{{ Number::currency($holding->dividends_earned ?? 0) }}
|
||||
{{ Number::currency($holding->dividends_earned ?? 0, $holding->market_data->currency) }}
|
||||
</p>
|
||||
|
||||
<p class="pt-2 text-sm" title="{{ \Carbon\Carbon::parse($holding->market_data->updated_at)->toIso8601String() }}">
|
||||
+19
-20
@@ -1,13 +1,12 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Holding;
|
||||
use App\Models\Portfolio;
|
||||
use App\Models\Transaction;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Volt\Component;
|
||||
use App\Models\Currency;
|
||||
|
||||
new class extends Component {
|
||||
|
||||
new class extends Component
|
||||
{
|
||||
// props
|
||||
public Portfolio $portfolio;
|
||||
|
||||
@@ -40,13 +39,13 @@ new class extends Component {
|
||||
{
|
||||
|
||||
$holdings = $this->portfolio
|
||||
->holdings()
|
||||
->withCount(['transactions as num_transactions' => function($query) {
|
||||
return $query->whereRaw('transactions.symbol = holdings.symbol');
|
||||
}])
|
||||
->orderBy(...array_values($this->sortBy))
|
||||
->holdings()
|
||||
->withCount(['transactions as num_transactions' => function ($query) {
|
||||
return $query->whereRaw('transactions.symbol = holdings.symbol');
|
||||
}])
|
||||
->orderBy(...array_values($this->sortBy))
|
||||
// ->where('holdings.quantity', '>', 0)
|
||||
->get();
|
||||
->get();
|
||||
|
||||
return $holdings;
|
||||
}
|
||||
@@ -55,7 +54,6 @@ new class extends Component {
|
||||
{
|
||||
return $this->redirect(route('holding.show', ['portfolio' => $holding['portfolio_id'], 'symbol' => $holding['symbol']]));
|
||||
}
|
||||
|
||||
}; ?>
|
||||
|
||||
|
||||
@@ -66,16 +64,17 @@ new class extends Component {
|
||||
@row-click="$wire.goToHolding($event.detail)"
|
||||
>
|
||||
@scope('cell_average_cost_basis', $row)
|
||||
{{ Number::currency($row->average_cost_basis ?? 0) }}
|
||||
{{ Number::currency($row->average_cost_basis ?? 0, $row->market_data->currency) }}
|
||||
|
||||
@endscope
|
||||
@scope('cell_total_cost_basis', $row)
|
||||
{{ Number::currency($row->total_cost_basis ?? 0) }}
|
||||
{{ Number::currency($row->total_cost_basis ?? 0, $row->market_data->currency) }}
|
||||
@endscope
|
||||
@scope('cell_realized_gain_dollars', $row)
|
||||
{{ Number::currency($row->realized_gain_dollars ?? 0) }}
|
||||
{{ Number::currency($row->realized_gain_dollars ?? 0, $row->market_data->currency) }}
|
||||
@endscope
|
||||
@scope('cell_market_gain_dollars', $row)
|
||||
{{ Number::currency($row->market_gain_dollars ?? 0) }}
|
||||
{{ Number::currency($row->market_gain_dollars ?? 0, $row->market_data->currency) }}
|
||||
@endscope
|
||||
@scope('cell_market_gain_percent', $row)
|
||||
<x-gain-loss-arrow-badge
|
||||
@@ -84,19 +83,19 @@ new class extends Component {
|
||||
/>
|
||||
@endscope
|
||||
@scope('cell_market_data_market_value', $row)
|
||||
{{ Number::currency($row->market_data_market_value ?? 0) }}
|
||||
{{ Number::currency($row->market_data_market_value ?? 0, $row->market_data->currency) }}
|
||||
@endscope
|
||||
@scope('cell_market_data_fifty_two_week_low', $row)
|
||||
{{ Number::currency($row->market_data_fifty_two_week_low ?? 0) }}
|
||||
{{ Number::currency($row->market_data_fifty_two_week_low ?? 0, $row->market_data->currency) }}
|
||||
@endscope
|
||||
@scope('cell_market_data_fifty_two_week_high', $row)
|
||||
{{ Number::currency($row->market_data_fifty_two_week_high ?? 0) }}
|
||||
{{ Number::currency($row->market_data_fifty_two_week_high ?? 0, $row->market_data->currency) }}
|
||||
@endscope
|
||||
@scope('cell_total_market_value', $row)
|
||||
{{ Number::currency($row->total_market_value ?? 0) }}
|
||||
{{ Number::currency($row->total_market_value ?? 0, $row->market_data->currency) }}
|
||||
@endscope
|
||||
@scope('cell_dividends_earned', $row)
|
||||
{{ Number::currency($row->dividends_earned ?? 0) }}
|
||||
{{ Number::currency($row->dividends_earned ?? 0, $row->market_data->currency) }}
|
||||
@endscope
|
||||
@scope('cell_market_data_updated_at', $row)
|
||||
{{ \Carbon\Carbon::parse($row->market_data_updated_at)->diffForHumans() }}
|
||||
@@ -1,3 +1,5 @@
|
||||
@use('App\Models\Currency')
|
||||
|
||||
<x-app-layout>
|
||||
<div x-data>
|
||||
|
||||
@@ -67,48 +69,56 @@
|
||||
|
||||
<x-ib-card title="{{ __('Fundamentals') }}" class="md:col-span-4">
|
||||
|
||||
@if(!empty($holding->market_data->market_cap))
|
||||
<p>
|
||||
<span class="font-bold">{{ __('Market Cap') }}: </span>
|
||||
${{ Number::forHumans($holding->market_data->market_cap ?? 0) }}
|
||||
{{ Currency::forHumans($holding->market_data->market_cap, $holding->market_data->currency) }}
|
||||
</p>
|
||||
@endif
|
||||
|
||||
@if(!empty($holding->market_data->forward_pe))
|
||||
<p>
|
||||
<span class="font-bold">{{ __('Forward PE') }}: </span>
|
||||
{{ $holding->market_data->forward_pe }}
|
||||
</p>
|
||||
@endif
|
||||
|
||||
@if(!empty($holding->market_data->trailing_pe))
|
||||
<p>
|
||||
<span class="font-bold">{{ __('Trailing PE') }}: </span>
|
||||
{{ $holding->market_data->trailing_pe }}
|
||||
</p>
|
||||
@endif
|
||||
|
||||
<p>
|
||||
<span class="font-bold">{{ __('Book Value') }}: </span>
|
||||
{{ $holding->market_data->book_value }}
|
||||
</p>
|
||||
@if(!empty($holding->market_data->book_value))
|
||||
<p>
|
||||
<span class="font-bold">{{ __('Book Value') }}: </span>
|
||||
{{ Number::currency($holding->market_data->book_value, $holding->market_data->currency) }}
|
||||
</p>
|
||||
@endif
|
||||
|
||||
<p>
|
||||
<span class="font-bold">{{ __('52 week') }}: </span>
|
||||
|
||||
<x-fifty-two-week-range
|
||||
:low="$holding->market_data->fifty_two_week_low"
|
||||
:high="$holding->market_data->fifty_two_week_high"
|
||||
:current="$holding->market_data->market_value"
|
||||
/>
|
||||
<x-fifty-two-week-range :market-data="$holding->market_data" />
|
||||
</p>
|
||||
|
||||
@if(!empty($holding->market_data->dividend_yield))
|
||||
<p>
|
||||
<span class="font-bold">{{ __('Dividend Yield') }}: </span>
|
||||
{{ Number::percentage(
|
||||
$holding->market_data->dividend_yield ?? 0,
|
||||
$holding->market_data->dividend_yield,
|
||||
$holding->market_data->dividend_yield < 1 ? 2 : 0
|
||||
) }}
|
||||
</p>
|
||||
@endif
|
||||
|
||||
@if(!empty($holding->market_data->last_dividend_date))
|
||||
<p>
|
||||
<span class="font-bold">{{ __('Last Dividend Paid') }}: </span>
|
||||
{{ $holding->market_data?->last_dividend_date?->format('F d, Y') ?? '' }}
|
||||
{{ $holding->market_data->last_dividend_date->format('F d, Y') }}
|
||||
</p>
|
||||
@endif
|
||||
|
||||
</x-ib-card>
|
||||
|
||||
@@ -164,7 +174,7 @@
|
||||
</x-ib-card>
|
||||
|
||||
@if(config('services.ai_chat_enabled'))
|
||||
{{-- // TODO: add to system prompt:
|
||||
{{-- // todo: add to system prompt:
|
||||
// Additionally, here is some recent news about {$this->holding->symbol}:
|
||||
// And their latest SEC filings: --}}
|
||||
@livewire('ai-chat-window', [
|
||||
@@ -209,7 +219,7 @@
|
||||
* 52 week high: {$holding->market_data->fifty_two_week_high}
|
||||
* Dividend yield: {$holding->market_data->dividend_yield}
|
||||
|
||||
This data is current as of today's date: " . now()->format('Y-m-d') . ". Based on this current market data, quantity owned, and average cost basis, you should determine if the {$holding->symbol} holding is making or losing money.
|
||||
This data is current as of today's date: " . now()->toDateString() . ". Based on this current market data, quantity owned, and average cost basis, you should determine if the {$holding->symbol} holding is making or losing money.
|
||||
|
||||
Below is the question from the investor. Considering these facts, provide a concise response to the following question (give a direct response). Limit your response to no more than 75 words and consider using a common decision framework. Use github style markdown for any formatting:"
|
||||
])
|
||||
|
||||
+18
-15
@@ -35,16 +35,7 @@ new class extends Component
|
||||
{
|
||||
$filterMethod = collect($this->scopeOptions)->where('id', $this->scope)->first();
|
||||
|
||||
$dailyChangeQuery = DailyChange::myDailyChanges()->selectRaw('
|
||||
date,
|
||||
SUM(total_market_value) as total_market_value,
|
||||
SUM(total_cost_basis) as total_cost_basis,
|
||||
SUM(total_gain) as total_gain
|
||||
/* ,
|
||||
SUM(realized_gains) as realized_gains,
|
||||
SUM(total_dividends_earned) as total_dividends_earned
|
||||
*/
|
||||
');
|
||||
$dailyChangeQuery = DailyChange::withDailyPerformance();
|
||||
|
||||
if (isset($this->portfolio)) {
|
||||
|
||||
@@ -54,18 +45,30 @@ new class extends Component
|
||||
} else {
|
||||
|
||||
// dashboard
|
||||
$dailyChangeQuery->withoutWishlists();
|
||||
$dailyChangeQuery->myDailyChanges()->withoutWishlists();
|
||||
}
|
||||
|
||||
if ($filterMethod['method']) {
|
||||
|
||||
$dailyChangeQuery->whereDate('date', '>=', now()->{$filterMethod['method']}(...$filterMethod['args']));
|
||||
$dailyChangeQuery->whereDate('daily_change.date', '>=', now()->{$filterMethod['method']}(...$filterMethod['args']));
|
||||
}
|
||||
|
||||
$dailyChange = $dailyChangeQuery
|
||||
->orderBy('date')
|
||||
$dailyChange = $dailyChangeQuery->get();
|
||||
|
||||
$dailyChange = $dailyChange
|
||||
->sortBy('date')
|
||||
->groupBy('date')
|
||||
->get();
|
||||
->map(function ($group) {
|
||||
return (object) [
|
||||
'date' => $group->first()->date->toDateString(),
|
||||
'total_market_value' => $group->sum('total_market_value'),
|
||||
'total_cost_basis' => $group->sum('total_cost_basis'),
|
||||
'total_gain' => $group->sum('total_gain'),
|
||||
'realized_gain_dollars' => $group->sum('realized_gain_dollars'),
|
||||
'total_dividends_earned' => $group->sum('total_dividends_earned'),
|
||||
];
|
||||
})
|
||||
->values();
|
||||
|
||||
return [
|
||||
'series' => [
|
||||
@@ -1,3 +1,5 @@
|
||||
@use('App\Models\Currency')
|
||||
|
||||
<x-app-layout>
|
||||
<div x-data>
|
||||
|
||||
@@ -63,30 +65,29 @@
|
||||
|
||||
<div class="grid sm:grid-cols-5 gap-5">
|
||||
|
||||
|
||||
<x-card class="col-span-5 sm:col-span-1 bg-slate-100 dark:bg-base-200 rounded-lg">
|
||||
<div class="text-sm text-gray-400 whitespace-nowrap truncate">{{ __('Market Gain/Loss') }}</div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->total_gain_dollars) }} </div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->get('total_gain_dollars', 0)) }} </div>
|
||||
</x-card>
|
||||
|
||||
<x-card class="col-span-5 sm:col-span-1 bg-slate-100 dark:bg-base-200 rounded-lg">
|
||||
<div class="text-sm text-gray-400 whitespace-nowrap truncate">{{ __('Total Cost Basis') }}</div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->total_cost_basis) }} </div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->get('total_cost_basis', 0)) }} </div>
|
||||
</x-card>
|
||||
|
||||
<x-card class="col-span-5 sm:col-span-1 bg-slate-100 dark:bg-base-200 rounded-lg">
|
||||
<div class="text-sm text-gray-400 whitespace-nowrap truncate">{{ __('Total Market Value') }}</div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->total_market_value) }} </div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->get('total_market_value', 0)) }} </div>
|
||||
</x-card>
|
||||
|
||||
<x-card class="col-span-5 sm:col-span-1 bg-slate-100 dark:bg-base-200 rounded-lg">
|
||||
<div class="text-sm text-gray-400 whitespace-nowrap truncate">{{ __('Realized Gain/Loss') }}</div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->realized_gain_dollars) }} </div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->get('realized_gain_dollars', 0)) }} </div>
|
||||
</x-card>
|
||||
|
||||
<x-card class="col-span-5 sm:col-span-1 bg-slate-100 dark:bg-base-200 rounded-lg">
|
||||
<div class="text-sm text-gray-400 whitespace-nowrap truncate">{{ __('Dividends Earned') }}</div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->total_dividends_earned) }} </div>
|
||||
<div class="font-black text-xl"> {{ Number::currency($metrics->get('total_dividends_earned', 0)) }} </div>
|
||||
</x-card>
|
||||
|
||||
</div>
|
||||
@@ -175,7 +176,7 @@
|
||||
|
||||
{$formattedHoldings}
|
||||
|
||||
This data is current as of today's date: " . now()->format('Y-m-d') . ". Based on the current market data, quantity owned, and average cost basis, you can determine the performance of any holding.
|
||||
This data is current as of today's date: " . now()->toDateString() . ". Based on the current market data, quantity owned, and average cost basis, you can determine the performance of any holding.
|
||||
|
||||
Below is the question from the investor. Considering these facts, provide a concise response to the following question (give a direct response). Limit your response to no more than 75 words and consider using a common decision framework. Use github style markdown for any formatting:"
|
||||
])
|
||||
|
||||
+17
-15
@@ -1,15 +1,15 @@
|
||||
<?php
|
||||
|
||||
use Livewire\WithFileUploads;
|
||||
use Livewire\Volt\Component;
|
||||
use Mary\Traits\Toast;
|
||||
use App\Models\BackupImport as BackupImportModel;
|
||||
use App\Imports\BackupImport;
|
||||
use App\Exports\BackupExport;
|
||||
use App\Models\BackupImport as BackupImportModel;
|
||||
use Livewire\Attributes\Rule;
|
||||
use Livewire\Volt\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
use Mary\Traits\Toast;
|
||||
|
||||
new class extends Component {
|
||||
new class extends Component
|
||||
{
|
||||
use Toast;
|
||||
use WithFileUploads;
|
||||
|
||||
@@ -18,23 +18,26 @@ new class extends Component {
|
||||
public $file;
|
||||
|
||||
public bool $importStatusDialog = false;
|
||||
|
||||
public ?BackupImportModel $backupImport = null;
|
||||
|
||||
public int $percent = 10;
|
||||
|
||||
// methods
|
||||
public function import()
|
||||
public function import()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
if (!RateLimiter::attempt('import:'.auth()->user()->id, $perMinute = 3, fn()=>null)) {
|
||||
if (! RateLimiter::attempt('import:'.auth()->user()->id, $perMinute = 3, fn () => null)) {
|
||||
|
||||
$this->error(__('Hang on! You\'re doing that too much.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->backupImport = BackupImportModel::create([
|
||||
'user_id' => auth()->user()->id,
|
||||
'path' => $this->file->getPathname()
|
||||
'path' => $this->file->getPathname(),
|
||||
]);
|
||||
|
||||
$this->importStatusDialog = true;
|
||||
@@ -45,17 +48,17 @@ new class extends Component {
|
||||
{
|
||||
if (Str::contains($this->backupImport?->message, 'portfolios')) {
|
||||
|
||||
$this->percent = (1/2) * 100;
|
||||
$this->percent = (1 / 2) * 100;
|
||||
}
|
||||
|
||||
if (Str::contains($this->backupImport?->message, 'transactions')) {
|
||||
|
||||
$this->percent = (3/4) * 100;
|
||||
$this->percent = (3 / 4) * 100;
|
||||
}
|
||||
|
||||
if (Str::contains($this->backupImport?->message, 'daily changes')) {
|
||||
|
||||
$this->percent = (7/8) * 100;
|
||||
$this->percent = (7 / 8) * 100;
|
||||
}
|
||||
|
||||
if ($this->backupImport?->status == 'failed') {
|
||||
@@ -75,9 +78,8 @@ new class extends Component {
|
||||
|
||||
public function downloadTemplate()
|
||||
{
|
||||
return Excel::download(new BackupExport(empty: true), now()->format('Y_m_d') . '_investbrain_template.xlsx');
|
||||
return Excel::download(new BackupExport(empty: true), now()->format('Y_m_d').'_investbrain_template.xlsx');
|
||||
}
|
||||
|
||||
}; ?>
|
||||
|
||||
<x-forms.form-section submit="import">
|
||||
@@ -87,13 +89,13 @@ new class extends Component {
|
||||
|
||||
<x-slot name="description">
|
||||
{{ __('Upload or recover your Investbrain portfolio and holdings.') }}
|
||||
<strong><a href="#" title="{{ __('Click to download import template.') }}" @click="$wire.downloadTemplate()"> {{ __('Download import template.') }}</a></strong>
|
||||
</x-slot>
|
||||
|
||||
<x-slot:form>
|
||||
|
||||
<div class="col-span-6 sm:col-span-4">
|
||||
<x-file wire:model="file" label="{{ __('Select a file') }}" hint="" accept=".xlsx" required />
|
||||
<p class="mt-4 text-xs text-secondary leading-tight"><a href="#" title="{{ __('Click to download import template.') }}" @click="$wire.downloadTemplate()"> {{ __('Download import template.') }}</a></p>
|
||||
</div>
|
||||
|
||||
<x-dialog-modal wire:model.live="importStatusDialog" persistent>
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Currency;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component
|
||||
{
|
||||
// props
|
||||
|
||||
public Collection $currencies;
|
||||
|
||||
public string $display_currency;
|
||||
|
||||
public ?string $locale;
|
||||
|
||||
public ?User $user;
|
||||
|
||||
// methods
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'locale' => ['required', 'in:'.implode(',', Arr::pluck(config('app.available_locales'), 'locale'))],
|
||||
'display_currency' => ['required', 'exists:currencies,currency'],
|
||||
];
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->currencies = Currency::get();
|
||||
$this->display_currency = auth()->user()->getCurrency();
|
||||
$this->locale = auth()->user()->getLocale();
|
||||
$this->user = auth()->user();
|
||||
}
|
||||
|
||||
public function updateProfileInformation()
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
|
||||
$this->validate();
|
||||
|
||||
$this->user->options = array_merge($this->user->options ?? [], [
|
||||
'locale' => $this->locale,
|
||||
'display_currency' => $this->display_currency,
|
||||
]);
|
||||
|
||||
$this->user->save();
|
||||
|
||||
cache()->tags(['metrics-'.$this->user->id])->flush();
|
||||
|
||||
$this->dispatch('saved');
|
||||
|
||||
//$this->js('window.location.reload();');
|
||||
}
|
||||
}; ?>
|
||||
<x-forms.form-section submit="updateProfileInformation">
|
||||
<x-slot name="title">
|
||||
{{ __('Locale Options') }}
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="description">
|
||||
{{ __('Adjust localization options for your preferred region.') }}
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="form">
|
||||
|
||||
<div class="col-span-6 sm:col-span-4">
|
||||
<x-select
|
||||
label="{{ __('Locale') }}"
|
||||
class="select block mt-1 w-full"
|
||||
:options="config('app.available_locales')"
|
||||
option-value="locale"
|
||||
option-label="label"
|
||||
placeholder="Choose a locale"
|
||||
wire:model="locale"
|
||||
id="locale"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-span-6 sm:col-span-4">
|
||||
<x-select
|
||||
label="{{ __('Display Currency') }}"
|
||||
class="select block mt-1 w-full"
|
||||
:options="$currencies"
|
||||
option-value="currency"
|
||||
option-label="label"
|
||||
placeholder="Choose a display currency"
|
||||
wire:model="display_currency"
|
||||
id="display_currency"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="actions">
|
||||
<x-forms.action-message class="me-3" on="saved">
|
||||
{{ __('Saved.') }}
|
||||
</x-forms.action-message>
|
||||
|
||||
<x-button type="submit">
|
||||
{{ __('Save') }}
|
||||
</x-button>
|
||||
</x-slot>
|
||||
</x-forms.form-section>
|
||||
@@ -7,7 +7,13 @@
|
||||
|
||||
<x-section-border hide-on-mobile />
|
||||
@endif
|
||||
|
||||
<div class="mt-10 sm:mt-0">
|
||||
@livewire('localization-form')
|
||||
</div>
|
||||
|
||||
<x-section-border hide-on-mobile />
|
||||
|
||||
@if (Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::updatePasswords()))
|
||||
<div class="mt-10 sm:mt-0">
|
||||
@livewire('profile.update-password-form')
|
||||
|
||||
+53
-16
@@ -1,10 +1,13 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Currency;
|
||||
use App\Models\MarketData;
|
||||
use App\Models\Portfolio;
|
||||
use App\Models\Transaction;
|
||||
use App\Rules\QuantityValidationRule;
|
||||
use App\Rules\SymbolValidationRule;
|
||||
use App\Traits\WithTrimStrings;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Volt\Component;
|
||||
use Mary\Traits\Toast;
|
||||
|
||||
@@ -34,6 +37,10 @@ new class extends Component
|
||||
|
||||
public bool $confirmingTransactionDeletion = false;
|
||||
|
||||
public Collection $currencies;
|
||||
|
||||
public string $currency;
|
||||
|
||||
// methods
|
||||
public function rules()
|
||||
{
|
||||
@@ -41,13 +48,14 @@ new class extends Component
|
||||
'symbol' => ['required', 'string', new SymbolValidationRule],
|
||||
'transaction_type' => 'required|string|in:BUY,SELL',
|
||||
'portfolio_id' => 'required|exists:portfolios,id',
|
||||
'date' => ['required', 'date_format:Y-m-d', 'before_or_equal:'.now()->format('Y-m-d')],
|
||||
'date' => ['required', 'date_format:Y-m-d', 'before_or_equal:'.now()->toDateString()],
|
||||
'quantity' => [
|
||||
'required',
|
||||
'numeric',
|
||||
'min:0',
|
||||
'gt:0',
|
||||
new QuantityValidationRule($this->portfolio, $this->symbol, $this->transaction_type, $this->date),
|
||||
],
|
||||
'currency' => ['required', 'exists:currencies,currency'],
|
||||
'cost_basis' => 'exclude_if:transaction_type,SELL|min:0|numeric',
|
||||
'sale_price' => 'exclude_if:transaction_type,BUY|min:0|numeric',
|
||||
];
|
||||
@@ -55,20 +63,31 @@ new class extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->currencies = Currency::list();
|
||||
$this->currency = auth()->user()->getCurrency();
|
||||
|
||||
if (isset($this->transaction)) {
|
||||
|
||||
$this->currency = $this->transaction->market_data->currency;
|
||||
|
||||
$this->symbol = $this->transaction->symbol;
|
||||
$this->transaction_type = $this->transaction->transaction_type;
|
||||
$this->portfolio_id = $this->transaction->portfolio_id;
|
||||
$this->date = $this->transaction->date->format('Y-m-d');
|
||||
$this->date = $this->transaction->date->toDateString();
|
||||
$this->quantity = $this->transaction->quantity;
|
||||
$this->cost_basis = $this->transaction->cost_basis;
|
||||
$this->sale_price = $this->transaction->sale_price;
|
||||
|
||||
} else {
|
||||
|
||||
if (isset($this->symbol)) {
|
||||
|
||||
$this->currency = MarketData::getMarketData($this->symbol)?->currency;
|
||||
}
|
||||
|
||||
$this->transaction_type = 'BUY';
|
||||
$this->portfolio_id = isset($this->portfolio) ? $this->portfolio->id : '';
|
||||
$this->date = now()->format('Y-m-d');
|
||||
$this->date = now()->toDateString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +119,7 @@ new class extends Component
|
||||
|
||||
$this->dispatch('transaction-saved');
|
||||
|
||||
$this->success(__('Transaction created'), redirectTo: route('holding.show', ['portfolio' => $this->portfolio->id, 'symbol' => $this->symbol]));
|
||||
$this->success(__('Transaction created'), redirectTo: route('holding.show', ['portfolio' => $this->portfolio->id, 'symbol' => $transaction->symbol]));
|
||||
}
|
||||
|
||||
public function delete()
|
||||
@@ -111,11 +130,6 @@ new class extends Component
|
||||
|
||||
$this->success(__('Transaction deleted'), redirectTo: route('holding.show', ['portfolio' => $this->portfolio->id, 'symbol' => $this->symbol]));
|
||||
}
|
||||
|
||||
public function updatedSymbol($value)
|
||||
{
|
||||
$this->symbol = strtoupper($value);
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div class="" x-data="{ transaction_type: @entangle('transaction_type') }">
|
||||
@@ -149,21 +163,44 @@ new class extends Component
|
||||
label="{{ __('Sale Price') }}"
|
||||
wire:model.number="sale_price"
|
||||
required
|
||||
prefix="USD"
|
||||
type="number"
|
||||
step="any"
|
||||
/>
|
||||
{{-- money --}}
|
||||
>
|
||||
<x-slot:prepend>
|
||||
|
||||
<x-select
|
||||
class="rounded-e-none border-e-0 bg-base-200"
|
||||
icon="o-banknotes"
|
||||
:options="$currencies"
|
||||
option-value="currency"
|
||||
option-label="currency"
|
||||
wire:model="currency"
|
||||
id="currency"
|
||||
/>
|
||||
</x-slot:prepend>
|
||||
</x-input>
|
||||
@else
|
||||
<x-input
|
||||
label="{{ __('Cost Basis') }}"
|
||||
wire:model.number="cost_basis"
|
||||
required
|
||||
prefix="USD"
|
||||
type="number"
|
||||
step="any"
|
||||
/>
|
||||
{{-- money --}}
|
||||
>
|
||||
<x-slot:prepend>
|
||||
|
||||
<x-select
|
||||
class="rounded-e-none border-e-0 bg-base-200"
|
||||
icon="o-banknotes"
|
||||
:options="$currencies"
|
||||
option-value="currency"
|
||||
option-label="currency"
|
||||
wire:model="currency"
|
||||
id="currency"
|
||||
/>
|
||||
</x-slot:prepend>
|
||||
|
||||
</x-input>
|
||||
@endif
|
||||
|
||||
<x-slot:actions>
|
||||
+24
-14
@@ -6,30 +6,38 @@ use Illuminate\Support\Collection;
|
||||
use Livewire\Volt\Component;
|
||||
use Mary\Traits\Toast;
|
||||
|
||||
new class extends Component {
|
||||
|
||||
use Toast;
|
||||
new class extends Component
|
||||
{
|
||||
use Toast;
|
||||
|
||||
// props
|
||||
public Collection $transactions;
|
||||
|
||||
public ?Portfolio $portfolio;
|
||||
|
||||
public ?Transaction $editingTransaction;
|
||||
public Bool $shouldGoToHolding = true;
|
||||
public Bool $showPortfolio = false;
|
||||
public Bool $paginate = true;
|
||||
public Int $perPage = 5;
|
||||
public Int $offset = 0;
|
||||
|
||||
public bool $shouldGoToHolding = true;
|
||||
|
||||
public bool $showPortfolio = false;
|
||||
|
||||
public bool $paginate = true;
|
||||
|
||||
public int $perPage = 5;
|
||||
|
||||
public int $offset = 0;
|
||||
|
||||
protected $listeners = [
|
||||
'transaction-updated' => '$refresh',
|
||||
'transaction-saved' => '$refresh'
|
||||
'transaction-saved' => '$refresh',
|
||||
];
|
||||
|
||||
// methods
|
||||
public function showTransactionDialog($transactionId)
|
||||
{
|
||||
if (!auth()->user()->can('fullAccess', $this->portfolio)) {
|
||||
if (! auth()->user()->can('fullAccess', $this->portfolio)) {
|
||||
$this->error(__('You do not have permission to manage transactions for this portfolio'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -46,7 +54,6 @@ new class extends Component {
|
||||
{
|
||||
$this->offset = $this->offset + $amount;
|
||||
}
|
||||
|
||||
}; ?>
|
||||
|
||||
<div class="">
|
||||
@@ -86,9 +93,12 @@ new class extends Component {
|
||||
/>
|
||||
{{ $transaction->symbol }}
|
||||
({{ $transaction->quantity }}
|
||||
@ {{ $transaction->transaction_type == 'BUY'
|
||||
? Number::currency($transaction->cost_basis)
|
||||
: Number::currency($transaction->sale_price) }})
|
||||
@ {{ Number::currency(
|
||||
$transaction->transaction_type == 'BUY'
|
||||
? $transaction->cost_basis
|
||||
: $transaction->sale_price,
|
||||
$transaction->market_data->currency
|
||||
) }})
|
||||
|
||||
<x-loading x-show="loading" x-cloak class="text-gray-400 ml-2" />
|
||||
</x-slot:value>
|
||||
+15
-15
@@ -1,22 +1,23 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Transaction;
|
||||
use Illuminate\Support\Collection;
|
||||
use App\Models\User;
|
||||
use Livewire\Volt\Component;
|
||||
use Livewire\WithPagination;
|
||||
use App\Models\Currency;
|
||||
|
||||
new class extends Component {
|
||||
|
||||
new class extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
// props
|
||||
public User $user;
|
||||
|
||||
public ?Transaction $editingTransaction;
|
||||
|
||||
protected $listeners = [
|
||||
'transaction-updated' => '$refresh',
|
||||
'transaction-saved' => '$refresh'
|
||||
'transaction-saved' => '$refresh',
|
||||
];
|
||||
|
||||
public array $sortBy = ['column' => 'date', 'direction' => 'desc'];
|
||||
@@ -47,12 +48,11 @@ new class extends Component {
|
||||
public function transactions()
|
||||
{
|
||||
return auth()
|
||||
->user()
|
||||
->transactions()
|
||||
->orderBy(...array_values($this->sortBy))
|
||||
->paginate(10);
|
||||
->user()
|
||||
->transactions()
|
||||
->orderBy(...array_values($this->sortBy))
|
||||
->paginate(10);
|
||||
}
|
||||
|
||||
}; ?>
|
||||
|
||||
<div class="">
|
||||
@@ -96,19 +96,19 @@ new class extends Component {
|
||||
/>
|
||||
@endscope
|
||||
@scope('cell_cost_basis', $row)
|
||||
{{ Number::currency($row->cost_basis ?? 0) }}
|
||||
{{ Number::currency($row->cost_basis ?? 0, $row->market_data->currency) }}
|
||||
@endscope
|
||||
@scope('cell_total_cost_basis', $row)
|
||||
{{ Number::currency($row->total_cost_basis ?? 0) }}
|
||||
{{ Number::currency($row->total_cost_basis ?? 0, $row->market_data->currency) }}
|
||||
@endscope
|
||||
@scope('cell_gain_dollars', $row)
|
||||
{{ Number::currency($row->gain_dollars ?? 0) }}
|
||||
{{ Number::currency($row->gain_dollars ?? 0, $row->market_data->currency) }}
|
||||
@endscope
|
||||
@scope('cell_market_data_market_value', $row)
|
||||
{{ Number::currency($row->market_data_market_value ?? 0) }}
|
||||
{{ Number::currency($row->market_data_market_value ?? 0, $row->market_data->currency) }}
|
||||
@endscope
|
||||
@scope('cell_total_market_value', $row)
|
||||
{{ Number::currency($row->total_market_value ?? 0) }}
|
||||
{{ Number::currency($row->total_market_value ?? 0, $row->market_data->currency) }}
|
||||
@endscope
|
||||
</x-table>
|
||||
|
||||
Reference in New Issue
Block a user