Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/DataTableConfigBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Kreyu\Bundle\DataTableBundle\Persistence\PersistenceSubjectProviderInterface;
use Kreyu\Bundle\DataTableBundle\Personalization\PersonalizationData;
use Kreyu\Bundle\DataTableBundle\Request\RequestHandlerInterface;
use Kreyu\Bundle\DataTableBundle\RowGroup\RowGroupingConfiguration;
use Kreyu\Bundle\DataTableBundle\Sorting\SortingData;
use Kreyu\Bundle\DataTableBundle\Type\ResolvedDataTableTypeInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
Expand Down Expand Up @@ -64,6 +65,9 @@ class DataTableConfigBuilder implements DataTableConfigBuilderInterface

private bool $sortingClearable = false;

private bool $rowGroupingEnabled = false;
private ?RowGroupingConfiguration $rowGroupingConfiguration = null;

private array $themes = [];
private array $attributes = [];
private array $headerRowAttributes = [];
Expand Down Expand Up @@ -687,6 +691,38 @@ public function setRequestHandler(?RequestHandlerInterface $requestHandler): sta
return $this;
}

public function isRowGroupingEnabled(): bool
{
return $this->rowGroupingEnabled;
}

public function setRowGroupingEnabled(bool $rowGroupingEnabled): static
{
if ($this->locked) {
throw $this->createBuilderLockedException();
}

$this->rowGroupingEnabled = $rowGroupingEnabled;

return $this;
}

public function getRowGroupingConfiguration(): ?RowGroupingConfiguration
{
return $this->rowGroupingConfiguration;
}

public function setRowGroupingConfiguration(?RowGroupingConfiguration $rowGroupingConfiguration): static
{
if ($this->locked) {
throw $this->createBuilderLockedException();
}

$this->rowGroupingConfiguration = $rowGroupingConfiguration;

return $this;
}

