feat(dashboard): add price history, caching, and i18n
This commit is contained in:
@@ -5,6 +5,7 @@ namespace App\Features\Dashboard\Clients;
|
|||||||
use App\Features\Dashboard\Contracts\PriceClient;
|
use App\Features\Dashboard\Contracts\PriceClient;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Http\Client\Factory;
|
use Illuminate\Http\Client\Factory;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
@@ -14,14 +15,14 @@ class ZonneplanPriceClient implements PriceClient
|
|||||||
private readonly Factory $http,
|
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<int, array<string, mixed>> */
|
/** @return array<int, array<string, mixed>> */
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Features\Dashboard\Contracts;
|
namespace App\Features\Dashboard\Contracts;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
interface PriceClient
|
interface PriceClient
|
||||||
{
|
{
|
||||||
public function getElectricityPrices(): array;
|
public function getElectricityPrices(): Collection;
|
||||||
|
|
||||||
public function getGasPrices(): array;
|
public function getGasPrices(): Collection;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Features\Dashboard\Controllers;
|
namespace App\Features\Dashboard\Controllers;
|
||||||
|
|
||||||
use App\Features\Dashboard\Data\DashboardData;
|
|
||||||
use App\Features\Dashboard\Services\DashboardService;
|
use App\Features\Dashboard\Services\DashboardService;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
|
||||||
@@ -10,6 +9,6 @@ class DashboardController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(DashboardService $dashboardService)
|
public function index(DashboardService $dashboardService)
|
||||||
{
|
{
|
||||||
return inertia('dashboard::index', DashboardData::from($dashboardService->getDashboardData()));
|
return $dashboardService->getDashboardData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,24 @@
|
|||||||
|
|
||||||
namespace App\Features\Dashboard\Data;
|
namespace App\Features\Dashboard\Data;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Spatie\LaravelData\Data;
|
use Spatie\LaravelData\Data;
|
||||||
|
|
||||||
class DashboardData extends Data
|
class DashboardData extends Data
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @property array<ElectricityData> $electricity
|
* @property Collection<ElectricityData> $electricityHistory
|
||||||
* @property array<GasData> $gas
|
* @property Collection<GasData> $gasHistory
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public array $electricity,
|
/** @var array<ElectricityData> */
|
||||||
public array $gas
|
public Collection $electricityHistory,
|
||||||
|
public ?ElectricityData $electricityCheapest,
|
||||||
|
public ?ElectricityData $electricityDurable,
|
||||||
|
public ?ElectricityData $electricityPriciest,
|
||||||
|
/** @var array<ElectricityData> */
|
||||||
|
public Collection $gasHistory,
|
||||||
|
public ?GasData $gasCheapest,
|
||||||
|
public ?GasData $gasPriciest,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use App\Features\Dashboard\Contracts\PriceRepository as ContractsPriceRepository
|
|||||||
use App\Features\Dashboard\Data\ElectricityData;
|
use App\Features\Dashboard\Data\ElectricityData;
|
||||||
use App\Features\Dashboard\Data\GasData;
|
use App\Features\Dashboard\Data\GasData;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Override;
|
use Override;
|
||||||
|
|
||||||
class ZonneplanPriceRepository implements ContractsPriceRepository
|
class ZonneplanPriceRepository implements ContractsPriceRepository
|
||||||
@@ -18,7 +19,7 @@ class ZonneplanPriceRepository implements ContractsPriceRepository
|
|||||||
#[Override]
|
#[Override]
|
||||||
public function retrieveElectricity(): Collection
|
public function retrieveElectricity(): Collection
|
||||||
{
|
{
|
||||||
$data = $this->client->getElectricityPrices();
|
$data = Cache::remember('electricity', 3600, fn () => $this->client->getElectricityPrices()->toArray());
|
||||||
|
|
||||||
return collect(ElectricityData::collect($data));
|
return collect(ElectricityData::collect($data));
|
||||||
}
|
}
|
||||||
@@ -26,7 +27,7 @@ class ZonneplanPriceRepository implements ContractsPriceRepository
|
|||||||
#[Override]
|
#[Override]
|
||||||
public function retrieveGas(): Collection
|
public function retrieveGas(): Collection
|
||||||
{
|
{
|
||||||
$data = $this->client->getGasPrices();
|
$data = Cache::remember('gas', 3600, fn () => $this->client->getGasPrices()->toArray());
|
||||||
|
|
||||||
return collect(GasData::collect($data));
|
return collect(GasData::collect($data));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Features\Dashboard\Services;
|
namespace App\Features\Dashboard\Services;
|
||||||
|
|
||||||
use App\Features\Dashboard\Contracts\PriceRepository;
|
use App\Features\Dashboard\Contracts\PriceRepository;
|
||||||
|
use App\Features\Dashboard\Data\DashboardData;
|
||||||
|
|
||||||
class DashboardService
|
class DashboardService
|
||||||
{
|
{
|
||||||
@@ -10,11 +11,25 @@ class DashboardService
|
|||||||
public PriceRepository $repository
|
public PriceRepository $repository
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function getDashboardData(): array
|
public function getDashboardData(): DashboardData
|
||||||
{
|
{
|
||||||
return [
|
$electricity = $this->repository->retrieveElectricity();
|
||||||
'electricity' => $this->repository->retrieveElectricity(),
|
$eCheapest = $electricity->min('total_price_tax_included');
|
||||||
'gas' => $this->repository->retrieveGas(),
|
$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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+30
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "Zonneplan",
|
"name": "html",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"axios": "^1.19.0",
|
"axios": "^1.19.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"concurrently": "^9.0.1",
|
"concurrently": "^9.0.1",
|
||||||
|
"i18next": "^26.3.6",
|
||||||
"laravel-vite-plugin": "^3.1",
|
"laravel-vite-plugin": "^3.1",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"tailwindcss": "^4.1.1",
|
"tailwindcss": "^4.1.1",
|
||||||
@@ -3958,6 +3959,34 @@
|
|||||||
"node": ">= 6"
|
"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": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"axios": "^1.19.0",
|
"axios": "^1.19.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"concurrently": "^9.0.1",
|
"concurrently": "^9.0.1",
|
||||||
|
"i18next": "^26.3.6",
|
||||||
"laravel-vite-plugin": "^3.1",
|
"laravel-vite-plugin": "^3.1",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"tailwindcss": "^4.1.1",
|
"tailwindcss": "^4.1.1",
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { createInertiaApp } from '@inertiajs/vue3';
|
import { createInertiaApp } from '@inertiajs/vue3';
|
||||||
import { DefineComponent } from 'vue';
|
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';
|
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
|
||||||
|
|
||||||
@@ -8,6 +10,9 @@ createInertiaApp({
|
|||||||
progress: {
|
progress: {
|
||||||
color: '#4B5563',
|
color: '#4B5563',
|
||||||
},
|
},
|
||||||
|
layout(name, page) {
|
||||||
|
return Layout;
|
||||||
|
},
|
||||||
resolve(name) {
|
resolve(name) {
|
||||||
const pages = import.meta.glob<DefineComponent>(
|
const pages = import.meta.glob<DefineComponent>(
|
||||||
['./features/*/pages/*.vue', './pages/*.vue'],
|
['./features/*/pages/*.vue', './pages/*.vue'],
|
||||||
@@ -26,4 +31,7 @@ createInertiaApp({
|
|||||||
|
|
||||||
return pages[`./pages/${name}.vue`];
|
return pages[`./pages/${name}.vue`];
|
||||||
},
|
},
|
||||||
|
async withApp(app, options) {
|
||||||
|
await init;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
import useDashboardViewModel from '../viewmodels/DashboardViewModel';
|
||||||
|
|
||||||
<template></template>
|
const model = useDashboardViewModel();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
model.getDashboardData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>{{ model.isLoading }}</div>
|
||||||
|
<div>{{ model.dashboardData }}</div>
|
||||||
|
</template>
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
export default function DashboardViewModel() {
|
import { create } from 'axios';
|
||||||
return {};
|
import { ref } from 'vue';
|
||||||
|
import DashboardData = App.Features.Dashboard.Data.DashboardData;
|
||||||
|
|
||||||
|
export default function useDashboardViewModel() {
|
||||||
|
const isLoading = ref<boolean>(false);
|
||||||
|
const dashboardData = ref<DashboardData>();
|
||||||
|
|
||||||
|
function getDashboardData() {
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
const client = create();
|
||||||
|
client
|
||||||
|
.get<DashboardData>('/api/energy-prices')
|
||||||
|
.then((res) => {
|
||||||
|
dashboardData.value = res.data;
|
||||||
|
})
|
||||||
|
.finally(() => (isLoading.value = false));
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isLoading, dashboardData, getDashboardData };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div></div>
|
||||||
|
</template>
|
||||||
+8
-3
@@ -1,10 +1,15 @@
|
|||||||
declare namespace App {
|
declare namespace App {
|
||||||
namespace Features {
|
namespace Features {
|
||||||
namespace Dashboard {
|
namespace Dashboard {
|
||||||
namespace DTO {
|
namespace Data {
|
||||||
export type DashboardData = {
|
export type DashboardData = {
|
||||||
electricity: App.Features.Dashboard.DTO.ElectricityData;
|
electricityHistory: App.Features.Dashboard.Data.ElectricityData[];
|
||||||
gas: App.Features.Dashboard.DTO.GasData;
|
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 = {
|
export type ElectricityData = {
|
||||||
startDate: string;
|
startDate: string;
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"generated.d.ts": "237c6a8a14bec8d9c63d81a00b842cf6"
|
"generated.d.ts": "63b1a5a3410956e9dfd29ea3c90abc71"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import i18next from 'i18next';
|
||||||
|
import translations from './translations';
|
||||||
|
|
||||||
|
export const init = i18next.init({
|
||||||
|
lng: 'nl',
|
||||||
|
resources: translations,
|
||||||
|
});
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import nl from "./nl";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
nl
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"title": "Overzicht"
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import dashboard from './dashboard.json';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
dashboard,
|
||||||
|
};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import dashboard from '@/routes/dashboard';
|
||||||
|
import history from '@/routes/history';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<nav class="fixed top-0 left-0 w-40">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a :href="dashboard.index().url">{{ t('dashboard.title') }}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a :href="history.index().url">{{ t('history.title') }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<div class="absolute top-0 left-40"><slot /></div>
|
||||||
|
</template>
|
||||||
+3
-1
@@ -3,4 +3,6 @@
|
|||||||
use App\Features\Dashboard\Controllers\DashboardController;
|
use App\Features\Dashboard\Controllers\DashboardController;
|
||||||
use Illuminate\Support\Facades\Route;
|
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');
|
||||||
|
});
|
||||||
|
|||||||
+2
-1
@@ -2,4 +2,5 @@
|
|||||||
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::inertia('/', 'dashboard::index');
|
Route::inertia('/', 'dashboard::index')->name('dashboard.index');
|
||||||
|
Route::inertia('/history', 'history::index')->name('history.index');
|
||||||
|
|||||||
Reference in New Issue
Block a user