Skip to content

Commit 3d305ba

Browse files
committed
- Created categorical stats
1 parent c783d5e commit 3d305ba

63 files changed

Lines changed: 4552 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎config/stats.php‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,25 @@
5555

5656
'default_time_series_mode' => \Javaabu\Stats\Enums\TimeSeriesModes::DAY,
5757

58+
/*
59+
|--------------------------------------------------------------------------
60+
| Default categorical mode
61+
|--------------------------------------------------------------------------
62+
*/
63+
64+
'default_categorical_mode' => \Javaabu\Stats\Enums\CategoricalModes::NON_EMPTY,
65+
66+
/*
67+
|--------------------------------------------------------------------------
68+
| Categorical items per page
69+
|--------------------------------------------------------------------------
70+
|
71+
| Default page size for the categorical provider search endpoint.
72+
|
73+
*/
74+
75+
'categorical_items_per_page' => 15,
76+
5877
/*
5978
|--------------------------------------------------------------------------
6079
| Default date range
@@ -112,4 +131,12 @@
112131
*/
113132

114133
'time_series_stats_view' => 'stats::material-admin-26.time-series-stats.index',
134+
135+
/*
136+
|--------------------------------------------------------------------------
137+
| View for categorical stats
138+
|--------------------------------------------------------------------------
139+
*/
140+
141+
'categorical_stats_view' => 'stats::material-admin-26.categorical-stats.index',
115142
];

