View Model Implementation This is how the Laravel view model is used with the spares dashboard to remove all the logic from the blade view

The DashboardViewModel

<?php

namespace Modules\Spares\ViewModels;

use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Modules\Spares\Repositories\API\APIStorageRepository;
use Psr\SimpleCache\InvalidArgumentException;
use Throwable;

final class DashboardViewModel
{
    public array $returns = [];
    public array $returnsLabels = [];
    public array $returnsValues = [];
    public float $totalPOValue = 0.0;

    public array $inventory = [];
    public array $inventoryLabels = [];
    public array $inventoryValues = [];
    public float $totalInventoryValue = 0.0;

    public array $issues = [];
    public array $issuesLabels = [];
    public array $issuesValues = [];
    public float $totalIssuesValue = 0.0;

    public array $stock = [];
    public array $stockLabels = [];
    public array $stockValues = [];
    public float $totalStockValue = 0.0;
    public float $totalStockValueLastMonth = 0.0;

    public float $grandTotal = 0.0;

    public array $topTenIssues = [];
    public array $topTenInventory = [];

    /**
     * Logs are normalized for the Blade:
     * [
     *   ['key'=>'inventory','label'=>'<i ...>Adjustments Sync','run_at'=>'14 Oct 2025, 12:30','server_name'=>'maximo01'],
     *   ...
     * ]
     */
    public array $logs = [];

    private APIStorageRepository $api;
    private bool $useCache;
    private int $cacheMinutes;

    public function __construct(APIStorageRepository $api = null, ?bool $useCache = null, int $cacheMinutes = 60)
    {
        $this->api = $api ?? new APIStorageRepository();
        $this->useCache = $useCache ?? (bool)settings()->group('maximo')->get('dashboard_cache', false);
        $this->cacheMinutes = $cacheMinutes;
    }

    /**
     * Build the data ready fro the view
     * @return $this
     * @throws Throwable
     * @throws InvalidArgumentException
     */
    public function build(): self
    {
        $start = microtime(true);
        $apiToken = (string)$this->api->getApiToken();
        $params = [];

        // ------- Returns -------
        $this->returns = $this->fetch('/api/v1/returns/index', $apiToken, $params, 'returns');
        $returnsByWeek = $this->groupByIsoWeek($this->returns, 'SRRTRDT', 'SRRUCST');
        $this->returnsLabels = array_keys($returnsByWeek);
        $this->returnsValues = array_values($returnsByWeek);
        $this->totalPOValue = array_sum($this->returnsValues);

        // ------- Inventory -------
        $this->inventory = $this->fetch('/api/v1/inventory/index', $apiToken, $params, 'inventory');
        $this->totalInventoryValue = (float)(collect($this->inventory)->sum('SPACOST') ?: 0.0);
        $inventoryByWeek = $this->groupByIsoWeek($this->inventory, 'SPATRDT', 'SPACOST');
        $this->inventoryLabels = array_keys($inventoryByWeek);
        $this->inventoryValues = array_values($inventoryByWeek);

        // ------- Issues -------
        $this->issues = $this->fetch('/api/v1/issues/index', $apiToken, $params, 'issues');
        $issuesByWeek = $this->groupByIsoWeek($this->issues, 'SPRTRDT', 'SPRUCST');
        $this->issuesLabels = array_keys($issuesByWeek);
        $this->issuesValues = array_values($issuesByWeek);
        $this->totalIssuesValue = array_sum($this->issuesValues);

        // ------- Stock -------
        $stock = $this->fetch('/api/v1/stock/index', $apiToken, $params, 'stock');
        $stockLastMonth = $this->fetch('/api/v1/stock/index/last_month', $apiToken, $params, 'stock_last_month');

        $totalStockNow = (float)(collect($stock)->sum('SPSRCST') ?: 0.0);
        $this->totalStockValueLastMonth = (float)(collect($stockLastMonth)->sum('SPSRCST') ?: 0.0);
        $this->totalStockValue = $totalStockNow - $this->totalStockValueLastMonth;

        $combinedStock = array_merge(is_array($stock) ? $stock : [], is_array($stockLastMonth) ? $stockLastMonth : []);
        $this->stock = $combinedStock;

        $stockByMonth = $this->groupByMonth($combinedStock, 'SPSTRDT', 'SPSRCST');
        $this->stockLabels = array_keys($stockByMonth);
        $this->stockValues = array_values($stockByMonth);
        array_multisort($this->stockLabels, SORT_ASC, $this->stockValues);

        // ------- Top Tens (defensive) -------
        $this->topTenIssues = $this->fetch('/api/v1/issues/index/top_ten', $apiToken, $params, 'top_ten_issues');
        $this->topTenInventory = $this->fetch('/api/v1/inventory/index/top_ten', $apiToken, $params,
            'top_ten_inventory');

        // ------- Logs (with label mapping & formatted timestamps) -------
        $labelMap = [
            'INVENTORY' => '<i class="fa-fw fas fa-sliders-h mr-2"></i>Adjustments Sync',
            'ISSUES' => '<i class="fas fa-exchange-alt mr-2"></i>Item Usage Sync',
            'RETURNS' => '<i class="fa-fw fas fa-thermometer-full mr-2"></i>PO Analysis Sync',
            'STOCK' => '<i class="fa-fw fas fa-warehouse mr-2"></i>Movement Sync',
        ];

        $this->logs = collect(['RETURNS', 'STOCK', 'ISSUES', 'INVENTORY'])
            ->map(function (string $type) use ($labelMap) {
                $raw = (array)$this->api->getRunLog('/api/v1/runlog/last', $type);

                return [
                    'key' => strtolower($type),
                    'label' => $labelMap[$type] ?? e($type),
                    'run_at' => $this->formatDate($raw['run_at'] ?? null),
                    'server_name' => (string)($raw['server_name'] ?? 'N/A'),
                ];
            })
            ->values()
            ->toArray();

        $this->grandTotal = $this->totalPOValue + $this->totalIssuesValue + $this->totalInventoryValue + $this->totalStockValue;

        if (app()->environment(['local', 'dev', 'qat'])) {
            $runtime = round(microtime(true) - $start, 3);
            Log::channel('dashboard')->info(emoji('orange') . "Dashboard ViewModel build() runtime: {$runtime} seconds");
        }

        return $this;
    }

