Skip to content

Commit 9138d5d

Browse files
authored
Merge pull request #640 from code16/embedded-dashboards
Embedded dashboards
2 parents e1ded62 + fd74f54 commit 9138d5d

39 files changed

Lines changed: 1447 additions & 326 deletions
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
namespace App\Sharp\Dashboard;
4+
5+
use App\Sharp\Utils\Filters\PeriodRequiredFilter;
6+
use Carbon\Carbon;
7+
use Carbon\CarbonPeriod;
8+
use Code16\Sharp\Dashboard\Layout\DashboardLayout;
9+
use Code16\Sharp\Dashboard\Layout\DashboardLayoutRow;
10+
use Code16\Sharp\Dashboard\SharpDashboard;
11+
use Code16\Sharp\Dashboard\Widgets\SharpFigureWidget;
12+
use Code16\Sharp\Dashboard\Widgets\SharpGraphWidgetDataSet;
13+
use Code16\Sharp\Dashboard\Widgets\SharpLineGraphWidget;
14+
use Code16\Sharp\Dashboard\Widgets\WidgetsContainer;
15+
use Code16\Sharp\EntityList\Filters\HiddenFilter;
16+
17+
class PostDashboard extends SharpDashboard
18+
{
19+
protected function buildWidgets(WidgetsContainer $widgetsContainer): void
20+
{
21+
$widgetsContainer
22+
->addWidget(
23+
SharpLineGraphWidget::make('visits_line')
24+
->setTitle('Visits')
25+
->setHeight(200)
26+
->setShowLegend(false)
27+
->setDisplayHorizontalAxisAsTimeline()
28+
)
29+
->addWidget(
30+
SharpFigureWidget::make('visit_count')
31+
->setTitle('Total visits'),
32+
)
33+
->addWidget(
34+
SharpFigureWidget::make('page_count')
35+
->setTitle('Total pageviews'),
36+
);
37+
}
38+
39+
protected function buildDashboardLayout(DashboardLayout $dashboardLayout): void
40+
{
41+
$dashboardLayout
42+
->addFullWidthWidget('visits_line')
43+
->addRow(fn (DashboardLayoutRow $row) => $row
44+
->addWidget(6, 'visit_count')
45+
->addWidget(6, 'page_count')
46+
);
47+
}
48+
49+
public function getFilters(): ?array
50+
{
51+
return [
52+
PeriodRequiredFilter::class,
53+
HiddenFilter::make('post'),
54+
];
55+
}
56+
57+
protected function buildWidgetsData(): void
58+
{
59+
$visitCount = $this->getStartDate()->diffInDays($this->getEndDate()) * rand(10, 100);
60+
61+
$this
62+
->setFigureData('visit_count', figure: $visitCount)
63+
->setFigureData('page_count', figure: $visitCount * rand(2, 10))
64+
->addGraphDataSet(
65+
'visits_line',
66+
SharpGraphWidgetDataSet::make(collect(CarbonPeriod::create($this->getStartDate(), $this->getEndDate()))
67+
->mapWithKeys(fn (Carbon $day, $k) => [
68+
$day->isoFormat('L') => rand(10, 100),
69+
]))
70+
->setLabel('Visits')
71+
->setColor('#274754'),
72+
);
73+
}
74+
75+
protected function getStartDate(): Carbon
76+
{
77+
return $this->queryParams->filterFor(PeriodRequiredFilter::class)->getStart();
78+
}
79+
80+
protected function getEndDate(): Carbon
81+
{
82+
return min(
83+
$this->queryParams->filterFor(PeriodRequiredFilter::class)->getEnd(),
84+
today()->subDay(),
85+
);
86+
}
87+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace App\Sharp\Entities;
4+
5+
use App\Sharp\Dashboard\PostDashboard;
6+
use Code16\Sharp\Auth\SharpEntityPolicy;
7+
use Code16\Sharp\Utils\Entities\SharpDashboardEntity;
8+
9+
class PostDashboardEntity extends SharpDashboardEntity
10+
{
11+
protected ?string $view = PostDashboard::class;
12+
13+
protected function getPolicy(): ?SharpEntityPolicy
14+
{
15+
return new class() extends SharpEntityPolicy
16+
{
17+
public function entity($user): bool
18+
{
19+
return $user->isAdmin();
20+
}
21+
};
22+
}
23+
}

demo/app/Sharp/Posts/PostShow.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use App\Models\Post;
66
use App\Sharp\Entities\AuthorEntity;
77
use App\Sharp\Entities\PostBlockEntity;
8+
use App\Sharp\Entities\PostDashboardEntity;
89
use App\Sharp\Entities\PostEntity;
910
use App\Sharp\Posts\Commands\EvaluateDraftPostWizardCommand;
1011
use App\Sharp\Posts\Commands\PreviewPostCommand;
@@ -14,6 +15,7 @@
1415
use App\Sharp\Utils\Embeds\TableOfContentsEmbed;
1516
use App\Sharp\Utils\Filters\CategoryFilter;
1617
use Code16\Sharp\Form\Eloquent\Uploads\Transformers\SharpUploadModelFormAttributeTransformer;
18+
use Code16\Sharp\Show\Fields\SharpShowDashboardField;
1719
use Code16\Sharp\Show\Fields\SharpShowEntityListField;
1820
use Code16\Sharp\Show\Fields\SharpShowFileField;
1921
use Code16\Sharp\Show\Fields\SharpShowListField;
@@ -64,6 +66,11 @@ protected function buildShowFields(FieldsContainer $showFields): void
6466
->setLabel('File')
6567
)
6668
)
69+
->addField(
70+
SharpShowDashboardField::make(PostDashboardEntity::class)
71+
->setLabel('Analytics')
72+
->hideFilterWithValue('post', fn ($instanceId) => $instanceId)
73+
)
6774
->addField(
6875
SharpShowEntityListField::make(PostBlockEntity::class)
6976
->setLabel('Blocks')
@@ -94,6 +101,7 @@ protected function buildShowLayout(ShowLayout $showLayout): void
94101
$column->withField('content');
95102
});
96103
})
104+
->addDashboardSection(PostDashboardEntity::class)
97105
->addEntityListSection(PostBlockEntity::class);
98106
}
99107