‎docs/categorical/_category_.json‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"label": "Categorical Stats",
3+
"position": 3
4+
}
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
---
2+
title: Category providers
3+
sidebar_position: 3
4+
---
5+
6+
A category provider maps the value stored in `getCategoryField()` to a label displayed in graphs, exports, and the category selector.
7+
8+
Each item has an `id` and `label`:
9+
10+
```php
11+
[
12+
['id' => 1, 'label' => 'Retail'],
13+
['id' => 2, 'label' => 'Wholesale'],
14+
]
15+
```
16+
17+
Choose a provider based on where your categories are stored.
18+
19+
## Export column titles
20+
21+
Providers supply the default titles for the category ID and category name columns in CSV exports. A custom provider can override:
22+
23+
```php
24+
public function getCategoricalStatsCategoryTitle(): string
25+
{
26+
return __('Customer');
27+
}
28+
29+
public function getCategoricalStatsCategoryIdTitle(): string
30+
{
31+
return __('Customer Number');
32+
}
33+
```
34+
35+
Eloquent providers derive the category title from the model's morph class when available, otherwise from the model class name. Enum providers use the enum class name. Array, collection, and other in-memory providers default to `Category` and `Category ID`.
36+
37+
The stat repository may override these titles for an individual metric with `getCategoryTitle()` and `getCategoryIdTitle()`.
38+
39+
## Eloquent model providers
40+
41+
Return a model class when the category ID is the model's primary key:
42+
43+
```php
44+
use App\Models\Customer;
45+
46+
public function categoryProvider(): mixed
47+
{
48+
return Customer::class;
49+
}
50+
```
51+
52+
By default, the provider:
53+
54+
- Uses the model's primary key as `id`.
55+
- Uses `admin_link_name` as the label when that accessor exists.
56+
- Otherwise uses the `name` attribute as the label.
57+
- Orders paginated searches by the primary key when no order is configured.
58+
- Derives export column titles from the model's morph class or class name.
59+
60+
### Customize labels and search
61+
62+
Add these methods to the category model when its fields differ from the defaults:
63+
64+
```php
65+
public function getCategoricalStatsLabelField(): string
66+
{
67+
return 'display_name';
68+
}
69+
70+
public function getCategoricalStatsSearchField(): string
71+
{
72+
return 'name';
73+
}
74+
```
75+
76+
For multi-column or relationship searches, define a `categoricalStatsSearch` scope:
77+
78+
```php
79+
use Illuminate\Database\Eloquent\Builder;
80+
81+
public function scopeCategoricalStatsSearch(Builder $query, string $search): Builder
82+
{
83+
return $query->where(function (Builder $query) use ($search) {
84+
$query->where('first_name', 'like', "%{$search}%")
85+
->orWhere('last_name', 'like', "%{$search}%")
86+
->orWhere('email', 'like', "%{$search}%");
87+
});
88+
}
89+
```
90+
91+
If `categoricalStatsSearch` is not defined, the provider uses an existing `search` scope. If neither scope exists, it applies a `LIKE` condition to `getCategoricalStatsSearchField()`.
92+
93+
### Restrict the available models
94+
95+
Return an Eloquent query to limit the categories users can select:
96+
97+
```php
98+
public function categoryProvider(): mixed
99+
{
100+
return Customer::query()
101+
->where('is_active', true)
102+
->where('company_id', auth()->user()->company_id);
103+
}
104+
```
105+
106+
The same query is used when resolving labels and serving paginated search results.
107+
108+
### Configure ordering
109+
110+
For straightforward ordering, create an `EloquentCategoryProvider` and select a field and direction:
111+
112+
```php
113+
use Javaabu\Stats\CategoryProviders\EloquentCategoryProvider;
114+
115+
public function categoryProvider(): mixed
116+
{
117+
return (new EloquentCategoryProvider(Customer::class))
118+
->sortCategoricalStatsItemsBy('name', 'asc');
119+
}
120+
```
121+
122+
For multiple order clauses, return an ordered query:
123+
124+
```php
125+
public function categoryProvider(): mixed
126+
{
127+
return Customer::query()
128+
->orderByDesc('priority')
129+
->orderBy('name');
130+
}
131+
```
132+
133+
You can also configure the query through a callback:
134+
135+
```php
136+
return (new EloquentCategoryProvider(Customer::class))
137+
->sortCategoricalStatsItemsUsing(
138+
fn ($query) => $query->orderByRaw('priority is null')->orderBy('priority')
139+
);
140+
```
141+
142+
### Use the provider trait on a model
143+
144+
A model can implement `CategoryProvider` directly with `IsCategoricalStatsProvider`:
145+
146+
```php
147+
use Javaabu\Stats\Concerns\IsCategoricalStatsProvider;
148+
use Javaabu\Stats\Contracts\CategoryProvider;
149+
150+
class Customer extends Model implements CategoryProvider
151+
{
152+
use IsCategoricalStatsProvider;
153+
}
154+
```
155+
156+
Return a configured model instance from the stat when you want to use the trait's sorting methods:
157+
158+
```php
159+
return (new Customer)
160+
->sortCategoricalStatsItemsBy('name');
161+
```
162+
163+
## Enum providers
164+
165+
Return a native enum class:
166+
167+
```php
168+
public function categoryProvider(): mixed
169+
{
170+
return PaymentStatus::class;
171+
}
172+
```
173+
174+
Backed enum values become IDs. The label is resolved from `getLabel()`, then `label()`, and finally the title-cased case name.
175+
176+
To configure ordering, wrap the enum:
177+
178+
```php
179+
use Javaabu\Stats\CategoryProviders\EnumCategoryProvider;
180+
181+
return (new EnumCategoryProvider(PaymentStatus::class))
182+
->sortCategoricalStatsItemsBy('label', 'asc');
183+
```
184+
185+
## Array and collection providers
186+
187+
Return an associative array when categories are already available in memory:
188+
189+
```php
190+
public function categoryProvider(): mixed
191+
{
192+
return [
193+
'card' => 'Card',
194+
'cash' => 'Cash',
195+
];
196+
}
197+
```
198+
199+
Arrays and collections can also contain explicit items:
200+
201+
```php
202+
return collect([
203+
['id' => 'card', 'label' => 'Card'],
204+
['id' => 'cash', 'label' => 'Cash'],
205+
]);
206+
```
207+
208+
Use `ArrayCategoryProvider` for field or callback sorting:
209+
210+
```php
211+
use Javaabu\Stats\CategoryProviders\ArrayCategoryProvider;
212+
213+
return (new ArrayCategoryProvider($categories))
214+
->sortCategoricalStatsItemsBy('label', 'desc');
215+
```
216+
217+
A custom comparator receives two normalized items:
218+
219+
```php
220+
return (new ArrayCategoryProvider($categories))
221+
->sortCategoricalStatsItemsUsing(
222+
fn (array $left, array $right) =>
223+
strlen($left['label']) <=> strlen($right['label'])
224+
);
225+
```
226+
227+
Array and collection providers search and paginate after loading their items into memory. Prefer an Eloquent or custom data-source provider for large category sets.
228+
229+
## Custom providers
230+
231+
Implement `CategoryProvider` when categories come from another data source. For an in-memory source, `HasCategoricalStatsItems` provides normalization, searching, sorting, and pagination:
232+
233+
```php
234+
use Javaabu\Stats\Concerns\HasCategoricalStatsItems;
235+
use Javaabu\Stats\Contracts\CategoryProvider;
236+
237+
class ChannelProvider implements CategoryProvider
238+
{
239+
use HasCategoricalStatsItems;
240+
241+
protected function categoricalStatsItems(): iterable
242+
{
243+
return config('channels');
244+
}
245+
}
246+
```
247+
248+
Implement the contract methods yourself when the underlying service should perform searching or pagination.
249+
250+
## Use the category search endpoint
251+
252+
The Select2 component calls:
253+
254+
```text
255+
GET /stats/categorical/categories
256+
?filter[metric]=payments_by_customer
257+
&filter[search]=acme
258+
&page=2
259+
```
260+
261+
Optional parameters are:
262+
263+
- `per_page`, from 1 to 100.
264+
- `filters`, for model-specific stats.
265+
266+
The response is a standard Laravel paginator whose `data` entries contain `id` and `label`. The requested metric must be registered, visible to the authenticated user, and compatible with the supplied filters.
267+
268+
269+

0 commit comments

Comments
 (0)