From 662fedcc128ff2b107f095c894a9b4d8a0485fa5 Mon Sep 17 00:00:00 2001 From: philippe Date: Wed, 8 Oct 2025 16:11:14 +0200 Subject: [PATCH 01/19] Basic backend code --- src/Show/Fields/SharpShowDashboardField.php | 103 ++++++++++++++++++++ src/Show/Layout/ShowLayout.php | 9 ++ src/routes/api.php | 8 +- 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 src/Show/Fields/SharpShowDashboardField.php diff --git a/src/Show/Fields/SharpShowDashboardField.php b/src/Show/Fields/SharpShowDashboardField.php new file mode 100644 index 000000000..c2faf1dff --- /dev/null +++ b/src/Show/Fields/SharpShowDashboardField.php @@ -0,0 +1,103 @@ +entityKeyFor($key); + } + + return tap( + new static($key, static::FIELD_TYPE), + fn ($instance) => $instance->dashboardKey = $dashboardKey ?: $key + ); + } + + public function hideFilterWithValue(string $filterFullClassNameOrKey, $value): self + { + if (class_exists($filterFullClassNameOrKey)) { + $key = tap( + app($filterFullClassNameOrKey), function (Filter $filter) { + $filter->buildFilterConfig(); + }) + ->getKey(); + } else { + $key = $filterFullClassNameOrKey; + } + + $this->hiddenFilters[$key] = $value; + + return $this; + } + + public function hideDashboardCommand(array|string $commands): self + { + foreach ((array) $commands as $command) { + if (class_exists($command)) { + $command = app($command)->getCommandKey(); + } + + $this->hiddenCommands[] = $command; + } + + return $this; + } + + public function setLabel(string $label): self + { + $this->label = $label; + + return $this; + } + + /** + * Create the property array for the field, using parent::buildArray(). + */ + public function toArray(): array + { + return tap( + parent::buildArray([ + 'label' => $this->label, + 'dashboardKey' => $this->dashboardKey, + 'hiddenCommands' => $this->hiddenCommands, + 'hiddenFilters' => count($this->hiddenFilters) + ? collect($this->hiddenFilters) + ->map(fn ($value) => is_callable($value) ? $value() : $value) + ->all() + : null, + ]), + function (array &$options) { + $options['endpointUrl'] = route('code16.sharp.api.dashboard', [ + 'entityKey' => $this->dashboardKey, + 'current_page_url' => request()->url(), + ...app(SharpEntityManager::class) + ->entityFor($this->dashboardKey) + ->getViewOrFail() + ->filterContainer() + ->getQueryParamsFromFilterValues($options['hiddenFilters'] ?? []), + ]); + }); + } + + protected function validationRules(): array + { + return [ + 'dashboardKey' => 'required', + 'hiddenCommands' => 'array', + 'hiddenFilters' => 'array', + ]; + } +} diff --git a/src/Show/Layout/ShowLayout.php b/src/Show/Layout/ShowLayout.php index bd244407a..8d0be6734 100644 --- a/src/Show/Layout/ShowLayout.php +++ b/src/Show/Layout/ShowLayout.php @@ -51,4 +51,13 @@ public function toArray(): array ->all(), ]; } + + final public function addDashboardSection(string $dashboardKey, ?bool $collapsable = null): self + { + $this->sections[] = (new ShowLayoutSection('')) + ->addColumn(12, fn ($column) => $column->withField($dashboardKey)) + ->when($collapsable !== null, fn ($section) => $section->setCollapsable($collapsable)); + + return $this; + } } diff --git a/src/routes/api.php b/src/routes/api.php index 16ddff1c4..5a388ac15 100644 --- a/src/routes/api.php +++ b/src/routes/api.php @@ -17,6 +17,7 @@ use Code16\Sharp\Http\Controllers\Api\Commands\ApiShowEntityStateController; use Code16\Sharp\Http\Controllers\Api\Commands\ApiShowInstanceCommandController; use Code16\Sharp\Http\Controllers\Api\Embeds\ApiEmbedsFormController; +use Code16\Sharp\Http\Controllers\DashboardController; use Code16\Sharp\Http\Controllers\EntityListController; use Illuminate\Support\Facades\Route; @@ -36,7 +37,7 @@ Route::post('/list/{entityKey}/form/{formEntityKey}/create', [ApiEntityListQuickCreationCommandController::class, 'store']) ->name('code16.sharp.api.list.command.quick-creation-form.store'); - // EEL + // EmbeddedEntityLists Route::get('/list/{entityKey}', [EntityListController::class, 'show']) ->name('code16.sharp.api.list') ->middleware('cache.headers:no_store'); @@ -65,6 +66,11 @@ Route::get('/list/{entityKey}/command/{commandKey}/{instanceId}/form', [ApiEntityListInstanceCommandController::class, 'show']) ->name('code16.sharp.api.list.command.instance.form'); + // EmbeddedDashboards + Route::get('/dashboard/{entityKey}', [DashboardController::class, 'show']) + ->name('code16.sharp.api.dashboard') + ->middleware('cache.headers:no_store'); + Route::post('/show/{entityKey}/command/{commandKey}/{instanceId?}', [ApiShowInstanceCommandController::class, 'update']) ->name('code16.sharp.api.show.command.instance'); From feaaeb84705a00f7ad1be0bbae0ee73d842969f7 Mon Sep 17 00:00:00 2001 From: philippe Date: Wed, 8 Oct 2025 16:11:25 +0200 Subject: [PATCH 02/19] Demo usecase --- demo/app/Sharp/Dashboard/PostDashboard.php | 94 +++++++++++++++++++ .../Sharp/Entities/PostDashboardEntity.php | 11 +++ demo/app/Sharp/Posts/PostShow.php | 7 ++ 3 files changed, 112 insertions(+) create mode 100644 demo/app/Sharp/Dashboard/PostDashboard.php create mode 100644 demo/app/Sharp/Entities/PostDashboardEntity.php diff --git a/demo/app/Sharp/Dashboard/PostDashboard.php b/demo/app/Sharp/Dashboard/PostDashboard.php new file mode 100644 index 000000000..79bc95348 --- /dev/null +++ b/demo/app/Sharp/Dashboard/PostDashboard.php @@ -0,0 +1,94 @@ +addWidget( + SharpLineGraphWidget::make('visits_line') + ->setTitle('Visits') + ->setHeight(200) + ->setShowLegend() + ->setCurvedLines(), + ) + ->addWidget( + SharpFigureWidget::make('visits_count') + ->setTitle('Total visits'), + ); + } + + protected function buildDashboardLayout(DashboardLayout $dashboardLayout): void + { + $dashboardLayout + ->addRow(function (DashboardLayoutRow $row) { + $row->addWidget(.75, 'visits_line') + ->addWidget(.25, 'visits_count'); + }); + } + + public function getFilters(): ?array + { + return [ + 'stats-section' => [ + PeriodRequiredFilter::class, + ], + ]; + } + + public function getDashboardCommands(): ?array + { + return [ + 'stats-section' => [ + ExportStatsAsCsvCommand::class, + ], + ]; + } + + protected function buildWidgetsData(): void + { + $visitTotalCount = rand(10, 25000); + + $this + ->setFigureData( + 'visits_count', + figure: $visitTotalCount, + ) + ->addGraphDataSet( + 'visits_line', + SharpGraphWidgetDataSet::make(collect(CarbonPeriod::create($this->getStartDate(), $this->getEndDate())) + ->mapWithKeys(fn (Carbon $day, $k) => [ + $day->isoFormat('L') => rand(10, 100), + ])) + ->setLabel('Visits') + ->setColor('#274754'), + ); + } + + protected function getStartDate(): Carbon + { + return $this->queryParams->filterFor(PeriodRequiredFilter::class)->getStart(); + } + + protected function getEndDate(): Carbon + { + return min( + $this->queryParams->filterFor(PeriodRequiredFilter::class)->getEnd(), + today()->subDay(), + ); + } +} diff --git a/demo/app/Sharp/Entities/PostDashboardEntity.php b/demo/app/Sharp/Entities/PostDashboardEntity.php new file mode 100644 index 000000000..26a57b57b --- /dev/null +++ b/demo/app/Sharp/Entities/PostDashboardEntity.php @@ -0,0 +1,11 @@ +setLabel('File') ) ) + ->addField( + SharpShowDashboardField::make(PostDashboardEntity::class) + ->setLabel('Stats and figures') + ) ->addField( SharpShowEntityListField::make(PostBlockEntity::class) ->setLabel('Blocks') @@ -94,6 +100,7 @@ protected function buildShowLayout(ShowLayout $showLayout): void $column->withField('content'); }); }) + ->addDashboardSection(PostDashboardEntity::class) ->addEntityListSection(PostBlockEntity::class); } From 7549f7689a8888a7a380326a594c3d0a5ce5db42 Mon Sep 17 00:00:00 2001 From: philippe Date: Wed, 8 Oct 2025 16:34:58 +0200 Subject: [PATCH 03/19] Add test --- src/Http/Controllers/DashboardController.php | 5 ++ tests/Http/Api/ApiDashboardControllerTest.php | 70 +++++++++++++++++++ tests/Http/DashboardControllerTest.php | 14 ++-- tests/Pest.php | 5 ++ 4 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 tests/Http/Api/ApiDashboardControllerTest.php diff --git a/src/Http/Controllers/DashboardController.php b/src/Http/Controllers/DashboardController.php index 51c88b6ee..d6d99db00 100644 --- a/src/Http/Controllers/DashboardController.php +++ b/src/Http/Controllers/DashboardController.php @@ -26,6 +26,11 @@ public function show(string $dashboardKey) 'filterValues' => $dashboard->filterContainer()->getCurrentFilterValuesForFront(request()->all()), ]; + if (request()->routeIs('code16.sharp.api.dashboard')) { + // EmbeddedDashboard case, need to return JSON + return response()->json(DashboardData::from($data)->toArray()); + } + return Inertia::render('Dashboard/Dashboard', [ 'dashboard' => DashboardData::from($data), 'breadcrumb' => BreadcrumbData::from([ diff --git a/tests/Http/Api/ApiDashboardControllerTest.php b/tests/Http/Api/ApiDashboardControllerTest.php new file mode 100644 index 000000000..788c0738d --- /dev/null +++ b/tests/Http/Api/ApiDashboardControllerTest.php @@ -0,0 +1,70 @@ +config()->declareEntity(DashboardEntity::class); + login(); +}); + +it('gets dashboard data as JSON in an EmbeddedDashboard case', function () { + fakeDashboardFor(DashboardEntity::class, new class() extends SharpDashboard + { + protected function buildWidgets(WidgetsContainer $widgetsContainer): void + { + $widgetsContainer + ->addWidget( + SharpPanelWidget::make('panel') + ->setTemplate('') + ) + ->addWidget( + SharpFigureWidget::make('figure') + ); + } + + protected function buildDashboardLayout(DashboardLayout $dashboardLayout): void + { + $dashboardLayout + ->addSection('section', function (DashboardLayoutSection $section) { + $section + ->addRow(function (DashboardLayoutRow $row) { + $row + ->addWidget(.3, 'panel') + ->addWidget(.7, 'figure'); + }); + }); + } + + protected function buildWidgetsData(): void + { + $this + ->setPanelData('panel', ['name' => 'Albert Einstein']) + ->setFigureData('figure', 200, '€', '+3%'); + } + }); + + $this->getJson('/sharp/api/dashboard/'.DashboardEntity::$entityKey, headers: [ + SharpBreadcrumb::CURRENT_PAGE_URL_HEADER => url('/sharp/s-list/person/s-show/person/1'), + ]) + ->assertOk() + ->assertJson(fn (AssertableJson $json) => $json + ->has('data.panel.data', fn (AssertableJson $json) => $json + ->where('name', 'Albert Einstein') + ->etc() + ) + ->has('data.figure.data', fn (AssertableJson $json) => $json + ->where('figure', '200') + ->etc() + ) + ->etc() + ); +}); diff --git a/tests/Http/DashboardControllerTest.php b/tests/Http/DashboardControllerTest.php index 17fe3f775..10311bacd 100644 --- a/tests/Http/DashboardControllerTest.php +++ b/tests/Http/DashboardControllerTest.php @@ -14,12 +14,12 @@ use Inertia\Testing\AssertableInertia as Assert; beforeEach(function () { - sharp()->config()->addEntity('stats', DashboardEntity::class); + sharp()->config()->declareEntity(DashboardEntity::class); login(); }); it('gets dashboard widgets, layout and data', function () { - fakeShowFor('stats', new class() extends SharpDashboard + fakeDashboardFor(DashboardEntity::class, new class() extends SharpDashboard { protected function buildWidgets(WidgetsContainer $widgetsContainer): void { @@ -56,7 +56,7 @@ protected function buildWidgetsData(): void $this->withoutExceptionHandling(); - $this->get('/sharp/s-dashboard/stats') + $this->get('/sharp/s-dashboard/'.DashboardEntity::$entityKey) ->assertOk() ->assertInertia(fn (Assert $page) => $page ->has('dashboard', fn (Assert $dashboard) => $dashboard @@ -79,7 +79,7 @@ protected function buildWidgetsData(): void it('allows to configure a page alert', function () { $this->withoutExceptionHandling(); - fakeShowFor('stats', new class() extends TestDashboard + fakeDashboardFor(DashboardEntity::class, new class() extends TestDashboard { public function buildPageAlert(PageAlert $pageAlert): void { @@ -90,7 +90,7 @@ public function buildPageAlert(PageAlert $pageAlert): void } }); - $this->get('/sharp/s-dashboard/stats') + $this->get('/sharp/s-dashboard/'.DashboardEntity::$entityKey) ->assertOk() ->assertInertia(fn (Assert $page) => $page ->where('dashboard.pageAlert', [ @@ -104,7 +104,7 @@ public function buildPageAlert(PageAlert $pageAlert): void }); it('allows to configure a page alert with a closure as content', function () { - fakeShowFor('stats', new class() extends TestDashboard + fakeDashboardFor(DashboardEntity::class, new class() extends TestDashboard { public function buildPageAlert(PageAlert $pageAlert): void { @@ -127,7 +127,7 @@ protected function buildWidgetsData(): void } }); - $this->get('/sharp/s-dashboard/stats') + $this->get('/sharp/s-dashboard/'.DashboardEntity::$entityKey) ->assertOk() ->assertInertia(fn (Assert $page) => $page ->where('dashboard.pageAlert', [ diff --git a/tests/Pest.php b/tests/Pest.php index e5341e7a8..00e9ba055 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -115,6 +115,11 @@ class_exists($entityKeyOrClass) return test(); } +function fakeDashboardFor(string $entityKeyOrClass, $fakeImplementation) +{ + return fakeShowFor($entityKeyOrClass, $fakeImplementation); +} + function fakePolicyFor(string $entityKeyOrClass, $fakeImplementation) { app(SharpEntityManager::class) From 356311a991a8e866791419e378fe5b0e92fce79a Mon Sep 17 00:00:00 2001 From: philippe Date: Wed, 8 Oct 2025 16:54:58 +0200 Subject: [PATCH 04/19] add ApiDashboardFiltersController --- .../Api/ApiDashboardFiltersController.php | 30 +++++++++++++++++++ src/routes/api.php | 4 +++ 2 files changed, 34 insertions(+) create mode 100644 src/Http/Controllers/Api/ApiDashboardFiltersController.php diff --git a/src/Http/Controllers/Api/ApiDashboardFiltersController.php b/src/Http/Controllers/Api/ApiDashboardFiltersController.php new file mode 100644 index 000000000..4886fcaab --- /dev/null +++ b/src/Http/Controllers/Api/ApiDashboardFiltersController.php @@ -0,0 +1,30 @@ +authorizationManager->check('entity', $dashboardKey); + + $dashboard = $this->getDashboardInstance($dashboardKey); + $dashboard->buildDashboardConfig(); + + $dashboard->filterContainer() + ->putRetainedFilterValuesInSession( + collect(request()->input('filterValues', [])) + ->diffKeys(request()->input('hiddenFilters') ?? []) + ->toArray() + ); + + return redirect()->route('code16.sharp.api.dashboard', [ + 'dashboardKey' => $dashboardKey, + ...(request()->input('query') ?? []), + ...$dashboard->filterContainer()->getQueryParamsFromFilterValues([ + ...request()->input('filterValues', []), + ...(request()->input('hiddenFilters') ?? []), + ]), + ]); + } +} diff --git a/src/routes/api.php b/src/routes/api.php index 5a388ac15..01f46d8d7 100644 --- a/src/routes/api.php +++ b/src/routes/api.php @@ -1,5 +1,6 @@ name('code16.sharp.api.dashboard') ->middleware('cache.headers:no_store'); + Route::post('/dashboard/{entityKey}/filters', [ApiDashboardFiltersController::class, 'store']) + ->name('code16.sharp.api.dashboard.filters.store'); + Route::post('/show/{entityKey}/command/{commandKey}/{instanceId?}', [ApiShowInstanceCommandController::class, 'update']) ->name('code16.sharp.api.show.command.instance'); From 6196717bf21543c7b23046525ab4156420cd5fb5 Mon Sep 17 00:00:00 2001 From: philippe Date: Tue, 14 Oct 2025 15:07:47 +0200 Subject: [PATCH 05/19] [demo] Add a HiddenFilter to PostDashboard --- demo/app/Sharp/Dashboard/PostDashboard.php | 10 ++++------ demo/app/Sharp/Posts/PostShow.php | 1 + 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/demo/app/Sharp/Dashboard/PostDashboard.php b/demo/app/Sharp/Dashboard/PostDashboard.php index 79bc95348..f6a5e4b51 100644 --- a/demo/app/Sharp/Dashboard/PostDashboard.php +++ b/demo/app/Sharp/Dashboard/PostDashboard.php @@ -13,6 +13,7 @@ use Code16\Sharp\Dashboard\Widgets\SharpGraphWidgetDataSet; use Code16\Sharp\Dashboard\Widgets\SharpLineGraphWidget; use Code16\Sharp\Dashboard\Widgets\WidgetsContainer; +use Code16\Sharp\EntityList\Filters\HiddenFilter; class PostDashboard extends SharpDashboard { @@ -44,18 +45,15 @@ protected function buildDashboardLayout(DashboardLayout $dashboardLayout): void public function getFilters(): ?array { return [ - 'stats-section' => [ - PeriodRequiredFilter::class, - ], + PeriodRequiredFilter::class, + HiddenFilter::make('post_id'), ]; } public function getDashboardCommands(): ?array { return [ - 'stats-section' => [ - ExportStatsAsCsvCommand::class, - ], + ExportStatsAsCsvCommand::class, ]; } diff --git a/demo/app/Sharp/Posts/PostShow.php b/demo/app/Sharp/Posts/PostShow.php index ca2eeed79..3521c3bc5 100644 --- a/demo/app/Sharp/Posts/PostShow.php +++ b/demo/app/Sharp/Posts/PostShow.php @@ -69,6 +69,7 @@ protected function buildShowFields(FieldsContainer $showFields): void ->addField( SharpShowDashboardField::make(PostDashboardEntity::class) ->setLabel('Stats and figures') + ->hideFilterWithValue('post_id', fn ($instanceId) => $instanceId) ) ->addField( SharpShowEntityListField::make(PostBlockEntity::class) From b6741ccd912a9b9ce97578cec02c6be7220ebd6d Mon Sep 17 00:00:00 2001 From: philippe Date: Tue, 14 Oct 2025 15:19:57 +0200 Subject: [PATCH 06/19] Fix dashboard HiddenFilters --- demo/app/Sharp/Dashboard/PostDashboard.php | 2 +- demo/app/Sharp/Posts/PostShow.php | 2 +- src/Show/Fields/SharpShowDashboardField.php | 5 ++++- src/Show/Fields/SharpShowEntityListField.php | 13 ++++--------- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/demo/app/Sharp/Dashboard/PostDashboard.php b/demo/app/Sharp/Dashboard/PostDashboard.php index f6a5e4b51..875954751 100644 --- a/demo/app/Sharp/Dashboard/PostDashboard.php +++ b/demo/app/Sharp/Dashboard/PostDashboard.php @@ -46,7 +46,7 @@ public function getFilters(): ?array { return [ PeriodRequiredFilter::class, - HiddenFilter::make('post_id'), + HiddenFilter::make('post'), ]; } diff --git a/demo/app/Sharp/Posts/PostShow.php b/demo/app/Sharp/Posts/PostShow.php index 3521c3bc5..7b463b7ee 100644 --- a/demo/app/Sharp/Posts/PostShow.php +++ b/demo/app/Sharp/Posts/PostShow.php @@ -69,7 +69,7 @@ protected function buildShowFields(FieldsContainer $showFields): void ->addField( SharpShowDashboardField::make(PostDashboardEntity::class) ->setLabel('Stats and figures') - ->hideFilterWithValue('post_id', fn ($instanceId) => $instanceId) + ->hideFilterWithValue('post', fn ($instanceId) => $instanceId) ) ->addField( SharpShowEntityListField::make(PostBlockEntity::class) diff --git a/src/Show/Fields/SharpShowDashboardField.php b/src/Show/Fields/SharpShowDashboardField.php index c2faf1dff..ebadae187 100644 --- a/src/Show/Fields/SharpShowDashboardField.php +++ b/src/Show/Fields/SharpShowDashboardField.php @@ -75,7 +75,10 @@ public function toArray(): array 'hiddenCommands' => $this->hiddenCommands, 'hiddenFilters' => count($this->hiddenFilters) ? collect($this->hiddenFilters) - ->map(fn ($value) => is_callable($value) ? $value() : $value) + ->map(fn ($value) => is_callable($value) + ? $value(sharp()->context()->instanceId()) + : $value + ) ->all() : null, ]), diff --git a/src/Show/Fields/SharpShowEntityListField.php b/src/Show/Fields/SharpShowEntityListField.php index cc12f3d12..c58db5423 100644 --- a/src/Show/Fields/SharpShowEntityListField.php +++ b/src/Show/Fields/SharpShowEntityListField.php @@ -144,15 +144,10 @@ public function toArray(): array 'hiddenCommands' => $this->hiddenCommands, 'hiddenFilters' => count($this->hiddenFilters) ? collect($this->hiddenFilters) - ->map(function ($value) { - // Filter value can be a Closure - if (is_callable($value)) { - // Call it with current instanceId - return $value(sharp()->context()->instanceId()); - } - - return $value; - }) + ->map(fn ($value) => is_callable($value) + ? $value(sharp()->context()->instanceId()) + : $value + ) ->all() : null, ]), From 960db1ec497f185e5f7a4e24324d31ef67451a7b Mon Sep 17 00:00:00 2001 From: antoine Date: Tue, 14 Oct 2025 15:42:51 +0200 Subject: [PATCH 07/19] wip embedded dashboard --- demo/app/Sharp/Dashboard/PostDashboard.php | 4 +- resources/js/Pages/Dashboard/Dashboard.vue | 257 +---------------- resources/js/Pages/Show/Show.vue | 24 +- resources/js/dashboard/Dashboard.ts | 31 ++ .../js/dashboard/components/Dashboard.vue | 269 ++++++++++++++++++ resources/js/show/Show.ts | 2 +- .../js/show/components/fields/Dashboard.vue | 156 ++++++++++ .../fields/{entity-list => }/EntityList.vue | 0 resources/js/types/generated.d.ts | 18 +- resources/js/types/routes.d.ts | 10 + src/Data/Dashboard/DashboardData.php | 6 + .../Show/Fields/ShowDashboardFieldData.php | 36 +++ src/Data/Show/Fields/ShowFieldData.php | 4 +- src/Enums/ShowFieldType.php | 1 + src/Http/Controllers/DashboardController.php | 1 + 15 files changed, 569 insertions(+), 250 deletions(-) create mode 100644 resources/js/dashboard/Dashboard.ts create mode 100644 resources/js/dashboard/components/Dashboard.vue create mode 100644 resources/js/show/components/fields/Dashboard.vue rename resources/js/show/components/fields/{entity-list => }/EntityList.vue (100%) create mode 100644 src/Data/Show/Fields/ShowDashboardFieldData.php diff --git a/demo/app/Sharp/Dashboard/PostDashboard.php b/demo/app/Sharp/Dashboard/PostDashboard.php index 79bc95348..0f16d5da8 100644 --- a/demo/app/Sharp/Dashboard/PostDashboard.php +++ b/demo/app/Sharp/Dashboard/PostDashboard.php @@ -36,8 +36,8 @@ protected function buildDashboardLayout(DashboardLayout $dashboardLayout): void { $dashboardLayout ->addRow(function (DashboardLayoutRow $row) { - $row->addWidget(.75, 'visits_line') - ->addWidget(.25, 'visits_count'); + $row->addWidget(6, 'visits_line') + ->addWidget(6, 'visits_count'); }); } diff --git a/resources/js/Pages/Dashboard/Dashboard.vue b/resources/js/Pages/Dashboard/Dashboard.vue index 93a8b9caf..084d8af6f 100644 --- a/resources/js/Pages/Dashboard/Dashboard.vue +++ b/resources/js/Pages/Dashboard/Dashboard.vue @@ -1,35 +1,16 @@ - -
-
- - - - -
- -
-
+
+
+
- +
diff --git a/resources/js/Pages/Show/Show.vue b/resources/js/Pages/Show/Show.vue index cd0030dcf..0b3bee6d5 100644 --- a/resources/js/Pages/Show/Show.vue +++ b/resources/js/Pages/Show/Show.vue @@ -1,6 +1,6 @@ + + diff --git a/resources/js/show/Show.ts b/resources/js/show/Show.ts index 64ef8c05e..26d7c86ec 100644 --- a/resources/js/show/Show.ts +++ b/resources/js/show/Show.ts @@ -108,7 +108,7 @@ export class Show implements ShowData { return config('app.debug'); } - if(field.type === 'entityList') { + if(field.type === 'entityList' || field.type === 'dashboard') { return true; } diff --git a/resources/js/show/components/fields/Dashboard.vue b/resources/js/show/components/fields/Dashboard.vue new file mode 100644 index 000000000..54c3b276f --- /dev/null +++ b/resources/js/show/components/fields/Dashboard.vue @@ -0,0 +1,156 @@ + + + diff --git a/resources/js/show/components/fields/entity-list/EntityList.vue b/resources/js/show/components/fields/EntityList.vue similarity index 100% rename from resources/js/show/components/fields/entity-list/EntityList.vue rename to resources/js/show/components/fields/EntityList.vue diff --git a/resources/js/types/generated.d.ts b/resources/js/types/generated.d.ts index fa6b53717..41e273be2 100644 --- a/resources/js/types/generated.d.ts +++ b/resources/js/types/generated.d.ts @@ -90,6 +90,9 @@ export type DashboardData = { data: { [key: string]: any }; filterValues: FilterValuesData; pageAlert: PageAlertData | null; + query: { + [filterKey: string]: string; + }; }; export type DashboardLayoutData = { sections: Array; @@ -797,6 +800,17 @@ export type ShowCustomFieldData = { type: string; emptyVisible: boolean; }; +export type ShowDashboardFieldData = { + value?: null; + key: string; + type: "dashboard"; + emptyVisible: boolean; + dashboardKey: string; + hiddenCommands: Array; + endpointUrl: string; + label: string | null; + hiddenFilters: { [key: string]: any } | null; +}; export type ShowData = { title: string | { [locale: string]: string } | null; authorizations: InstanceAuthorizationsData; @@ -824,6 +838,7 @@ export type ShowEntityListFieldData = { hiddenFilters: { [key: string]: any } | null; }; export type ShowFieldData = + | ShowDashboardFieldData | ShowEntityListFieldData | ShowFileFieldData | ShowListFieldData @@ -835,7 +850,8 @@ export type ShowFieldType = | "list" | "picture" | "text" - | "entityList"; + | "entityList" + | "dashboard"; export type ShowFileFieldData = { value?: { disk: string; diff --git a/resources/js/types/routes.d.ts b/resources/js/types/routes.d.ts index 4081fe3d2..26efc847f 100644 --- a/resources/js/types/routes.d.ts +++ b/resources/js/types/routes.d.ts @@ -212,6 +212,16 @@ declare module 'ziggy-js' { "name": "instanceId" } ], + "code16.sharp.api.dashboard": [ + { + "name": "entityKey" + } + ], + "code16.sharp.api.dashboard.filters.store": [ + { + "name": "entityKey" + } + ], "code16.sharp.api.show.command.instance": [ { "name": "entityKey" diff --git a/src/Data/Dashboard/DashboardData.php b/src/Data/Dashboard/DashboardData.php index 444a7a622..4439de46d 100644 --- a/src/Data/Dashboard/DashboardData.php +++ b/src/Data/Dashboard/DashboardData.php @@ -6,6 +6,7 @@ use Code16\Sharp\Data\Data; use Code16\Sharp\Data\Filters\FilterValuesData; use Code16\Sharp\Data\PageAlertData; +use Spatie\TypeScriptTransformer\Attributes\LiteralTypeScriptType; /** * @internal @@ -21,6 +22,10 @@ public function __construct( public array $data, public FilterValuesData $filterValues, public ?PageAlertData $pageAlert = null, + #[LiteralTypeScriptType('{ + [filterKey: string]: string, + }')] + public ?array $query = null, ) {} public static function from(array $dashboard): self @@ -32,6 +37,7 @@ public static function from(array $dashboard): self data: $dashboard['data'], filterValues: FilterValuesData::from($dashboard['filterValues']), pageAlert: PageAlertData::optional($dashboard['pageAlert'] ?? null), + query: $dashboard['query'], ); } } diff --git a/src/Data/Show/Fields/ShowDashboardFieldData.php b/src/Data/Show/Fields/ShowDashboardFieldData.php new file mode 100644 index 000000000..14b150c14 --- /dev/null +++ b/src/Data/Show/Fields/ShowDashboardFieldData.php @@ -0,0 +1,36 @@ +value.'"')] + public ShowFieldType $type, + public bool $emptyVisible, + public string $dashboardKey, + /** @var string[] */ + public array $hiddenCommands, + public string $endpointUrl, + public ?string $label = null, + /** @var array */ + public ?array $hiddenFilters = null, + ) {} + + public static function from(array $field): self + { + return new self(...$field); + } +} diff --git a/src/Data/Show/Fields/ShowFieldData.php b/src/Data/Show/Fields/ShowFieldData.php index d98cff116..28b4f178a 100644 --- a/src/Data/Show/Fields/ShowFieldData.php +++ b/src/Data/Show/Fields/ShowFieldData.php @@ -7,7 +7,8 @@ use Spatie\TypeScriptTransformer\Attributes\TypeScriptType; #[TypeScriptType( - ShowEntityListFieldData::class + ShowDashboardFieldData::class + .'|'.ShowEntityListFieldData::class // .'|'.ShowCustomFieldData::class .'|'.ShowFileFieldData::class .'|'.ShowListFieldData::class @@ -26,6 +27,7 @@ public static function from(array $field): Data $field['type'] = ShowFieldType::tryFrom($field['type']) ?? $field['type']; return match ($field['type']) { + ShowFieldType::Dashboard => ShowDashboardFieldData::from($field), ShowFieldType::EntityList => ShowEntityListFieldData::from($field), ShowFieldType::File => ShowFileFieldData::from($field), ShowFieldType::List => ShowListFieldData::from($field), diff --git a/src/Enums/ShowFieldType.php b/src/Enums/ShowFieldType.php index c5cdb654a..c7eaa9762 100644 --- a/src/Enums/ShowFieldType.php +++ b/src/Enums/ShowFieldType.php @@ -10,4 +10,5 @@ enum ShowFieldType: string case Picture = 'picture'; case Text = 'text'; case EntityList = 'entityList'; + case Dashboard = 'dashboard'; } diff --git a/src/Http/Controllers/DashboardController.php b/src/Http/Controllers/DashboardController.php index d6d99db00..7c328dd87 100644 --- a/src/Http/Controllers/DashboardController.php +++ b/src/Http/Controllers/DashboardController.php @@ -24,6 +24,7 @@ public function show(string $dashboardKey) 'data' => $dashboardData, 'pageAlert' => $dashboard->pageAlert($dashboardData), 'filterValues' => $dashboard->filterContainer()->getCurrentFilterValuesForFront(request()->all()), + 'query' => count(request()->query()) ? request()->query() : null, ]; if (request()->routeIs('code16.sharp.api.dashboard')) { From aeeb84e5b82b7ada9fed1d43897f5744179254a1 Mon Sep 17 00:00:00 2001 From: antoine Date: Tue, 14 Oct 2025 17:29:54 +0200 Subject: [PATCH 08/19] wip embedded dashboard --- demo/app/Sharp/Posts/PostShow.php | 4 +- resources/js/Pages/Dashboard/Dashboard.vue | 7 +- resources/js/Pages/Show/Show.vue | 4 +- resources/js/components/ui/card/CardTitle.vue | 2 +- resources/js/dashboard/Dashboard.ts | 35 +- .../js/dashboard/components/Dashboard.vue | 384 ++++++++++-------- .../js/show/components/fields/Dashboard.vue | 57 ++- ...EntityLists.php => PreloadsShowFields.php} | 6 +- src/Http/Controllers/ShowController.php | 2 +- src/Http/Controllers/SingleShowController.php | 2 +- src/Show/Fields/SharpShowDashboardField.php | 2 +- src/routes/api.php | 4 +- 12 files changed, 286 insertions(+), 223 deletions(-) rename src/Http/Controllers/{PreloadsShowEntityLists.php => PreloadsShowFields.php} (84%) diff --git a/demo/app/Sharp/Posts/PostShow.php b/demo/app/Sharp/Posts/PostShow.php index 7b463b7ee..e9c3819b2 100644 --- a/demo/app/Sharp/Posts/PostShow.php +++ b/demo/app/Sharp/Posts/PostShow.php @@ -101,8 +101,8 @@ protected function buildShowLayout(ShowLayout $showLayout): void $column->withField('content'); }); }) - ->addDashboardSection(PostDashboardEntity::class) - ->addEntityListSection(PostBlockEntity::class); + ->addDashboardSection(PostDashboardEntity::class, collapsable: true) + ->addEntityListSection(PostBlockEntity::class, collapsable: true); } public function buildShowConfig(): void diff --git a/resources/js/Pages/Dashboard/Dashboard.vue b/resources/js/Pages/Dashboard/Dashboard.vue index 084d8af6f..d2b919134 100644 --- a/resources/js/Pages/Dashboard/Dashboard.vue +++ b/resources/js/Pages/Dashboard/Dashboard.vue @@ -7,10 +7,11 @@ import { route } from "@/utils/url"; import Title from "@/components/Title.vue"; import { useCommands } from "@/commands/useCommands"; - import { watch } from "vue"; + import { ref, watch } from "vue"; import PageBreadcrumb from "@/components/PageBreadcrumb.vue"; import { config } from "@/utils/config"; import DashboardComponent from "@/dashboard/components/Dashboard.vue"; + import { Dashboard } from "@/dashboard/Dashboard"; const props = defineProps<{ dashboard: DashboardData, @@ -18,10 +19,12 @@ }>(); const dashboardKey = route().params.dashboardKey as string; + const dashboard = ref(new Dashboard(props.dashboard, dashboardKey)); const filters = useFilters(props.dashboard.config.filters, props.dashboard.filterValues); const commands = useCommands('dashboard'); watch(() => props.dashboard, () => { + dashboard.value = new Dashboard(props.dashboard, dashboardKey); filters.update(props.dashboard.config.filters, props.dashboard.filterValues); }); @@ -62,7 +65,7 @@