    /**
     * Returns all the variables as an array.
     * @return array
     */
    public function toArray(): array
    {
        return [
            'totalPOValue' => $this->totalPOValue,
            'totalInventoryValue' => $this->totalInventoryValue,
            'totalIssuesValue' => $this->totalIssuesValue,
            'totalStockValueLastMonth' => $this->totalStockValueLastMonth,
            'totalStockValue' => $this->totalStockValue,
            'grandTotal' => $this->grandTotal,

            'inventory' => $this->inventory,
            'inventoryLabels' => $this->inventoryLabels,
            'inventoryValues' => $this->inventoryValues,

            'stock' => $this->stock,
            'stockLabels' => $this->stockLabels,
            'stockValues' => $this->stockValues,

            'issues' => $this->issues,
            'issuesLabels' => $this->issuesLabels,
            'issuesValues' => $this->issuesValues,

            'returns' => $this->returns,
            'returnsLabels' => $this->returnsLabels,
            'returnsValues' => $this->returnsValues,

            'topTenIssues' => $this->topTenIssues,
            'topTenInventory' => $this->topTenInventory,
            'logs' => $this->logs,
        ];
    }

    // ----------------- helpers -----------------

    /**
     * A combined log formatter
     * @param string $url
     * @param string $token
     * @param array $params
     * @param string $name
     * @return array|mixed
     */
    private function fetch(string $url, string $token, array $params, string $name)
    {
        // normalize everything to strings for cache key safety
        $url = (string)$url;
        $name = (string)$name;
        $paramsJson = json_encode($params, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        $key = $name . '_' . md5($url . '|' . ($paramsJson ?? '[]'));

        if (!$this->useCache) {
            return $this->executeApi($token, $url, $params);
        }

        return Cache::remember($key, now()->addMinutes($this->cacheMinutes), function () use ($token, $url, $params) {
            return $this->executeApi($token, $url, $params);
        });
    }

    /**
     * A common API executor
     * @param string $token
     * @param string $url
     * @param array $params
     * @return array
     * @throws InvalidArgumentException
     */
    private function executeApi(string $token, string $url, array $params)
    {
        try {
            $data = APIStorageRepository::execute($token, $url, $params);
            // Always return an array so downstream code is predictable
            return is_array($data) ? $data : [];
        } catch (Throwable $e) {
            if (app()->environment(['local', 'dev', 'qat'])) {
                Log::channel('dashboard')->warning("API fetch failed for {$url}: {$e->getMessage()}");
            }
            return [];
        }
    }

    /** ISO year-week to avoid cross-year collisions, e.g. '2025-W01'. */
    private function groupByIsoWeek(array $data, string $dateField, string $valueField): array
    {
        return collect($data)
            ->filter(fn($i) => !empty($i[$dateField]) && is_numeric($i[$valueField] ?? null))
            ->groupBy(function ($i) use ($dateField) {
                $d = Carbon::parse($i[$dateField]);
                return $d->format('o-\WW');
            })
            ->map(fn($items) => collect($items)->sum($valueField))
            ->toArray();
    }

    private function groupByMonth(array $data, string $dateField, string $valueField): array
    {
        return collect($data)
            ->filter(fn($i) => !empty($i[$dateField]) && is_numeric($i[$valueField] ?? null))
            ->groupBy(fn($i) => Carbon::parse($i[$dateField])->format('Y-m'))
            ->map(fn($items) => collect($items)->sum($valueField))
            ->toArray();
    }

    /**
     * Nice helper for format dates
     * @param string|null $value
     * @return string
     */
    private function formatDate(?string $value): string
    {
        if (empty($value)) {
            return 'N/A';
        }
        try {
            return Carbon::parse($value)->format('d M Y, H:i');
        } catch (Throwable $e) {
            return 'N/A';
        }
    }
}

And the new controller DashBoardController.php

<?php

namespace Modules\Spares\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Contracts\View\View;
use Modules\Spares\ViewModels\DashboardViewModel;

class DashboardController extends Controller
{

    /**
     * Main View Loader
     * now uses a view model
     * @return View
     *
     */
    public function index(): View
    {
        $dashboardViewModel = (new DashboardViewModel())->build();

        if (app()->environment(['local', 'dev', 'qat'])) {
            $useCache = (bool)settings()->group('maximo')->get('dashboard_cache', false);
            logger()->channel('dashboard')->info(
                $useCache
                    ? emoji('green') . 'Using Cached Data'
                    : emoji('red') . 'Not Using Cached Data'
            );
        }
        return view('spares::Components.dashboard', $dashboardViewModel->toArray());
    }
}

This abstracts the data from the view and makes it far more maintainable and extendable.