Skip to content

Commit

Permalink
Add support for showTotals in the Lists widget (#1262)
Browse files Browse the repository at this point in the history
  • Loading branch information
LukeTowers authored Dec 3, 2024
1 parent f67cb2c commit b6ab161
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 36 deletions.
8 changes: 8 additions & 0 deletions modules/backend/classes/ListColumn.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ class ListColumn
*/
public $sortable = true;

/**
* @var bool Specifies if this column can be summed.
*/
public $summable = true;

/**
* @var bool If set to false, disables the default click behavior when the column is clicked.
*/
Expand Down Expand Up @@ -152,6 +157,9 @@ protected function evalConfig($config)
if (isset($config['sortable'])) {
$this->sortable = $config['sortable'];
}
if (isset($config['summable'])) {
$this->summable = $config['summable'];
}
if (isset($config['clickable'])) {
$this->clickable = $config['clickable'];
}
Expand Down
166 changes: 131 additions & 35 deletions modules/backend/widgets/Lists.php
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
<?php namespace Backend\Widgets;

use Db;
use Str;
use Html;
use Lang;
use Backend;
use DbDongle;
use Carbon\Carbon;
use Winter\Storm\Html\Helper as HtmlHelper;
use Winter\Storm\Router\Helper as RouterHelper;
use System\Helpers\DateTime as DateTimeHelper;
use System\Classes\PluginManager;
use System\Classes\MediaLibrary;
use System\Classes\ImageResizer;
<?php

namespace Backend\Widgets;

use Backend\Classes\ListColumn;
use Backend\Classes\WidgetBase;
use Backend\Facades\Backend;
use Backend\Facades\BackendAuth;
use Backend\Traits\PreferenceMaker;
use Carbon\Carbon;
use Illuminate\Support\Facades\Lang;
use System\Classes\ImageResizer;
use System\Classes\MediaLibrary;
use System\Classes\PluginManager;
use System\Helpers\DateTime as DateTimeHelper;
use Winter\Storm\Database\Model;
use ApplicationException;
use BackendAuth;
use Winter\Storm\Exception\ApplicationException;
use Winter\Storm\Html\Helper as HtmlHelper;
use Winter\Storm\Router\Helper as RouterHelper;
use Winter\Storm\Support\Facades\DB;
use Winter\Storm\Support\Facades\DbDongle;
use Winter\Storm\Support\Facades\Html;
use Winter\Storm\Support\Str;

/**
* List Widget
Expand All @@ -28,7 +31,7 @@
*/
class Lists extends WidgetBase
{
use Backend\Traits\PreferenceMaker;
use PreferenceMaker;

//
// Configurable properties
Expand Down Expand Up @@ -109,6 +112,11 @@ class Lists extends WidgetBase
*/
public $showPageNumbers = true;

/**
* @var bool Display totals for number columns
*/
public $showTotals = true;

/**
* @var string Specify a custom view path to override partials used by the list.
*/
Expand Down Expand Up @@ -204,6 +212,7 @@ public function init()
'recordOnClick',
'noRecordsMessage',
'showPageNumbers',
'showTotals',
'recordsPerPage',
'perPageOptions',
'showSorting',
Expand Down Expand Up @@ -258,9 +267,9 @@ public function render()
public function prepareVars()
{
$this->vars['cssClasses'] = implode(' ', $this->cssClasses);
$this->vars['columns'] = $this->getVisibleColumns();
$this->vars['columns'] = $columns = $this->getVisibleColumns();
$this->vars['columnTotal'] = $this->getTotalColumns();
$this->vars['records'] = $this->getRecords();
$this->vars['records'] = $records = $this->getRecords();
$this->vars['noRecordsMessage'] = trans($this->noRecordsMessage);
$this->vars['showCheckboxes'] = $this->showCheckboxes;
$this->vars['showSetup'] = $this->showSetup;
Expand All @@ -273,24 +282,54 @@ public function prepareVars()
$this->vars['treeLevel'] = 0;

if ($this->showPagination) {
$this->vars['pageCurrent'] = $this->records->currentPage();
$this->vars['pageCurrent'] = $records->currentPage();
// Store the currently visited page number in the session so the same
// data can be displayed when the user returns to this list.
$this->putSession('lastVisitedPage', $this->vars['pageCurrent']);
if ($this->showPageNumbers) {
$this->vars['recordTotal'] = $this->records->total();
$this->vars['pageLast'] = $this->records->lastPage();
$this->vars['pageFrom'] = $this->records->firstItem();
$this->vars['pageTo'] = $this->records->lastItem();
}
else {
$this->vars['hasMorePages'] = $this->records->hasMorePages();
$this->vars['recordTotal'] = $records->total();
$this->vars['pageLast'] = $records->lastPage();
$this->vars['pageFrom'] = $records->firstItem();
$this->vars['pageTo'] = $records->lastItem();
} else {
$this->vars['hasMorePages'] = $records->hasMorePages();
}
}
else {
$this->vars['recordTotal'] = $this->records->count();
} else {
$this->vars['recordTotal'] = $records->count();
$this->vars['pageCurrent'] = 1;
}

// Initialize sums arrays
if ($this->showTotals) {
$sums = [];
// Initialize sums to zero for numeric columns
foreach ($columns as $column) {
if ($column->type === 'number' && $column->summable) {
$sums[$column->columnName] = 0;
}
}

if (empty($sums)) {
$this->showTotals = false;
} else {
// Calculate sums for the current page
foreach ($this->vars['records'] as $record) {
foreach ($columns as $column) {
if ($column->type === 'number' && $column->summable) {
$value = $this->getColumnValueRaw($record, $column);
if (is_numeric($value)) {
$sums[$column->columnName] += $value;
}
}
}
}

// Pass sums to view variables
$this->vars['sums'] = $sums;
$this->vars['totalSums'] = $this->calculateTotalSums($columns);
}
}
$this->vars['showTotals'] = $this->showTotals;
}

/**
Expand Down Expand Up @@ -390,8 +429,6 @@ public function prepareQuery()
*/
$primarySearchable = [];
$relationSearchable = [];

$columnsToSearch = [];
if (
strlen($this->searchTerm) !== 0
&& trim($this->searchTerm) !== ''
Expand All @@ -415,7 +452,7 @@ public function prepareQuery()
else {
$columnName = isset($column->sqlSelect)
? DbDongle::raw($this->parseTableName($column->sqlSelect, $primaryTable))
: DbDongle::cast(Db::getTablePrefix() . $primaryTable . '.' . $column->columnName, 'TEXT');
: DbDongle::cast(DB::getTablePrefix() . $primaryTable . '.' . $column->columnName, 'TEXT');

$primarySearchable[] = $columnName;
}
Expand Down Expand Up @@ -528,7 +565,7 @@ public function prepareQuery()

$joinSql = $joinQuery->toSql();

$selects[] = Db::raw("(".$joinSql.") as ".$alias);
$selects[] = DB::raw("(" . $joinSql . ") as " . $alias);

/*
* If this is a polymorphic relation there will be bindings that need to be added to the query
Expand Down Expand Up @@ -604,6 +641,65 @@ public function prepareQuery()
return $query;
}

/**
* Calculate the totals for the summable columns
*/
protected function calculateTotalSums(array $columns): array
{
$sums = [];

$query = $this->prepareQuery();

// Build an array of numeric columns to sum
$sumColumns = [];
foreach ($columns as $column) {
if ($column->type === 'number' && $column->summable) {
$columnName = $column->columnName;
$sumColumns[$columnName] = $column;
$sums[$columnName] = 0;
}
}

if (empty($sums)) {
return [];
}

// Modify the query to select the sums
$query->getQuery()->columns = [];

foreach ($sumColumns as $alias => $column) {
// Handle columns with custom select
if (isset($column->sqlSelect)) {
$sqlSelect = $column->sqlSelect;
$sumExpression = "SUM({$sqlSelect}) as {$alias}";
$query->addSelect(DB::raw($sumExpression));
} else {
$columnName = $column->columnName;
$sumExpression = "SUM({$columnName}) as {$alias}";
$query->addSelect(DB::raw($sumExpression));
}
}

// Remove any ordering to optimize performance
$query->getQuery()->orders = null;

// Get the sums
try {
$result = $query->first();
} catch (QueryException $ex) {
traceLog("Lists widget: showSums disabled due to SQL error", $ex);
$this->showTotals = false;
return [];
}

// Assign the sums to the $sums array
foreach ($sumColumns as $alias => $column) {
$sums[$alias] = $result->$alias ?? 0;
}

return $sums;
}

public function prepareModel()
{
traceLog('Method ' . __METHOD__ . '() has been deprecated, please use the ' . __CLASS__ . '::prepareQuery() method instead.');
Expand Down
8 changes: 8 additions & 0 deletions modules/backend/widgets/lists/partials/_list.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<div class="control-list list-scrollable" data-control="listwidget">
<table class="table data" data-control="rowlink">
<thead>
<?php if ($showTotals): ?>
<?= $this->makePartial('list_totals') ?>
<?php endif; ?>
<?= $this->makePartial('list_head_row') ?>
</thead>
<tbody>
Expand All @@ -14,6 +17,11 @@
</tr>
<?php endif ?>
</tbody>
<?php if ($showTotals): ?>
<tfoot>
<?= $this->makePartial('list_totals') ?>
</tfoot>
<?php endif; ?>
</table>
<?php if ($showPagination): ?>
<div class="list-footer">
Expand Down
23 changes: 23 additions & 0 deletions modules/backend/widgets/lists/partials/_list_totals.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<tr class="table-totals">
<?php if ($showCheckboxes): ?>
<td></td>
<?php endif ?>
<?php if ($showTree): ?>
<td class="list-tree">
<span></span>
</td>
<?php endif ?>
<?php foreach ($columns as $column): ?>
<td>
<?php if ($column->type == 'number' && $column->summable): ?>
<span>
<?= number_format($sums[$column->columnName], 0, '.', ',') ?>
(<?= number_format($totalSums[$column->columnName], 0, '.', ',') ?>)
</span>
<?php endif ?>
</td>
<?php endforeach ?>
<?php if ($showSetup): ?>
<td></td>
<?php endif; ?>
</tr>
25 changes: 25 additions & 0 deletions modules/system/assets/ui/less/list.less
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,31 @@ table.table.data {
}
}

.table-totals {
background-color: #f2f2f2;

span {
display: block;
color: @color-list-text-head;
}

td {
border: 0;
}
}
thead .table-totals span {
text-align: center;
padding: 6.5px 7.5px;
}
tfoot .table-totals {
td {
border-top: 2px solid @color-list-border;
}
span {
text-align: right;
}
}

font-size: @font-size-base - 1;
border-bottom: 1px solid @color-list-border;

Expand Down
2 changes: 1 addition & 1 deletion modules/system/assets/ui/storm.css

Large diffs are not rendered by default.

0 comments on commit b6ab161

Please sign in to comment.