public function getThemes(): array
{
return $this->themes;
Expand Down
5 changes: 5 additions & 0 deletions src/DataTableConfigBuilderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Kreyu\Bundle\DataTableBundle\Persistence\PersistenceSubjectProviderInterface;
use Kreyu\Bundle\DataTableBundle\Personalization\PersonalizationData;
use Kreyu\Bundle\DataTableBundle\Request\RequestHandlerInterface;
use Kreyu\Bundle\DataTableBundle\RowGroup\RowGroupingConfiguration;
use Kreyu\Bundle\DataTableBundle\Sorting\SortingData;
use Kreyu\Bundle\DataTableBundle\Type\ResolvedDataTableTypeInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
Expand Down Expand Up @@ -103,6 +104,10 @@ public function setPaginationPersistenceSubjectProvider(?PersistenceSubjectProvi

public function setDefaultPaginationData(?PaginationData $defaultPaginationData): static;

public function setRowGroupingEnabled(bool $rowGroupingEnabled): static;

public function setRowGroupingConfiguration(?RowGroupingConfiguration $rowGroupingConfiguration): static;

public function setRequestHandler(?RequestHandlerInterface $requestHandler): static;

public function addTheme(string $theme): static;
Expand Down
5 changes: 5 additions & 0 deletions src/DataTableConfigInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Kreyu\Bundle\DataTableBundle\Persistence\PersistenceSubjectProviderInterface;
use Kreyu\Bundle\DataTableBundle\Personalization\PersonalizationData;
use Kreyu\Bundle\DataTableBundle\Request\RequestHandlerInterface;
use Kreyu\Bundle\DataTableBundle\RowGroup\RowGroupingConfiguration;
use Kreyu\Bundle\DataTableBundle\Sorting\SortingData;
use Kreyu\Bundle\DataTableBundle\Type\ResolvedDataTableTypeInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
Expand Down Expand Up @@ -105,6 +106,10 @@ public function getDefaultPaginationData(): ?PaginationData;

public function getRequestHandler(): ?RequestHandlerInterface;

public function isRowGroupingEnabled(): bool;

public function getRowGroupingConfiguration(): ?RowGroupingConfiguration;

public function getAttributes(): array;

public function hasAttribute(string $name): bool;
Expand Down
6 changes: 6 additions & 0 deletions src/Resources/config/core.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Kreyu\Bundle\DataTableBundle\Persistence\TokenStoragePersistenceSubjectProvider;
use Kreyu\Bundle\DataTableBundle\Query\ArrayProxyQueryFactory;
use Kreyu\Bundle\DataTableBundle\Request\HttpFoundationRequestHandler;
use Kreyu\Bundle\DataTableBundle\RowGroup\RowGroupProcessor;
use Kreyu\Bundle\DataTableBundle\Type\DataTableType;
use Kreyu\Bundle\DataTableBundle\Type\ResolvedDataTableTypeFactory;
use Kreyu\Bundle\DataTableBundle\Type\ResolvedDataTableTypeFactoryInterface;
Expand All @@ -31,9 +32,14 @@
->alias(ResolvedDataTableTypeFactoryInterface::class, 'kreyu_data_table.resolved_type_factory')
;

$services
->set('kreyu_data_table.row_group.processor', RowGroupProcessor::class)
;

$services
->set('kreyu_data_table.type.data_table', DataTableType::class)
->arg('$defaults', abstract_arg('Default options, provided by KreyuDataTableExtension and DefaultConfigurationPass'))
->arg('$rowGroupProcessor', service('kreyu_data_table.row_group.processor'))
->tag('kreyu_data_table.type')
;

Expand Down
30 changes: 29 additions & 1 deletion src/Resources/views/themes/base.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,38 @@

{% block table_body_row_results %}
{% for value_row in value_rows %}
<tr {% with { attr: value_row.vars.attr } %}{{ block('attributes') }}{% endwith %}>{{ data_table_value_row(value_row) }}</tr>
{% if value_row.vars.row_type|default('data') == 'group_header' %}
{{ block('table_body_row_group_header', theme) }}
{% elseif value_row.vars.row_type|default('data') == 'group_footer' %}
{{ block('table_body_row_group_footer', theme) }}
{% else %}
<tr {% with { attr: value_row.vars.attr } %}{{ block('attributes') }}{% endwith %}>{{ data_table_value_row(value_row) }}</tr>
{% endif %}
{% endfor %}
{% endblock %}

{% block table_body_row_group_header %}
<tr class="data-table-row-group-header data-table-row-group-level-{{ value_row.level }}" {% with { attr: value_row.vars.attr } %}{{ block('attributes') }}{% endwith %}>
<td colspan="{{ value_row.columnCount }}">
<strong>{{ value_row.groupValue }}</strong>
{% for aggregation in value_row.aggregations %}
<span class="data-table-row-group-aggregation">{{ aggregation.label ?? aggregation.columnName }}: {{ aggregation.value }}</span>
{% endfor %}
</td>
</tr>
{% endblock %}

{% block table_body_row_group_footer %}
<tr class="data-table-row-group-footer data-table-row-group-level-{{ value_row.level }}" {% with { attr: value_row.vars.attr } %}{{ block('attributes') }}{% endwith %}>
<td colspan="{{ value_row.columnCount }}">
{{ 'Rows'|trans({}, 'KreyuDataTable') }}: {{ value_row.rowCount }}
{% for aggregation in value_row.aggregations %}
<span class="data-table-row-group-aggregation">{{ aggregation.label ?? aggregation.columnName }}: {{ aggregation.value }}</span>
{% endfor %}
</td>
</tr>
{% endblock %}

{% block table_body_row_no_results %}
<tr>
<td colspan="{{ column_count }}" {% with { attr: table_body_row_no_results_attr|default({}) } %}{{- block('attributes') -}}{% endwith %}>{{ 'No results found'|trans({}, 'KreyuDataTable') }}</td>
Expand Down
75 changes: 75 additions & 0 deletions src/RowGroup/AggregationCalculator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);

namespace Kreyu\Bundle\DataTableBundle\RowGroup;

use Symfony\Component\PropertyAccess\PropertyAccessorInterface;

class AggregationCalculator
{
public function __construct(
private readonly PropertyAccessorInterface $propertyAccessor,
) {
}

/**
* Calculates all aggregation results for a given set of rows and aggregation definitions.
*
* @param list<mixed> $rows
* @param list<AggregationDefinition> $definitions
*
* @return list<AggregationResult>
*/
public function calculate(array $rows, array $definitions): array
{
$results = [];

foreach ($definitions as $definition) {
$values = $this->extractValues($rows, $definition->getPropertyPath());
$computed = $this->compute($definition->getType(), $values);

$results[] = new AggregationResult($definition, $computed);
}

return $results;
}

/**
* @param list<mixed> $rows
*
* @return list<mixed>
*/
private function extractValues(array $rows, string $propertyPath): array
{
$values = [];

foreach ($rows as $row) {
if (is_array($row) || is_object($row)) {
$values[] = $this->propertyAccessor->getValue($row, $propertyPath);
}
}

return $values;
}

/**
* @param list<mixed> $values
*/
private function compute(AggregationType $type, array $values): int|float|null
{
if ([] === $values) {
return null;
}

$numericValues = array_map(static fn (mixed $v): float => (float) $v, $values);

return match ($type) {
AggregationType::Count => count($values),
AggregationType::Sum => array_sum($numericValues),
AggregationType::Average => array_sum($numericValues) / count($numericValues),
AggregationType::Min => min($numericValues),
AggregationType::Max => max($numericValues),
};
}
}
42 changes: 42 additions & 0 deletions src/RowGroup/AggregationDefinition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Kreyu\Bundle\DataTableBundle\RowGroup;

class AggregationDefinition
{
/**
* @param string $columnName The column name to aggregate
* @param AggregationType $type The aggregation type
* @param string|null $label Optional label for the aggregation
* @param string|null $propertyPath Property path to read the value from (defaults to column name)
*/
public function __construct(
private readonly string $columnName,
private readonly AggregationType $type,
private readonly ?string $label = null,
private readonly ?string $propertyPath = null,
) {
}

public function getColumnName(): string
{
return $this->columnName;
}

public function getType(): AggregationType
{
return $this->type;
}

public function getLabel(): ?string
{
return $this->label;
}

public function getPropertyPath(): string
{
return $this->propertyPath ?? $this->columnName;
}
}
39 changes: 39 additions & 0 deletions src/RowGroup/AggregationResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Kreyu\Bundle\DataTableBundle\RowGroup;

class AggregationResult
{
public function __construct(
private readonly AggregationDefinition $definition,
private readonly mixed $value,
) {
}

public function getDefinition(): AggregationDefinition
{
return $this->definition;
}

public function getColumnName(): string
{
return $this->definition->getColumnName();
}

public function getType(): AggregationType
{
return $this->definition->getType();
}

public function getLabel(): ?string
{
return $this->definition->getLabel();
}

public function getValue(): mixed
{
return $this->value;
}
}
14 changes: 14 additions & 0 deletions src/RowGroup/AggregationType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Kreyu\Bundle\DataTableBundle\RowGroup;

enum AggregationType: string
{
case Count = 'count';
case Sum = 'sum';
case Average = 'average';
case Min = 'min';
case Max = 'max';
}
43 changes: 43 additions & 0 deletions src/RowGroup/RowGroupData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace Kreyu\Bundle\DataTableBundle\RowGroup;

class RowGroupData
{
/**
* @param list<string> $fields
*/
public function __construct(
private array $fields = [],
private bool $enabled = true,
) {
}

/**
* @return list<string>
*/
public function getFields(): array
{
return $this->fields;
}

/**
* @param list<string> $fields
*/
public function setFields(array $fields): void
{
$this->fields = $fields;
}

public function isEnabled(): bool
{
return $this->enabled;
}

public function setEnabled(bool $enabled): void
{
$this->enabled = $enabled;
}
}
Loading
Loading