diff --git a/assets/vue/components/charts/LineChart.vue b/assets/vue/components/charts/LineChart.vue deleted file mode 100644 index 9ce7870..0000000 --- a/assets/vue/components/charts/LineChart.vue +++ /dev/null @@ -1,97 +0,0 @@ - - - - diff --git a/assets/vue/components/dashboard/CampaignsTable.vue b/assets/vue/components/dashboard/CampaignsTable.vue index eff6d5d..6688945 100644 --- a/assets/vue/components/dashboard/CampaignsTable.vue +++ b/assets/vue/components/dashboard/CampaignsTable.vue @@ -1,6 +1,6 @@ diff --git a/assets/vue/components/dashboard/PerformanceChartCard.vue b/assets/vue/components/dashboard/PerformanceChartCard.vue index 7266e23..4dd02a2 100644 --- a/assets/vue/components/dashboard/PerformanceChartCard.vue +++ b/assets/vue/components/dashboard/PerformanceChartCard.vue @@ -1,8 +1,8 @@ diff --git a/assets/vue/components/dashboard/QuickActionsCard.vue b/assets/vue/components/dashboard/QuickActionsCard.vue new file mode 100644 index 0000000..51e5419 --- /dev/null +++ b/assets/vue/components/dashboard/QuickActionsCard.vue @@ -0,0 +1,89 @@ + + + diff --git a/assets/vue/components/dashboard/RecentCampaignsCard.vue b/assets/vue/components/dashboard/RecentCampaignsCard.vue index 57849dd..0f6f010 100644 --- a/assets/vue/components/dashboard/RecentCampaignsCard.vue +++ b/assets/vue/components/dashboard/RecentCampaignsCard.vue @@ -18,35 +18,9 @@ import BaseCard from '../../components/base/BaseCard.vue' import CampaignsTable from './CampaignsTable.vue' const props = defineProps({ - // todo: take from backend rows: { type: Array, - default: () => [ - { - id: 'campaign-1', - name: 'Summer Sale', - status: 'Sent', - date: '2023-07-01', - openRate: '15%', - clickRate: '5%', - }, - { - id: 'campaign-2', - name: 'Winter Sale', - status: 'scheduled', - date: '2023-07-01', - openRate: '23%', - clickRate: '0%', - }, - { - id: 'campaign-3', - name: 'Autumn Sale', - status: 'draft', - date: '2023-07-01', - openRate: '11%', - clickRate: '0%', - }, - ], + default: () => [], }, }) diff --git a/assets/vue/views/DashboardView.vue b/assets/vue/views/DashboardView.vue index 050be6b..16dff3d 100644 --- a/assets/vue/views/DashboardView.vue +++ b/assets/vue/views/DashboardView.vue @@ -8,16 +8,19 @@ -
-
- +
+
+ +
+
+
- +
@@ -28,13 +31,33 @@ import AdminLayout from '../layouts/AdminLayout.vue' import KpiGrid from '../components/dashboard/KpiGrid.vue' import PerformanceChartCard from '../components/dashboard/PerformanceChartCard.vue' +import QuickActionsCard from '../components/dashboard/QuickActionsCard.vue' import RecentCampaignsCard from '../components/dashboard/RecentCampaignsCard.vue' -const chart = { - labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], +const appElement = document.getElementById('vue-app') + +const parseDashboardStats = () => { + const raw = appElement?.dataset.dashboardStats + if (!raw) { + return {} + } + + try { + return JSON.parse(raw) + } catch { + return {} + } +} + +const dashboardStats = parseDashboardStats() + +const chart = dashboardStats.chart || { + labels: [], series: [ - { name: 'Opens', data: [2500, 2200, 10000, 4000, 4500, 3800] }, - { name: 'Clicks', data: [4200, 3500, 3200, 3000, 3600, 4100] }, + { name: 'Opens', data: [] }, + { name: 'Clicks', data: [] }, ], } + +const recentCampaigns = dashboardStats.recent_campaigns || [] diff --git a/package.json b/package.json index 66fdd7b..7b5b04a 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,10 @@ }, "dependencies": { "@tatevikgr/rest-api-client": "^1.0.2", + "apexcharts": "^5.10.4", "vue": "^3.5.16", - "vue-router": "4" + "vue-router": "4", + "vue3-apexcharts": "^1.11.1" }, "scripts": { "prepare": "husky install" diff --git a/src/Controller/DashboardController.php b/src/Controller/DashboardController.php index 6215d3a..8a92959 100755 --- a/src/Controller/DashboardController.php +++ b/src/Controller/DashboardController.php @@ -4,6 +4,7 @@ namespace PhpList\WebFrontend\Controller; +use PhpList\RestApiClient\Endpoint\StatisticsClient; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -11,13 +12,64 @@ class DashboardController extends AbstractController { + public function __construct(private readonly StatisticsClient $statisticsClient) + { + } + #[Route('/', name: 'home', methods: ['GET'])] public function index(Request $request): Response { + $stats = $this->statisticsClient->getDashboardStats(); + $recentCampaigns = []; + foreach ($stats->recentCampaigns as $campaign) { + $recentCampaigns[] = [ + 'name' => $campaign->name, + 'status' => $campaign->status, + 'date' => $campaign->date?->format('Y-m-d') ?? '', + 'openRate' => $campaign->openRate, + 'clickRate' => $campaign->clickRate, + ]; + } + + $chartLabels = []; + $chartOpens = []; + $chartClicks = []; + foreach ($stats->campaignPerformance as $point) { + $chartLabels[] = $point->date?->format('M d') ?? ''; + $chartOpens[] = $point->opens; + $chartClicks[] = $point->clicks; + } + return $this->render('spa.html.twig', [ 'page' => 'Dashboard', 'api_token' => $request->getSession()->get('auth_token'), 'api_base_url' => $this->getParameter('api_base_url'), + 'dashboard_stats' => [ + 'total_subscribers' => [ + 'value' => $stats->totalSubscribers->value, + 'change_vs_last_month' => $stats->totalSubscribers->changeVsLastMonth, + ], + 'active_campaigns' => [ + 'value' => $stats->activeCampaigns->value, + 'change_vs_last_month' => $stats->activeCampaigns->changeVsLastMonth, + ], + 'open_rate' => [ + 'value' => $stats->openRate->value, + 'change_vs_last_month' => $stats->openRate->changeVsLastMonth, + ], + 'bounce_rate' => [ + 'value' => $stats->bounceRate->value, + 'change_vs_last_month' => $stats->bounceRate->changeVsLastMonth, + ], + 'recent_campaigns' => $recentCampaigns, + 'chart' => [ + 'labels' => $chartLabels, + 'series' => [ + ['name' => 'Opens', 'data' => $chartOpens], + ['name' => 'Clicks', 'data' => $chartClicks], + ], + ], + ], ]); } } diff --git a/src/Security/SessionAuthenticator.php b/src/Security/SessionAuthenticator.php index badce5a..dbdf738 100644 --- a/src/Security/SessionAuthenticator.php +++ b/src/Security/SessionAuthenticator.php @@ -34,6 +34,9 @@ public function supports(Request $request): ?bool public function authenticate(Request $request): Passport { + if (!$request->hasSession()) { + throw new AuthenticationException('Session is not available'); + } $session = $request->getSession(); if ($session->has('auth_token')) { diff --git a/templates/spa.html.twig b/templates/spa.html.twig index 668d4df..91e6c01 100644 --- a/templates/spa.html.twig +++ b/templates/spa.html.twig @@ -5,6 +5,7 @@ {% block body %}
+ data-api-base-url="{{ api_base_url|default('') }}" + data-dashboard-stats="{{ dashboard_stats|default({})|json_encode|e('html_attr') }}">
{% endblock %} diff --git a/yarn.lock b/yarn.lock index e7838df..c3bd267 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1531,6 +1531,11 @@ ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +apexcharts@^5.10.4: + version "5.10.4" + resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-5.10.4.tgz#79c9a05ab40b069f33873a1859de6cb0882ccf0e" + integrity sha512-gt0VUqZ2+mr25ScbUcKZgJr96jKYm4vjOcxEWCEh/E5F4dWqhyo3dBhPRvNNnkKiWxkMd2cBwj3ZYH3rK39fkA== + argparse@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" @@ -3700,6 +3705,11 @@ vue-router@4: dependencies: "@vue/devtools-api" "^6.6.4" +vue3-apexcharts@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/vue3-apexcharts/-/vue3-apexcharts-1.11.1.tgz#5a9f8a319ae196e0490bc5413ab68bb400685c55" + integrity sha512-MbN3vg8bMG19wc0Lm1HkeQvODgLm56DgpIxtNUO0xpf/JCzYWVGE4jzXp2JISzy2s3Kul1yOxNQUYsLvKQ5L9g== + vue@^3.5.16: version "3.5.16" resolved "https://registry.npmjs.org/vue/-/vue-3.5.16.tgz"