From 7fb49131c44c15fc92b9c9e085f17f6b8b23d0e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zo=C3=AB?= Date: Sat, 1 Aug 2026 21:21:43 +0200 Subject: [PATCH] feat(dashboard): add price history, caching, and i18n --- .../Clients/ZonneplanPriceClient.php | 9 +++--- .../Dashboard/Contracts/PriceClient.php | 6 ++-- .../Controllers/DashboardController.php | 3 +- app/Features/Dashboard/Data/DashboardData.php | 16 +++++++--- .../Repositories/ZonneplanPriceRepository.php | 5 +-- .../Dashboard/Services/DashboardService.php | 25 ++++++++++++--- package-lock.json | 31 ++++++++++++++++++- package.json | 1 + resources/js/app.ts | 8 +++++ .../js/features/dashboard/pages/Index.vue | 16 ++++++++-- .../viewmodels/DashboardViewModel.ts | 23 ++++++++++++-- resources/js/features/history/pages/Index.vue | 5 +++ .../history/viewmodels/HistoryViewModel.ts | 0 resources/js/generated/generated.d.ts | 11 +++++-- .../typescript-transformer-manifest.json | 2 +- resources/js/lang/init.ts | 7 +++++ resources/js/lang/translations/index.ts | 5 +++ .../js/lang/translations/nl/dashboard.json | 3 ++ resources/js/lang/translations/nl/index.ts | 5 +++ resources/js/layouts/Layout.vue | 19 ++++++++++++ routes/api.php | 4 ++- routes/web.php | 3 +- 22 files changed, 177 insertions(+), 30 deletions(-) create mode 100644 resources/js/features/history/pages/Index.vue create mode 100644 resources/js/features/history/viewmodels/HistoryViewModel.ts create mode 100644 resources/js/lang/init.ts create mode 100644 resources/js/lang/translations/index.ts create mode 100644 resources/js/lang/translations/nl/dashboard.json create mode 100644 resources/js/lang/translations/nl/index.ts create mode 100644 resources/js/layouts/Layout.vue diff --git a/app/Features/Dashboard/Clients/ZonneplanPriceClient.php b/app/Features/Dashboard/Clients/ZonneplanPriceClient.php index 5db189c..dbaabb9 100644 --- a/app/Features/Dashboard/Clients/ZonneplanPriceClient.php +++ b/app/Features/Dashboard/Clients/ZonneplanPriceClient.php @@ -5,6 +5,7 @@ namespace App\Features\Dashboard\Clients; use App\Features\Dashboard\Contracts\PriceClient; use Exception; use Illuminate\Http\Client\Factory; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Log; use Throwable; @@ -14,14 +15,14 @@ class ZonneplanPriceClient implements PriceClient private readonly Factory $http, ) {} - public function getElectricityPrices(): array + public function getElectricityPrices(): Collection { - return $this->get('/energy-prices/electricity/upcoming'); + return collect($this->get('/energy-prices/electricity/upcoming')); } - public function getGasPrices(): array + public function getGasPrices(): Collection { - return $this->get('/energy-prices/gas/upcoming'); + return collect($this->get('/energy-prices/gas/upcoming')); } /** @return array> */ diff --git a/app/Features/Dashboard/Contracts/PriceClient.php b/app/Features/Dashboard/Contracts/PriceClient.php index 1b61269..81f07e7 100644 --- a/app/Features/Dashboard/Contracts/PriceClient.php +++ b/app/Features/Dashboard/Contracts/PriceClient.php @@ -2,9 +2,11 @@ namespace App\Features\Dashboard\Contracts; +use Illuminate\Support\Collection; + interface PriceClient { - public function getElectricityPrices(): array; + public function getElectricityPrices(): Collection; - public function getGasPrices(): array; + public function getGasPrices(): Collection; } diff --git a/app/Features/Dashboard/Controllers/DashboardController.php b/app/Features/Dashboard/Controllers/DashboardController.php index 668aa39..c1a04ef 100644 --- a/app/Features/Dashboard/Controllers/DashboardController.php +++ b/app/Features/Dashboard/Controllers/DashboardController.php @@ -2,7 +2,6 @@ namespace App\Features\Dashboard\Controllers; -use App\Features\Dashboard\Data\DashboardData; use App\Features\Dashboard\Services\DashboardService; use App\Http\Controllers\Controller; @@ -10,6 +9,6 @@ class DashboardController extends Controller { public function index(DashboardService $dashboardService) { - return inertia('dashboard::index', DashboardData::from($dashboardService->getDashboardData())); + return $dashboardService->getDashboardData(); } } diff --git a/app/Features/Dashboard/Data/DashboardData.php b/app/Features/Dashboard/Data/DashboardData.php index 7fcb3db..7654698 100644 --- a/app/Features/Dashboard/Data/DashboardData.php +++ b/app/Features/Dashboard/Data/DashboardData.php @@ -2,16 +2,24 @@ namespace App\Features\Dashboard\Data; +use Illuminate\Support\Collection; use Spatie\LaravelData\Data; class DashboardData extends Data { /** - * @property array $electricity - * @property array $gas + * @property Collection $electricityHistory + * @property Collection $gasHistory */ public function __construct( - public array $electricity, - public array $gas + /** @var array */ + public Collection $electricityHistory, + public ?ElectricityData $electricityCheapest, + public ?ElectricityData $electricityDurable, + public ?ElectricityData $electricityPriciest, + /** @var array */ + public Collection $gasHistory, + public ?GasData $gasCheapest, + public ?GasData $gasPriciest, ) {} } diff --git a/app/Features/Dashboard/Repositories/ZonneplanPriceRepository.php b/app/Features/Dashboard/Repositories/ZonneplanPriceRepository.php index 681e3b0..d3a43c6 100644 --- a/app/Features/Dashboard/Repositories/ZonneplanPriceRepository.php +++ b/app/Features/Dashboard/Repositories/ZonneplanPriceRepository.php @@ -7,6 +7,7 @@ use App\Features\Dashboard\Contracts\PriceRepository as ContractsPriceRepository use App\Features\Dashboard\Data\ElectricityData; use App\Features\Dashboard\Data\GasData; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Cache; use Override; class ZonneplanPriceRepository implements ContractsPriceRepository @@ -18,7 +19,7 @@ class ZonneplanPriceRepository implements ContractsPriceRepository #[Override] public function retrieveElectricity(): Collection { - $data = $this->client->getElectricityPrices(); + $data = Cache::remember('electricity', 3600, fn () => $this->client->getElectricityPrices()->toArray()); return collect(ElectricityData::collect($data)); } @@ -26,7 +27,7 @@ class ZonneplanPriceRepository implements ContractsPriceRepository #[Override] public function retrieveGas(): Collection { - $data = $this->client->getGasPrices(); + $data = Cache::remember('gas', 3600, fn () => $this->client->getGasPrices()->toArray()); return collect(GasData::collect($data)); } diff --git a/app/Features/Dashboard/Services/DashboardService.php b/app/Features/Dashboard/Services/DashboardService.php index 4af29c5..e427f32 100644 --- a/app/Features/Dashboard/Services/DashboardService.php +++ b/app/Features/Dashboard/Services/DashboardService.php @@ -3,6 +3,7 @@ namespace App\Features\Dashboard\Services; use App\Features\Dashboard\Contracts\PriceRepository; +use App\Features\Dashboard\Data\DashboardData; class DashboardService { @@ -10,11 +11,25 @@ class DashboardService public PriceRepository $repository ) {} - public function getDashboardData(): array + public function getDashboardData(): DashboardData { - return [ - 'electricity' => $this->repository->retrieveElectricity(), - 'gas' => $this->repository->retrieveGas(), - ]; + $electricity = $this->repository->retrieveElectricity(); + $eCheapest = $electricity->min('total_price_tax_included'); + $eDurable = $electricity->min('sustainability_score'); + $ePriciest = $electricity->max('total_price_tax_included'); + + $gas = $this->repository->retrieveGas(); + $gCheapest = $gas->min('total_price_tax_included'); + $gPriciest = $gas->min('total_price_tax_included'); + + return new DashboardData( + $electricity, + $eCheapest, + $eDurable, + $ePriciest, + $gas, + $gCheapest, + $gPriciest + ); } } diff --git a/package-lock.json b/package-lock.json index edbe537..cb92fad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "Zonneplan", + "name": "html", "lockfileVersion": 3, "requires": true, "packages": { @@ -12,6 +12,7 @@ "axios": "^1.19.0", "clsx": "^2.1.1", "concurrently": "^9.0.1", + "i18next": "^26.3.6", "laravel-vite-plugin": "^3.1", "tailwind-merge": "^3.2.0", "tailwindcss": "^4.1.1", @@ -3958,6 +3959,34 @@ "node": ">= 6" } }, + "node_modules/i18next": { + "version": "26.3.6", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.6.tgz", + "integrity": "sha512-Bu5Z2nAXgfVyM8xvW3jk9EKRIuX37PudsrBViThNFx7CR7aaYTpP01cxNB/E4c4UUzTDiAZRstEhsRfPOL/8xA==", + "funding": [ + { + "type": "individual", + "url": "https://www.locize.com/i18next" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + }, + { + "type": "individual", + "url": "https://www.locize.com" + } + ], + "license": "MIT", + "peerDependencies": { + "typescript": "^5 || ^6 || ^7" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", diff --git a/package.json b/package.json index 45f59c8..353c676 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "axios": "^1.19.0", "clsx": "^2.1.1", "concurrently": "^9.0.1", + "i18next": "^26.3.6", "laravel-vite-plugin": "^3.1", "tailwind-merge": "^3.2.0", "tailwindcss": "^4.1.1", diff --git a/resources/js/app.ts b/resources/js/app.ts index 2a2dcaa..4362f10 100644 --- a/resources/js/app.ts +++ b/resources/js/app.ts @@ -1,5 +1,7 @@ import { createInertiaApp } from '@inertiajs/vue3'; import { DefineComponent } from 'vue'; +import Layout from './layouts/Layout.vue'; +import { init } from './lang/init.js'; const appName = import.meta.env.VITE_APP_NAME || 'Laravel'; @@ -8,6 +10,9 @@ createInertiaApp({ progress: { color: '#4B5563', }, + layout(name, page) { + return Layout; + }, resolve(name) { const pages = import.meta.glob( ['./features/*/pages/*.vue', './pages/*.vue'], @@ -26,4 +31,7 @@ createInertiaApp({ return pages[`./pages/${name}.vue`]; }, + async withApp(app, options) { + await init; + }, }); diff --git a/resources/js/features/dashboard/pages/Index.vue b/resources/js/features/dashboard/pages/Index.vue index e519b41..d9062ab 100644 --- a/resources/js/features/dashboard/pages/Index.vue +++ b/resources/js/features/dashboard/pages/Index.vue @@ -1,3 +1,15 @@ - + + + diff --git a/resources/js/features/dashboard/viewmodels/DashboardViewModel.ts b/resources/js/features/dashboard/viewmodels/DashboardViewModel.ts index 4421803..5108f14 100644 --- a/resources/js/features/dashboard/viewmodels/DashboardViewModel.ts +++ b/resources/js/features/dashboard/viewmodels/DashboardViewModel.ts @@ -1,3 +1,22 @@ -export default function DashboardViewModel() { - return {}; +import { create } from 'axios'; +import { ref } from 'vue'; +import DashboardData = App.Features.Dashboard.Data.DashboardData; + +export default function useDashboardViewModel() { + const isLoading = ref(false); + const dashboardData = ref(); + + function getDashboardData() { + isLoading.value = true; + + const client = create(); + client + .get('/api/energy-prices') + .then((res) => { + dashboardData.value = res.data; + }) + .finally(() => (isLoading.value = false)); + } + + return { isLoading, dashboardData, getDashboardData }; } diff --git a/resources/js/features/history/pages/Index.vue b/resources/js/features/history/pages/Index.vue new file mode 100644 index 0000000..ce1e62b --- /dev/null +++ b/resources/js/features/history/pages/Index.vue @@ -0,0 +1,5 @@ + + + diff --git a/resources/js/features/history/viewmodels/HistoryViewModel.ts b/resources/js/features/history/viewmodels/HistoryViewModel.ts new file mode 100644 index 0000000..e69de29 diff --git a/resources/js/generated/generated.d.ts b/resources/js/generated/generated.d.ts index 4f3a46d..7b5b492 100644 --- a/resources/js/generated/generated.d.ts +++ b/resources/js/generated/generated.d.ts @@ -1,10 +1,15 @@ declare namespace App { namespace Features { namespace Dashboard { - namespace DTO { + namespace Data { export type DashboardData = { - electricity: App.Features.Dashboard.DTO.ElectricityData; - gas: App.Features.Dashboard.DTO.GasData; + electricityHistory: App.Features.Dashboard.Data.ElectricityData[]; + electricityCheapest: App.Features.Dashboard.Data.ElectricityData | null; + electricityDurable: App.Features.Dashboard.Data.ElectricityData | null; + electricityPriciest: App.Features.Dashboard.Data.ElectricityData | null; + gasHistory: App.Features.Dashboard.Data.ElectricityData[]; + gasCheapest: App.Features.Dashboard.Data.GasData | null; + gasPriciest: App.Features.Dashboard.Data.GasData | null; }; export type ElectricityData = { startDate: string; diff --git a/resources/js/generated/typescript-transformer-manifest.json b/resources/js/generated/typescript-transformer-manifest.json index bc4a7e8..36f9081 100644 --- a/resources/js/generated/typescript-transformer-manifest.json +++ b/resources/js/generated/typescript-transformer-manifest.json @@ -1,3 +1,3 @@ { - "generated.d.ts": "237c6a8a14bec8d9c63d81a00b842cf6" + "generated.d.ts": "63b1a5a3410956e9dfd29ea3c90abc71" } \ No newline at end of file diff --git a/resources/js/lang/init.ts b/resources/js/lang/init.ts new file mode 100644 index 0000000..9c8f877 --- /dev/null +++ b/resources/js/lang/init.ts @@ -0,0 +1,7 @@ +import i18next from 'i18next'; +import translations from './translations'; + +export const init = i18next.init({ + lng: 'nl', + resources: translations, +}); diff --git a/resources/js/lang/translations/index.ts b/resources/js/lang/translations/index.ts new file mode 100644 index 0000000..21c29f5 --- /dev/null +++ b/resources/js/lang/translations/index.ts @@ -0,0 +1,5 @@ +import nl from "./nl"; + +export default { + nl +} diff --git a/resources/js/lang/translations/nl/dashboard.json b/resources/js/lang/translations/nl/dashboard.json new file mode 100644 index 0000000..cc61669 --- /dev/null +++ b/resources/js/lang/translations/nl/dashboard.json @@ -0,0 +1,3 @@ +{ + "title": "Overzicht" +} diff --git a/resources/js/lang/translations/nl/index.ts b/resources/js/lang/translations/nl/index.ts new file mode 100644 index 0000000..f483f3c --- /dev/null +++ b/resources/js/lang/translations/nl/index.ts @@ -0,0 +1,5 @@ +import dashboard from './dashboard.json'; + +export default { + dashboard, +}; diff --git a/resources/js/layouts/Layout.vue b/resources/js/layouts/Layout.vue new file mode 100644 index 0000000..50db2fb --- /dev/null +++ b/resources/js/layouts/Layout.vue @@ -0,0 +1,19 @@ + + + diff --git a/routes/api.php b/routes/api.php index cc3c5c1..8390db6 100644 --- a/routes/api.php +++ b/routes/api.php @@ -3,4 +3,6 @@ use App\Features\Dashboard\Controllers\DashboardController; use Illuminate\Support\Facades\Route; -Route::get('/energy-prices', [DashboardController::class, 'index'])->name('dashboard.index'); +Route::name('api.')->group(function () { + Route::get('/energy-prices', [DashboardController::class, 'index'])->name('dashboard.index'); +}); diff --git a/routes/web.php b/routes/web.php index a3038b3..5d5cbff 100644 --- a/routes/web.php +++ b/routes/web.php @@ -2,4 +2,5 @@ use Illuminate\Support\Facades\Route; -Route::inertia('/', 'dashboard::index'); +Route::inertia('/', 'dashboard::index')->name('dashboard.index'); +Route::inertia('/history', 'history::index')->name('history.index');