docs/.vitepress/sidebar.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ export function sidebar(): DefaultTheme.SidebarItem[] {
5757
{ text: 'Picture', link: '/guide/show-fields/picture.md' },
5858
{ text: 'List', link: '/guide/show-fields/list.md' },
5959
{ text: 'File', link: '/guide/show-fields/file.md' },
60-
{ text: 'Embedded EntityList', link: '/guide/show-fields/embedded-entity-list.md' },
60+
{ text: 'Entity List', link: '/guide/show-fields/entity-list.md' },
61+
{ text: 'Dashboard', link: '/guide/show-fields/dashboard.md' },
6162
// { text: 'Custom show field', link: '/guide/custom-show-fields.md' }
6263
]
6364
},

docs/guide/building-show-page.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class MyShow extends SharpShow
5656

5757
Each available Show field is detailed below; here are the attributes they all share :
5858

59-
- `setShowIfEmpty(bool $show = true): self`: by default, an empty field (meaning: with null or empty data) is not displayed at all in the Show UI. You can change this behaviour with this attribute. This method has no impact for [embedded EntityList](show-fields/embedded-entity-list.md).
59+
- `setShowIfEmpty(bool $show = true): self`: by default, an empty field (meaning: with null or empty data) is not displayed at all in the Show UI. You can change this behaviour with this attribute. This method has no impact for the [Entity List field](show-fields/entity-list.md).
6060

6161
#### Available simple Show fields
6262

@@ -95,9 +95,9 @@ Notice that you have three possibilities for the actual code of this Entity List
9595

9696
As always with Sharp, implementation is up to you.
9797

98-
The next thing to do is to scope the data of the embedded Entity List. In our case, we want to display and interact only with the products for this order... For this and more on personalization, refer to the detailed documentation of this field:
98+
The next thing to do is to scope the data of the Entity List field. In our case, we want to display and interact only with the products for this order... For this and more on personalization, refer to the detailed documentation of this field:
9999

100-
- [embedded EntityList](show-fields/embedded-entity-list.md)
100+
- [Entity List field](show-fields/entity-list.md)
101101

102102
### `buildShowLayout(ShowLayout $showLayout): void`
103103

