Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

elemes's avatar

Pass multiple of the same card to default Nova dashboard

Is it possible to pass multiples of the same card in the NovaServiceProvider if the card constructor accepts parameters that would produce different metric values per card?

An example use case would be to quickly show multiple cards based on different users and the content statuses of the content they have assigned to them.

I did quick and dirty test with a partition card and found that the default card params were accepted and displayed correctly (e.g. title, helpText, etc.), but the custom return values (manual built format) for each partition card would not show, only the first manual result array shows across all cards, same values... even though the array used for the custom return values is confirmed to be different per instantiated card instance. See below, again this was a quick and dirty test to see if it was possible.

Passing multiple of same card but with diff params @NovaServiceProvider

    /**
     * Get the cards that should be displayed on the default Nova dashboard.
     *
     * @return array
     */
    protected function cards()
    {
        $ret_arr = [
            (new ItemsAssigned)->canSee(function($request) {
                return !$request->user()->is_admin;
            })
        ];

        $non_admin_users =  User::where('is_admin', '<>', 1)->pluck('id')->toArray();
        
        foreach($non_admin_users as $user_id) {

            $ret_arr[] = (new ItemStatusByUser($user_id))->canSee(function($request) {
                return $request->user()->is_admin;
            });

        }

        //dd($ret_arr);
        return $ret_arr;

        //return [
        //    new Help,
        //];
    }

@ItemStatusByUser::The card class with constructor that accepts some params

<?php

namespace App\Nova\Metrics;

use App\Item;
use App\User;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Metrics\Partition;
use Illuminate\Support\Str;

class ItemStatusByUser extends Partition
{

    private $user_item_status_arr;
    private $user_id;

    public function __construct($user_id)
    {
        $this->user_id = $user_id;
        //$this->name = "(".$this->user_id.") ". User::where('id',$this->user_id)->pluck('name')->first();
        $this->name = Str::random(32);

        $item_status = Item::where('user_id',$user_id)->pluck('status')->countBy();

        $status_lookup_arr = [
            '0' => 'Draft',
            '1' => 'Needs Review',
            '2' => 'Requires Edit',
            '3' => 'Approved',
        ];

        foreach ($item_status as $status => $count) {
            if (array_key_exists($status, $status_lookup_arr)) {
                $this->user_item_status_arr[$status_lookup_arr[$status]] = $count;
            }
        }

    }

    
    /**
     * Calculate the value of the metric.
     *
     * @param  \Laravel\Nova\Http\Requests\NovaRequest  $request
     * @return mixed
     */
    public function calculate(NovaRequest $request)
    {

        return $this->result($this->user_item_status_arr)->colors([
            'Draft' => '#434D59',
            'Needs Review' => '#F2C438',
            'Requires Edit' => '#F25749',
            'Approved' => '#50BFA0',
        ]);

    }

The output of the array passed to the dashboard // notice the names are different

array:4 [▼
  0 => App\Nova\Metrics\ItemsAssigned {#431 ▶}
  1 => App\Nova\Metrics\ItemStatusByUser {#426 ▼
    -user_item_status_arr: array:3 [▼
      "Draft" => 349
      "Needs Review" => 1
      "Approved" => 8
    ]
    -user_id: 2
    +component: "partition-metric"
    +roundingPrecision: 0
    +roundingMode: 1
    +name: "PdAHDK9pZjnUdrKTQkB7jG9aWkxFfeZx"
    +refreshWhenActionRuns: false
    +width: "1/3"
    +onlyOnDetail: false
    +meta: []
    +seeCallback: Closure($request) {#429 ▶}
    +helpText: null
    +helpWidth: 250
  }
  2 => App\Nova\Metrics\ItemStatusByUser {#427 ▼
    -user_item_status_arr: array:2 [▼
      "Draft" => 350
      "Approved" => 8
    ]
    -user_id: 3
    +component: "partition-metric"
    +roundingPrecision: 0
    +roundingMode: 1
    +name: "i1vs2tkLZFgD3F1zsitXo1hwHB7DJxA8"
    +refreshWhenActionRuns: false
    +width: "1/3"
    +onlyOnDetail: false
    +meta: []
    +seeCallback: Closure($request) {#419 ▶}
    +helpText: null
    +helpWidth: 250
  }
  3 => App\Nova\Metrics\ItemStatusByUser {#788 ▼
    -user_item_status_arr: array:4 [▼
      "Draft" => 347
      "Needs Review" => 1
      "Requires Edit" => 1
      "Approved" => 9
    ]
    -user_id: 4
    +component: "partition-metric"
    +roundingPrecision: 0
    +roundingMode: 1
    +name: "i7E1c3cvzghZR0kxkMfR1BGGZWI0jlw1"
    +refreshWhenActionRuns: false
    +width: "1/3"
    +onlyOnDetail: false
    +meta: []
    +seeCallback: Closure($request) {#789 ▶}
    +helpText: null
    +helpWidth: 250
  }
]

The multiple same cards showing on the dashboard // notice the values are the same alt text

0 likes
3 replies
GregoryC's avatar
GregoryC
Best Answer
Level 1

Hi,

Realise this is a little old, but I found this whilst attempting to do the same thing. I eventually found a way around it, so thought I might post it for fear it is useful to someone.

When you instantiate the metric cards with parameter it works fine. I have a big list of units, which can be classified into "types", which are subdivided into "categories", I wanted a metric for each of the types by category.

So I the type name and an id as parameters, and that works fine to display the name. In my resource file I can:

    public function cards(Request $request)
    {

        $cards = [];
        $types = \App\Models\Type::get();

        foreach($types as $type) {
            $cards[] = new UnitsPerCategory($type->id, $type->name);
        }

        return $cards;
    }

Then in the constructor of the UnitsPerCategory metric, I can pick those up, and use the name to display the name for the metric, and it works fine.

    function __construct($type_id, $type_name) {
        parent::__construct();
        $this->type_id = $type_id;
        $this->type_name = $type_name;
    }

    public function name()
    {
        return str::plural($this->type_name);
    }

    public function calculate(NovaRequest $request)
    {
        //$this->type_id = $request->get('type');
        return $this->count($request, \App\Models\Unit::where('type_id', $this->type_id), 'category_id')
            ->label(function ($value) {
                switch ($value) {
                    case null:
                        return '';
                    default:
                        return \App\Models\UnitCategory::find($value)->name;
                }
            });

The issue I had was that the metric itself returned the same result for all of the cards, like the user above.

After a bit of messing, I realised that the data to populate the card is actually being supplied in a seperate XHR request to the server. The same XHR was request was being made for each of the cards. The solution was simply to override the URL to be used for the metric.

    /**
     * Get the URI key for the metric.
     *
     * @return string
     */
    public function uriKey()
    {
        return 'units-per-category-' . $this->type_id;
    }

And it seems to work just fine! Ok, it would have been nicer to pass the id as a param, rather than changing the URL. I tried 'units-per-category?type=' . $this->type_id; This worked to make the XHRs include the parameter, but got 404 responses, obviously the URL key is being used to setup the routing dynamically.

It's not ideal, purists will of course tell me you should not change the url like this to put a parameter in there, and indeed that you should not have an id in the URL at all, but hey, it works.

Hope it might be useful to someone!

4 likes

Please or to participate in this conversation.