Skip to content

Commit fea446d

Browse files
committed
Vue dashboard
1 parent 9e0378b commit fea446d

30 files changed

Lines changed: 1261 additions & 23 deletions

assets/app.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
console.log('app.js loaded');
12
import { createApp } from 'vue';
23
import App from './vue/App.vue';
4+
import { router } from './router';
35

4-
// Mount the main app if the element exists
56
const appElement = document.getElementById('vue-app');
67
if (appElement) {
7-
createApp(App).mount('#vue-app');
8+
const app = createApp(App);
9+
app.use(router);
10+
app.mount('#vue-app');
811
}
912

assets/router/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { createRouter, createWebHistory } from 'vue-router';
2+
3+
export const router = createRouter({
4+
history: createWebHistory(),
5+
routes: [ /* ... */ ],
6+
});

assets/vue/App.vue

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,55 @@
11
<template>
2-
<div>
3-
<h2>Hello from Vue</h2>
4-
<p>{{ message }}</p>
5-
</div>
2+
<aside class="sidebar">
3+
<div class="sidebar__top">
4+
<SidebarLogo />
5+
6+
<nav class="sidebar__nav">
7+
<SidebarNavSection
8+
v-for="section in sections"
9+
:key="section.id"
10+
v-bind="section"
11+
/>
12+
</nav>
13+
</div>
14+
15+
<SidebarCapacityCard class="sidebar__bottom" />
16+
</aside>
617
</template>
718

