revosystems/sidecar

Laravel report tool

4.1.5 2024-10-10 15:43 UTC

README

Install

composer require revosystems/sidecar

You need to have tailwind 2.x and apline 3.x in you main template

Add the blades path to the tailwindcss config file

purge: [
    ...
    './vendor/revosystems/sidecar/**/*.blade.php',
  ],

And define your brand color like so:

theme: {
    extend: {
      colors: {
        'brand': '#F2653A',
      }
    },
  },

You should also include the chart.js and choices.js to your main template's header with this line:

{!! \Revo\Sidecar\Sidecar::dependencies() !!}

Configuration

Publish the configuration tot adapt it to you project

php artisan vendor:publish

In config/sidecar.php you will find the following parameters that can be adapted for your project

In assets/css/sidecar.css you will find the default styles

You should add those files to your assets compilation.

Global Variables

You can customize some runtime variables implementing the serving callback

class AppServiceProvider extends ServiceProvider	
    public function boot() {
        Sidecar::$usesMultitenant = true;	// When true, all the caches and jobs will use the `auth()->user()->id` as prefix
	    Sidecar::serving(function(){	
	            \Revo\Sidecar\ExportFields\Date::$timezone = auth()->user()->timezone;							// The timezone to display the dates
	            \Revo\Sidecar\ExportFields\Date::$openingTime = auth()->user()->getBusiness()->openingTime;		// To define a day change time instead of 00:00
	            \Revo\Sidecar\ExportFields\Currency::setFormatter('es_ES', auth()->user()->currency ?? 'EUR');	// For the currency field
        });
    }

Reports

To create your report you should create a new file caled WathereverYouWantReport (note it needs to end with Report) in the folder you defined in the reportsPath of the config file This report class needs to extend the main Revo\Sidecar\Report class

And define the main model class and implement the fields method

<?php

namespace App\Reports;

use Revo\Sidecar\Report;
use App\Post;

class OrdersReport extends Report {

	protected $model  = Post::class;

	public function getFields() : array{
     	return [ ];	
    }
}

Now we just need to define the fields we want to show from our report

We can also add default query filters overriding the query() method

public function query() : Builder {
    return parent::query()->withTrashed();
}

We can also add MainActions as a button in the top right corner.

public function mainActions(): array
{
    return [
        MainAction::make(?string $title = null, ?string $icon = null, ?string $url = '')
    ];
}

More features

Fields

You can define the export fields with a simple array, most of them share the same features

Creation

ExportField::make($field, $title, $dependsOnField)

Default options
Text

· When filtering it will perform a like and you can enter you custom search text

Number

· It will align the row to the right · When grouping by, by default will do a sum (you can change to it with the onGroupingBy() funciton) · When filtering it will allow you to chose the operator and the amount · It provides the function trimZeros() that will remove the trailing zeros of decimal values

Decimal

· Extends from number

Currency

· Extends from number

Percentage

· Extends from number and adds a % symbol to the html export

Date

· This field allows different grouping by options (hour, day, dayOfWeek, week, month, quarter) · When enabling the filterable, by default uses the last 7 days · The filterOnClick option, performs a depth grouping filter, so if you group by month, and you click Novemeber, it will filter just november, grouping by week · Date field also comes with a timeFilterable() that shows a time frame filter as well

Computed

· This field allows you to perform operations on the field, for example Computed::make('guests/total') · You can define the groupingBy operation as well Computed::make('guests/total')->onGroupingBy('sum(guests)/sum(total)')

Id

· Id field shows the id of the row · When grouping by, it will perform a count · It is very common to use the id only when grouping Id::make()->onlyWhenGrouping()

BelongsTo

· When you have a belongs to relationship on your model, you can use this field to automatically create the filters/groups by · By default it will use the name field on the relationship, use the relationShipDisplayField($field) function to use another field · It will perform the needed joins when sorting / grouping By
· You can scope the belongs by filter options by providing the list of ids filterOptionsIdsScope()
· If you need to perform the join always you can call the defaultJoin()

You would usually filter the whole query as well to avoid accessing issues

BelongsToThrough

· When you have a belongs to through relationship on your model, you can use this field to automatically create the filters/groups by, it will perform · By default it will use the name field on the relationship, use the relationShipDisplayField($field) function to use another field · You need to define the pivot relation using the through() function BelongsToThrough::make('user')->trough('comment') · It will perform the needed joins when sorting / grouping By

