Dashboard


Contents

Introduction

The client's dashboard is the first page, for a user who is done with the onboarding process, to see when they log in. It is a summary of the user's account and the current state of their investments.

The dashboard shows the user's Portfolio Balance, Investment, and Earnings cards. The dashboard also has a section for active goals, completed goals, and inactive goals.

Overview

The overview section gets data from the endpoint /api:fH8MNGPq:v1/auth/load_goals which runs App\Actions\GetPortfolioDataAction::make()->handle(Auth::id()); and displays the user's Portfolio Balance, Investment, and Earnings cards.

API Response Example

Here is an example of the API response:

{
    "portfolioBalance": {
        "value": 1456753.13,
        "formatted": "1,456,753.13"
    },
    "availableToWithdraw": {
        "value": 1456753.13,
        "formatted": "1,456,753.13"
    },
    "lockedBalance": {
        "value": 0,
        "formatted": "0.00"
    },
    "goals": {
        "short_term": [
            {
                ...
            }
        ],
        "long_term": [
            {
                ...
            }
        ]
    },
    "total_invested": 1336483,
    "total_interest": 120270.13,
    "portfolio_value": 1456753.13,
    "goals_ff": [],
    "savings": 100000,
    "goals_ef": {
        "ef": "no"
    },
    "goals_cf": [
        {
            ...
        }
    ],
    "goals_completed": [
        {
            ...
        }
    ],
    "goals_inactive": [
        {
            ...
        }
    ],
    "graph": {
        ...
    },
    "slider_array": []
}

In this section, we take interest in the total_invested,total_interest, and portfolio_value values. The data is retrieved at:

class GetPortfolioDataAction
{
    use AsAction;

    public function handle(int $userId): array
    {
        $portfolioSummary = $this->getPortfolioSummary($userId);

        .
        .
        .
            'total_invested' => $portfolioSummary->totalInvested,
            'total_interest' => $portfolioSummary->totalInterest,
            'portfolio_value' => $portfolioSummary->totalPortfolioValue,

The getPortfolioSummary(int $userId) returns a PortfolioSummaryDTO To calculate the Summary, we query for all user goals, and for each goal,

        foreach ($goals as $goal) {
            $currentValue = MaGetGoalCurrentValue::make()->handle($goal->id);
            $principle = MaGetPrincipleAmount::make()->handle($goal->id);
            $totalInvested = $totalInvested->plus($principle);
            $goalSummary = $this->buildGoalSummary($goal, $currentValue, $principle);

            if ($goal->isLongTerm()) {
                $longTermGoals[] = $goalSummary;
                $longTermTotal = $longTermTotal->plus($currentValue);
            } else {
                $shortTermGoals[] = $goalSummary;
                $shortTermTotal = $shortTermTotal->plus($currentValue);
            }
        }

        $totalPortfolioValue = $longTermTotal->plus($shortTermTotal);
        $totalInterest = $totalPortfolioValue->minus($totalInvested);

The above introduces us to two new Actions, App\Actions\MultiAsset\MaGetGoalCurrentValue which automatically calculates the goal's current value; and App\Actions\MultiAsset\MaGetPrincipleAmount which basically sums amount for all App\Models\GoalTransaction for the particular goal.

Active Goals

Active goals are goals that are currently being invested into by the user. The user can see the progress of the goal and the amount saved so far. The user can also see the target amount and the target date for the goal. The data is retrieved at:

class GetPortfolioDataAction
{
    use AsAction;

    public function handle(int $userId): array
    {
        .
        .
        .

        $data = array_merge($data, $this->getDashboardData($userId));

        return $data;
    }

The getDashboardData(int $userId) returns an array of goals_ff, goals_ef, goals_cf which are basically the active goals split into financial freedom goal, emergency fund goal, and custom goals respectively.

    $allGoals = Goal::query()
        ->select(['sys_goal_type_id', 'principle', 'current_nper', 'original_nper', 'target', 'value', 'title', 'id', 'created_at', 'savings', 'is_achieved', 'is_wishlist', 'deactivated_at'])
        ->where('users_id', $userId)
        ->get()
        ->groupBy(function ($goal) {
            if ($goal->is_achieved) {
                return 'completed';
            }
            if ($goal->deactivated_at !== null) {
                return 'inactive';
            }
            return 'active';
        });

    $activeGoals = $allGoals->get('active', collect());

Completed Goals

Completed goals are goals that the user has successfully saved up for. The console command php artisan goals:achieveGoals dispatches the App\Jobs\AchieveGoalsJob which checks if the goal has been achieved and completes it by running App\Actions\Goal\GoalCompleterAction::make()->handle($goal);.

    class GoalCompleterAction
    {
        use AsAction;

        public function handle(Goal $goal): void
        {
            $goal->value = MaGetGoalCurrentValue::make()->handle($goal->id);
            if ($goal->value < $goal->target) {
                return;
            }
            $goal->update([
                'is_achieved' => true,
                'status' => 'completed',
            ]);

            // TODO: Notify user of goal completion
        }
    }

The user can see the amount saved and the target amount for the goal. The data is retrieved at:

class GetPortfolioDataAction
{
    use AsAction;

    public function handle(int $userId): array
    {
        .
        .
        .

        $data = array_merge($data, $this->getDashboardData($userId));

        return $data;
    }

The getDashboardData(int $userId) returns an array of goals_completed which are basically the completed goals.

    $allGoals = Goal::query()
        ->select(['sys_goal_type_id', 'principle', 'current_nper', 'original_nper', 'target', 'value', 'title', 'id', 'created_at', 'savings', 'is_achieved', 'is_wishlist', 'deactivated_at'])
        ->where('users_id', $userId)
        ->get()
        ->groupBy(function ($goal) {
            if ($goal->is_achieved) {
                return 'completed';
            }
            if ($goal->deactivated_at !== null) {
                return 'inactive';
            }
            return 'active';
        });

    $goals_completed = $allGoals->get('completed', collect())
            ->map(fn(Goal $goal) => FormatGoalForDashboard::run($goal));

Inactive Goals

Inactive goals are goals that the user has stopped saving for. More information on this can be found in the Your Plan documentation. The user can see the amount saved and the target amount for the goal. The data is retrieved at:

class GetPortfolioDataAction
{
    use AsAction;

    public function handle(int $userId): array
    {
        .
        .
        .

        $data = array_merge($data, $this->getDashboardData($userId));

        return $data;
    }

The getDashboardData(int $userId) returns an array of goals_inactive which are basically the inactive goals.

    $allGoals = Goal::query()
        ->select(['sys_goal_type_id', 'principle', 'current_nper', 'original_nper', 'target', 'value', 'title', 'id', 'created_at', 'savings', 'is_achieved', 'is_wishlist', 'deactivated_at'])
        ->where('users_id', $userId)
        ->get()
        ->groupBy(function ($goal) {
            if ($goal->is_achieved) {
                return 'completed';
            }
            if ($goal->deactivated_at !== null) {
                return 'inactive';
            }
            return 'active';
        });

    $goals_inactive = $allGoals->get('inactive', collect())
            ->map(fn(Goal $goal) => FormatGoalForDashboard::run($goal));

The FormatGoalForDashboard::run($goal) is a class that formats the goal data for the dashboard found at:

App\Actions\Goals\FormatGoalForDashboard