8-
<script>
9-
export default {
10-
name: 'App',
11-
data() {
12-
return {
13-
message: 'This is a reusable component!'
14-
}
19+
<script setup>
20+
import SidebarLogo from './SidebarLogo.vue'
21+
import SidebarNavSection from './SidebarNavSection.vue'
22+
import SidebarCapacityCard from './SidebarCapacityCard.vue'
23+
24+
const sections = [
25+
{
26+
id: 'general',
27+
label: 'General',
28+
items: [
29+
{ label: 'Dashboard', icon: 'grid', route: '/dashboard', badge: null },
30+
{ label: 'Subscribers', icon: 'users', route: '/subscribers' },
31+
{ label: 'Lists & Segments', icon: 'list', route: '/lists' },
32+
],
33+
},
34+
{
35+
id: 'marketing',
36+
label: 'Marketing',
37+
items: [
38+
{ label: 'Campaigns', icon: 'paper-plane', route: '/campaigns', badge: 3 },
39+
{ label: 'Templates', icon: 'layout', route: '/templates' },
40+
],
1541
},
16-
created() {
17-
console.log('App component created');
42+
{
43+
id: 'analytics',
44+
label: 'Analytics',
45+
items: [
46+
{ label: 'Bounces & Reports', icon: 'chart-bar', route: '/reports' },
47+
],
1848
},
19-
mounted() {
20-
console.log('App component mounted');
49+
{
50+
id: 'system',
51+
label: 'System',
52+
items: [{ label: 'Settings', icon: 'settings', route: '/settings' }],
2153
},
22-
updated() {
23-
console.log('App component updated');
24-
}
25-
}
54+
]
2655
</script>

assets/vue/AppSidebar.vue

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<template>
2+
<aside class="sidebar">
3+
<div class="sidebar__top">
4+
<SidebarLogo />
5+
6+
<nav class="sidebar__nav">
7+
<SidebarNavSection
8+
v-for="section in sections"
9+
:key="section.id"
10+
v-bind="section"
11+
/>
12+
</nav>
13+
</div>
14+
15+
<SidebarCapacityCard class="sidebar__bottom" />
16+
</aside>
17+
</template>
18+
19+
<script setup>
20+
import SidebarLogo from './SidebarLogo.vue'
21+
import SidebarNavSection from './SidebarNavSection.vue'
22+
import SidebarCapacityCard from './SidebarCapacityCard.vue'
23+
24+
const sections = [
25+
{
26+
id: 'general',
27+
label: 'General',
28+
items: [
29+
{ label: 'Dashboard', icon: 'grid', route: '/dashboard', badge: null },
30+
{ label: 'Subscribers', icon: 'users', route: '/subscribers' },
31+
{ label: 'Lists & Segments', icon: 'list', route: '/lists' },
32+
],
33+
},
34+
{
35+
id: 'marketing',
36+
label: 'Marketing',
37+
items: [
38+
{ label: 'Campaigns', icon: 'paper-plane', route: '/campaigns', badge: 3 },
39+
{ label: 'Templates', icon: 'layout', route: '/templates' },
40+
],
41+
},
42+
{
43+
id: 'analytics',
44+
label: 'Analytics',
45+
items: [
46+
{ label: 'Bounces & Reports', icon: 'chart-bar', route: '/reports' },
47+
],
48+
},
49+
{
50+
id: 'system',
51+
label: 'System',
52+
items: [{ label: 'Settings', icon: 'settings', route: '/settings' }],
53+
},
54+
]
55+
</script>

assets/vue/BaseCard.vue

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<template>
2+
<div class="card" :class="[`card--${variant}`]">
3+
<slot />
4+
</div>
5+
</template>
6+
7+
<script setup>
8+
const props = defineProps({
9+
variant: {
10+
type: String,
11+
default: 'default', // default | subtle | danger | ...
12+
},
13+
})
14+
</script>

assets/vue/CampaignsTable.vue

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<template>
2+
<table class="campaigns-table">
3+
<thead>
4+
<tr>
5+
<th>Campaign Name</th>
6+
<th>Status</th>
7+
<th>Date</th>
8+
<th>Open Rate</th>
9+
<th>Click Rate</th>
10+
</tr>
11+
</thead>
12+
13+
<tbody>
14+
<tr
15+
v-for="row in rows"
16+
:key="row.id"
17+
>
18+
<td class="campaigns-table__name">
19+
{{ row.name }}
20+
</td>
21+
22+
<td>
23+
<span
24+
class="campaigns-table__status"
25+
:class="`campaigns-table__status--${row.status.toLowerCase()}`"
26+
>
27+
{{ row.status }}
28+
</span>
29+
</td>
30+
31+
<td>{{ row.date }}</td>
32+
<td>{{ row.openRate ?? '—' }}</td>
33+
<td>{{ row.clickRate ?? '—' }}</td>
34+
</tr>
35+
36+
<tr v-if="!rows.length">
37+
<td colspan="5" class="campaigns-table__empty">
38+
No campaigns yet.
39+
</td>
40+
</tr>
41+
</tbody>
42+
</table>
43+
</template>
44+
45+
<script setup>
46+
const props = defineProps({
47+
rows: {
48+
type: Array,
49+
default: () => [],
50+
/*
51+
rows: [
52+
{
53+
id: 1,
54+
name: 'Monthly Newsletter - June',
55+
status: 'Sent', // 'Sent' | 'Scheduled' | 'Draft' etc.
56+
date: '2024-06-01',
57+
openRate: '24.5%',
58+
clickRate: '3.2%',
59+
}
60+
]
61+
*/
62+
},
63+
})
64+
</script>
65+
66+
<style scoped>
67+
.campaigns-table {
68+
width: 100%;
69+
border-collapse: collapse;
70+
font-size: 0.86rem;
71+
}
72+
73+
thead tr {
74+
border-bottom: 1px solid #e5e7eb;
75+
}
76+
77+
th {
78+
text-align: left;
79+
padding: 0.6rem 1rem 0.6rem 0;
80+
font-weight: 600;
81+
color: #9ca3af;
82+
font-size: 0.75rem;
83+
text-transform: uppercase;
84+
letter-spacing: 0.03em;
85+
}
86+
87+
th:last-child,
88+
td:last-child {
89+
padding-right: 0;
90+
}
91+
92+
tbody tr {
93+
border-bottom: 1px solid #f3f4f6;
94+
}
95+
96+
td {
97+
padding: 0.65rem 1rem 0.65rem 0;
98+
color: #111827;
99+
}
100+
101+
.campaigns-table__name {
102+
font-weight: 500;
103+
color: #111827;
104+
}
105+
106+
.campaigns-table__status {
107+
display: inline-flex;
108+
align-items: center;
109+
padding: 0.15rem 0.55rem;
110+
border-radius: 999px;
111+
font-size: 0.75rem;
112+
font-weight: 600;
113+
}
114+
115+
/* tweak colors to match your palette */
116+
.campaigns-table__status--sent {
117+
background: #ecfdf3;
118+
color: #15803d;
119+
}
120+
121+
.campaigns-table__status--scheduled {
122+
background: #eff6ff;
123+
color: #1d4ed8;
124+
}
125+
126+
.campaigns-table__status--draft {
127+
background: #f9fafb;
128+
color: #4b5563;
129+
}
130+
131+
.campaigns-table__empty {
132+
text-align: center;
133+
padding: 1.2rem 0;
134+
color: #6b7280;
135+
}
136+
</style>

assets/vue/DashboardView.vue

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<template>
2+
<DashboardLayout>
3+
<div class="dashboard">
4+
<KpiGrid class="dashboard__section" />
5+
6+
<section class="dashboard__section dashboard__row">
7+
<PerformanceChartCard
8+
class="dashboard__col--2"
9+
:labels="chart.labels"
10+
:series="chart.series"
11+
/>
12+
<SystemOverviewCard class="dashboard__col--1" />
13+
</section>
14+
15+
<section class="dashboard__section">
16+
<RecentCampaignsCard />
17+
</section>
18+
</div>
19+
</DashboardLayout>
20+
</template>
21+
22+
<script setup>
23+
import DashboardLayout from './layouts/DashboardLayout.vue'
24+
import KpiGrid from '/components/dashboard/KpiGrid.vue'
25+
import PerformanceChartCard from './components/dashboard/PerformanceChartCard.vue'
26+
import SystemOverviewCard from './components/dashboard/SystemOverviewCard.vue'
27+
import RecentCampaignsCard from './components/dashboard/RecentCampaignsCard.vue'
28+
29+
const chart = {
30+
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
31+
series: [
32+
{ name: 'Opens', data: [2500, 2200, 10000, 4000, 4500, 3800] },
33+
{ name: 'Clicks', data: [4200, 3500, 3200, 3000, 3600, 4100] },
34+
],
35+
}
36+
</script>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<template>
2+
<BaseCard class="chart-card">
3+
<header class="chart-card__header">
4+
<h2>Campaign Performance</h2>
5+
<p>Daily opens and clicks for the last 30 days</p>
6+
</header>
7+
<LineChart :series="series" :labels="labels" />
8+
</BaseCard>
9+
</template>
10+
11+
<script setup>
12+
import BaseCard from './components/base/BaseCard.vue'
13+
import LineChart from './components/charts/LineChart.vue'
14+
15+
const props = defineProps({
16+
series: Array,
17+
labels: Array,
18+
})
19+
</script>

assets/vue/RecentCampaignsCard.vue

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<!-- RecentCampaignsCard.vue -->
2+
<template>
3+
<BaseCard>
4+
<header class="card-header">
5+
<h2>Recent Campaigns</h2>
6+
</header>
7+
8+
<CampaignsTable :rows="rows" />
9+
</BaseCard>
10+
</template>
11+
12+
<script setup>
13+
import BaseCard from './components/base/BaseCard.vue'
14+
import CampaignsTable from './CampaignsTable.vue'
15+
16+
const rows = [
17+
{
18+
id: 1,
19+
name: 'Monthly Newsletter - June',
20+
status: 'Sent',
21+
date: '2024-06-01',
22+
openRate: '24.5%',
23+
clickRate: '3.2%',
24+
},
25+
// ...
26+
]
27+
</script>

0 commit comments

Comments
 (0)