docs/guide/entity-class.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,5 +202,5 @@ You must remove all `->declareEntity()` calls in order to use `->declareEntityRe
202202
:::
203203

204204
::: warning
205-
If you are using a custom entity resolver, you won’t be able to use the `SharpEntity` classes in the [menu](building-menu.md), or in [`LinkTo` links](link-to.md), or for [embedded entity lists](show-fields/embedded-entity-list.md): you will have to use the entity key instead. For instance: `LinkToForm::make('products', $id)`.
205+
If you are using a custom entity resolver, you won’t be able to use the `SharpEntity` classes in the [menu](building-menu.md), or in [`LinkTo` links](link-to.md), or for [Entity List fields](show-fields/entity-list.md): you will have to use the entity key instead. For instance: `LinkToForm::make('products', $id)`.
206206
:::
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Dashboard
2+
3+
Class: `Code16\Sharp\Show\Fields\SharpShowDashboardField`.
4+
5+
The field allows you to integrate a [Dashboard](../building-dashboard.md) into your Show Page.
6+
7+
## Constructor
8+
9+
This field needs, as first parameter, either the entity key or the `SharpDashboardEntity` class that declares the dashboard which will be included in the Show Page.
10+
11+
For instance:
12+
13+
```php
14+
SharpShowDashboardField::make('posts_dashboard')
15+
```
16+
17+
or
18+
19+
```php
20+
SharpShowDashboardField::make(PostDashboardEntity::class)
21+
```
22+
::: warning
23+
This last syntax is better in terms of DX (since it allows using the IDE to navigate to the Entity List implementation), but it won’t work in two specific cases: if you use a custom `SharpEntityResolver` or if you your Entity is declared with multiple keys.
24+
:::
25+
26+
## Configuration
27+
28+
### `hideFilterWithValue(string $filterName, $value)`
29+
This is the most important method of the field, since it will not only hide a filter but also set its value. The purpose is to allow to **scope the data to the instance** of the Show Page. For example, let’s say we display a Post and that we want to embed a dashboard with the post's statistics:
30+
31+
32+
```php
33+
class PostShow extends SharpShow
34+
{
35+
// ...
36+
37+
public function buildShowFields(FieldsContainer $showFields): void
38+
{
39+
$showFields->addField(
40+
SharpShowDashboardField::make(PostDashboardEntity::class)
41+
->hideFilterWithValue(PostFilter::class, 64)
42+
);
43+
}
44+
}
45+
```
46+
47+
Here we're scoping the `PostDashboard` declared in the `PostDashboardEntity` to the instance of the `Post` with id 64.
48+
49+
50+
You can pass a closure as the value, and it will contain the current Show instance id. In most cases, you'll have to write this:
51+
52+
```php
53+
SharpShowDashboardField::make(PostDashboardEntity::class)
54+
->hideFilterWithValue(PostFilter::class, fn ($instanceId) => $instanceId);
55+
```
56+
57+
**One final note**: sometimes the linked filter is really just a scope, never displayed to the user. In this case, it can be tedious to write a full implementation in the Dashboard. In this situation, you can use the `HiddenFiler` class for the filter, passing a key:
58+
59+
```php
60+
class PostShow extends SharpShow
61+
{
62+
// ...
63+
64+
public function buildShowFields(FieldsContainer $showFields): void
65+
{
66+
$showFields->addField(
67+
SharpShowDashboardField::make(PostDashboardEntity::class)
68+
->hideFilterWithValue('post', fn ($instanceId) => $instanceId);
69+
);
70+
}
71+
}
72+
```
73+
74+
```php
75+
use \Code16\Sharp\EntityList\Filters\HiddenFilter;
76+
77+
class PostDashboard extends SharpDashboard
78+
{
79+
// ...
80+
81+
protected function getFilters(): ?array
82+
{
83+
return [
84+
HiddenFilter::make('post')
85+
];
86+
}
87+
88+
protected function buildWidgetsData(): void
89+
{
90+
return $this->setFigureData('visit_count',
91+
figure: Post::query()
92+
->findOrFail($this->queryParams->filterFor('post'))
93+
->get()?->visit_count
94+
);
95+
}
96+
}
97+
```
98+
99+
### `hideDashboardCommand(array|string $commands): self`
100+
101+
Use it to hide any dashboard command in this particular Dashboard (useful when reusing a Dashboard). This will apply before looking at authorizations.
102+
103+
## Display in layout
104+
105+
To display your dashboard in your show page's layout, you have to use the `addDashboardSection()` method in your Show Page's `buildShowLayout()` method.
106+
107+
```php
108+
protected function buildShowLayout(ShowLayout $showLayout): void
109+
{
110+
$showLayout
111+
->addSection(function (ShowLayoutSection $section) {
112+
$section
113+
->addColumn(7, function (ShowLayoutColumn $column) {
114+
$column
115+
->withFields(categories: 5, author: 7)
116+
// ...
117+
})
118+
->addColumn(5, function (ShowLayoutColumn $column) {
119+
// ...
120+
});
121+
})
122+
->addDashboardSection(PostDashboardEntity::class);
123+
}
124+
```

0 commit comments

Comments
 (0)