HasMany

· This field, will display all the hasMany models related, imploding the name with a , . By default it will use the name field on the relation, you can change it with relationShipDisplayField($field) · This field is not filterable neither groupable

HasOne

· It provides the defaultJoin() option to perform the join in every query · It is not filterable neither groupable

Enum

· It works for fields that are enum (consts) like a status or type · There is the options() function where you define the array of [["value" => "displayName"]] that will be used to display and filter

Icon

· A simple field, that will show the fontawesome icon using the field value as icon name

Create your ExportField

You can create your own Export Fields by just extending any of the previous fields, or the main Sidecar\ExportFields\ExportField

<?php

namespace App\Reports\Sidecar;

use Revo\Sidecar\ExportFields\Link;

class Preview extends Link
{
    protected ?string $linkClasses = "showPopup";

    public static function make($field, $title = null, $dependsOnField = null)
    {
        $field = parent::make($field, $title, $dependsOnField);
        $field->route = $title;
        return $field;
    }

    public function getTitle(): string
    {
        return "";
    }

    public function getLinkTitle($row) : string {
        return '<i class="fa fa-eye fa-fw"></i>';
    }

    public function toHtml($row): string
    {
        $link = route($this->route, $this->getValue($row));
        return "<a href='{$link}' class='{$this->linkClasses}' style='color:gray;'>{$this->getLinkTitle($row)}</a>";
    }
}

The ExportField has the function toHtml($row) that is the one that will display the value when in the browser, when exporting to CSV it will use the getValue($row) so you can customize both.

It is common to override the getFilterKey() with something like non-filterable to avoid collisions with the common fields, in the case of this Preview file, would collide with the Id field as it is the one to use in the link

Widgets

A report can define a set of widgets to summarize what is being displayed (not paginated) so when it is not grouping it will show them. Right now there are just two possible widgets

<?php

namespace App\Reports\V2;

use App\Models\Orders\Order;

use Revo\Sidecar\Widgets\Count;
use Revo\Sidecar\Widgets\Sum;

class OrdersReport extends Report
{
    protected $model  = Order::class;

    public function getFields() : array{
        return [
            ...
        ];	
    }

    public function getWidgets() : array
    {
        return [
            Count::make('id', __('admin.count')),
            Sum::make('guests', trans_choice('admin.guest', 2)),
        ];
    }

}

Dashboard

Sidecar also comes with nice dashboard panels that can be embeded into any page

You just need to create one by extending the Revo\Sidecar\Panel and define the dimesion and the metric You need to pass the filters to apply to this report.

<?php

namespace App\Reports\V2\Sidecar\Widgets;

use App\Models\Orders\Order;
use App\Reports\V2\OrdersReport;
use Illuminate\Database\Eloquent\Builder;
use Revo\Sidecar\ExportFields\Currency;
use Revo\Sidecar\ExportFields\Date;
use Revo\Sidecar\ExportFields\ExportField;
use Revo\Sidecar\Filters\Filters;
use Revo\Sidecar\Panels\Panel;

class Sales extends Panel
{
    protected $model            = Order::class;
    protected ?string $tooltip  = "salesByDayDesc";

    public function query() : Builder{
        return parent::query()->whereNull('canceled')->whereNull('merged')->whereNotNull('opened');
    }

    public function __construct()
    {
        $filters = (new Filters())->groupingBy(['opened' => 'day'])
                                  ->forPeriod('opened', 'last30days')
                                  ->sortBy('opened', 'asc');
        parent::__construct("salesByDay", $filters);
    }

    public function metricField(): ExportField
    {
        return Currency::make('total');
    }

    public function dimensionField(): ExportField
    {
        return Date::make('opened')->filterable();
    }

    public function getFullReportLink(): ?string
    {
        return url('reports/v2/orders?' . $this->filters->getQueryString() );
    }
}

You can implement the getFullReportLink() to link to a full report Note $filters->getQueryString() returns the query string to use for the url

Finally you can render them in your blade with something like this

<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
    @foreach($panels as $panel)
        {!! $panel->render() !!}
    @endforeach
</div>

This dashboard panels will be cached until the next day, and will be loaded with ajax.

Panel Type

There are different types of Panels defined by the enum PanelType, bar, list, trend (default), and pie

class TopPaymentMethods extends Panel
{
    ...

    public PanelType $type = PanelType::pie;

    ...
}
``