monacousa-portal/design-mockups/pages/admin/ProfessionalAdminDashboard.vue

814 lines
20 KiB
Vue

<template>
<div class="admin-dashboard">
<!-- Sidebar Navigation -->
<aside class="sidebar" :class="{ 'sidebar--collapsed': isSidebarCollapsed }">
<div class="sidebar-header">
<div class="sidebar-logo">
<img src="/MONACOUSA-Flags_376x376.png" alt="MonacoUSA" />
<span v-if="!isSidebarCollapsed" class="sidebar-title">Admin Portal</span>
</div>
<button @click="isSidebarCollapsed = !isSidebarCollapsed" class="sidebar-toggle">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
<nav class="sidebar-nav">
<a
v-for="item in navItems"
:key="item.id"
:class="['sidebar-item', { 'sidebar-item--active': activeNav === item.id }]"
@click="activeNav = item.id"
>
<span class="sidebar-item-icon">
<component :is="item.icon" />
</span>
<span v-if="!isSidebarCollapsed" class="sidebar-item-label">{{ item.label }}</span>
<span v-if="!isSidebarCollapsed && item.badge" class="sidebar-item-badge">{{ item.badge }}</span>
</a>
</nav>
<div class="sidebar-footer">
<div class="sidebar-user">
<div class="sidebar-user-avatar">
<img src="https://via.placeholder.com/40" alt="Admin" />
</div>
<div v-if="!isSidebarCollapsed" class="sidebar-user-info">
<div class="sidebar-user-name">John Admin</div>
<div class="sidebar-user-role">System Admin</div>
</div>
</div>
</div>
</aside>
<!-- Main Content -->
<main class="main-content">
<!-- Top Bar -->
<header class="topbar">
<div class="topbar-left">
<h1 class="topbar-title">Dashboard Overview</h1>
<p class="topbar-subtitle">Welcome back, John. Here's what's happening today.</p>
</div>
<div class="topbar-right">
<button class="topbar-button">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</button>
<button class="topbar-button topbar-button--notification">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
<span class="notification-badge">3</span>
</button>
<div class="topbar-divider"></div>
<button class="topbar-user">
<img src="https://via.placeholder.com/32" alt="User" class="topbar-user-avatar" />
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
</div>
</header>
<!-- Stats Grid -->
<div class="stats-grid">
<StatCard
v-for="stat in stats"
:key="stat.title"
:title="stat.title"
:value="stat.value"
:change="stat.change"
:trend="stat.trend"
:icon="stat.icon"
/>
</div>
<!-- Content Grid -->
<div class="content-grid">
<!-- Chart Card -->
<NeumorphicCard class="chart-card">
<template #header>
<div class="card-header">
<h2 class="card-title">Revenue Overview</h2>
<div class="card-actions">
<button class="card-action">Day</button>
<button class="card-action card-action--active">Week</button>
<button class="card-action">Month</button>
<button class="card-action">Year</button>
</div>
</div>
</template>
<div class="chart-container">
<canvas ref="chartCanvas"></canvas>
</div>
</NeumorphicCard>
<!-- Activity Feed -->
<NeumorphicCard class="activity-card">
<template #header>
<div class="card-header">
<h2 class="card-title">Recent Activity</h2>
<button class="card-link">View All</button>
</div>
</template>
<div class="activity-list">
<div v-for="activity in activities" :key="activity.id" class="activity-item">
<div class="activity-icon" :class="`activity-icon--${activity.type}`">
<component :is="activity.icon" />
</div>
<div class="activity-content">
<p class="activity-description">{{ activity.description }}</p>
<span class="activity-time">{{ activity.time }}</span>
</div>
</div>
</div>
</NeumorphicCard>
<!-- Members Table -->
<NeumorphicCard class="table-card">
<template #header>
<div class="card-header">
<h2 class="card-title">Recent Members</h2>
<ProfessionalButton variant="outline" size="sm">
View All Members
</ProfessionalButton>
</div>
</template>
<div class="table-wrapper">
<table class="data-table">
<thead>
<tr>
<th>Member</th>
<th>Status</th>
<th>Joined</th>
<th>Last Active</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="member in recentMembers" :key="member.id">
<td>
<div class="member-cell">
<img :src="member.avatar" :alt="member.name" class="member-avatar" />
<div>
<div class="member-name">{{ member.name }}</div>
<div class="member-email">{{ member.email }}</div>
</div>
</div>
</td>
<td>
<span class="status-badge" :class="`status-badge--${member.status}`">
{{ member.status }}
</span>
</td>
<td>{{ member.joined }}</td>
<td>{{ member.lastActive }}</td>
<td>
<button class="table-action">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" />
</svg>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</NeumorphicCard>
</div>
</main>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import NeumorphicCard from '../../components/core/NeumorphicCard.vue';
import ProfessionalButton from '../../components/core/ProfessionalButton.vue';
import StatCard from '../../components/data/StatCard.vue';
import Chart from 'chart.js/auto';
// Icons (simplified for demo)
const HomeIcon = { template: '<svg fill="currentColor" viewBox="0 0 20 20"><path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/></svg>' };
const UsersIcon = { template: '<svg fill="currentColor" viewBox="0 0 20 20"><path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z"/></svg>' };
const CalendarIcon = { template: '<svg fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd"/></svg>' };
const ChartIcon = { template: '<svg fill="currentColor" viewBox="0 0 20 20"><path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z"/></svg>' };
// Data
const isSidebarCollapsed = ref(false);
const activeNav = ref('dashboard');
const chartCanvas = ref<HTMLCanvasElement | null>(null);
const navItems = [
{ id: 'dashboard', label: 'Dashboard', icon: HomeIcon },
{ id: 'members', label: 'Members', icon: UsersIcon, badge: '127' },
{ id: 'events', label: 'Events', icon: CalendarIcon, badge: '5' },
{ id: 'analytics', label: 'Analytics', icon: ChartIcon },
];
const stats = [
{ title: 'Total Members', value: '1,247', change: '+12%', trend: 'up', icon: UsersIcon },
{ title: 'Active Events', value: '18', change: '+3', trend: 'up', icon: CalendarIcon },
{ title: 'Monthly Revenue', value: '$48,392', change: '+8%', trend: 'up', icon: ChartIcon },
{ title: 'Engagement Rate', value: '87%', change: '-2%', trend: 'down', icon: ChartIcon },
];
const activities = [
{ id: 1, type: 'user', icon: UsersIcon, description: 'New member registration: Sarah Johnson', time: '5 minutes ago' },
{ id: 2, type: 'event', icon: CalendarIcon, description: 'Annual Gala event updated', time: '1 hour ago' },
{ id: 3, type: 'payment', icon: ChartIcon, description: 'Payment received from Michael Brown', time: '2 hours ago' },
{ id: 4, type: 'user', icon: UsersIcon, description: 'Member profile updated: Robert Davis', time: '3 hours ago' },
];
const recentMembers = [
{ id: 1, name: 'Sarah Johnson', email: 'sarah@example.com', avatar: 'https://via.placeholder.com/40', status: 'active', joined: 'Jan 15, 2024', lastActive: '2 hours ago' },
{ id: 2, name: 'Michael Brown', email: 'michael@example.com', avatar: 'https://via.placeholder.com/40', status: 'active', joined: 'Jan 10, 2024', lastActive: '1 day ago' },
{ id: 3, name: 'Emma Wilson', email: 'emma@example.com', avatar: 'https://via.placeholder.com/40', status: 'pending', joined: 'Jan 8, 2024', lastActive: '3 days ago' },
{ id: 4, name: 'James Taylor', email: 'james@example.com', avatar: 'https://via.placeholder.com/40', status: 'inactive', joined: 'Dec 20, 2023', lastActive: '1 week ago' },
];
// Initialize chart
onMounted(() => {
if (chartCanvas.value) {
new Chart(chartCanvas.value, {
type: 'line',
data: {
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
datasets: [{
label: 'Revenue',
data: [12000, 19000, 15000, 25000, 22000, 30000, 28000],
borderColor: '#DC2626',
backgroundColor: 'rgba(220, 38, 38, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
grid: {
display: true,
color: 'rgba(0, 0, 0, 0.05)'
}
},
x: {
grid: {
display: false
}
}
}
}
});
}
});
</script>
<style lang="scss" scoped>
@import '../../styles/neumorphic-system.scss';
.admin-dashboard {
display: flex;
min-height: 100vh;
background: $neutral-100;
}
// Sidebar
.sidebar {
width: $sidebar-width;
background: white;
box-shadow: $shadow-soft-md;
display: flex;
flex-direction: column;
transition: width $transition-base;
&--collapsed {
width: $sidebar-width-collapsed;
}
&-header {
padding: $space-6;
border-bottom: 1px solid $neutral-200;
display: flex;
align-items: center;
justify-content: space-between;
}
&-logo {
display: flex;
align-items: center;
gap: $space-3;
img {
width: 40px;
height: 40px;
border-radius: $radius-lg;
}
}
&-title {
font-weight: $font-semibold;
color: $neutral-800;
white-space: nowrap;
}
&-toggle {
background: none;
border: none;
padding: $space-2;
cursor: pointer;
color: $neutral-600;
border-radius: $radius-md;
transition: all $transition-base;
svg {
width: 20px;
height: 20px;
}
&:hover {
background: $neutral-100;
}
}
&-nav {
flex: 1;
padding: $space-4;
}
&-item {
display: flex;
align-items: center;
gap: $space-3;
padding: $space-3 $space-4;
margin-bottom: $space-2;
border-radius: $radius-lg;
color: $neutral-600;
text-decoration: none;
cursor: pointer;
transition: all $transition-base;
position: relative;
&:hover {
background: $neutral-100;
color: $neutral-800;
}
&--active {
background: linear-gradient(135deg, $primary-500, $primary-600);
color: white;
box-shadow: $shadow-soft-sm;
}
&-icon {
width: 20px;
height: 20px;
flex-shrink: 0;
svg {
width: 100%;
height: 100%;
}
}
&-label {
font-size: $text-sm;
font-weight: $font-medium;
white-space: nowrap;
}
&-badge {
margin-left: auto;
padding: 2px 8px;
background: $primary-100;
color: $primary-700;
border-radius: $radius-full;
font-size: $text-xs;
font-weight: $font-semibold;
}
}
&-footer {
padding: $space-4;
border-top: 1px solid $neutral-200;
}
&-user {
display: flex;
align-items: center;
gap: $space-3;
padding: $space-3;
&-avatar {
width: 40px;
height: 40px;
border-radius: $radius-full;
overflow: hidden;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
&-info {
flex: 1;
min-width: 0;
}
&-name {
font-size: $text-sm;
font-weight: $font-semibold;
color: $neutral-800;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&-role {
font-size: $text-xs;
color: $neutral-500;
}
}
}
// Main Content
.main-content {
flex: 1;
display: flex;
flex-direction: column;
overflow-x: hidden;
}
// Topbar
.topbar {
background: white;
padding: $space-6;
box-shadow: $shadow-soft-sm;
display: flex;
align-items: center;
justify-content: space-between;
&-left {
flex: 1;
}
&-title {
font-size: $text-2xl;
font-weight: $font-bold;
color: $neutral-800;
margin-bottom: $space-1;
}
&-subtitle {
font-size: $text-sm;
color: $neutral-600;
}
&-right {
display: flex;
align-items: center;
gap: $space-3;
}
&-button {
position: relative;
padding: $space-2;
background: white;
border: none;
border-radius: $radius-lg;
cursor: pointer;
color: $neutral-600;
box-shadow: $shadow-soft-sm;
transition: all $transition-base;
svg {
width: 20px;
height: 20px;
}
&:hover {
box-shadow: $shadow-soft-md;
color: $neutral-800;
}
&--notification {
.notification-badge {
position: absolute;
top: -4px;
right: -4px;
width: 18px;
height: 18px;
background: $error-500;
color: white;
border-radius: $radius-full;
font-size: $text-xs;
display: flex;
align-items: center;
justify-content: center;
font-weight: $font-bold;
}
}
}
&-divider {
width: 1px;
height: 24px;
background: $neutral-200;
}
&-user {
display: flex;
align-items: center;
gap: $space-2;
padding: $space-2;
background: white;
border: none;
border-radius: $radius-lg;
cursor: pointer;
box-shadow: $shadow-soft-sm;
transition: all $transition-base;
&:hover {
box-shadow: $shadow-soft-md;
}
&-avatar {
width: 32px;
height: 32px;
border-radius: $radius-full;
}
}
}
// Stats Grid
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: $space-6;
padding: $space-6;
}
// Content Grid
.content-grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: $space-6;
padding: 0 $space-6 $space-6;
@media (max-width: $breakpoint-lg) {
grid-template-columns: 1fr;
}
.chart-card {
grid-column: 1;
grid-row: 1;
}
.activity-card {
grid-column: 2;
grid-row: 1;
}
.table-card {
grid-column: 1 / -1;
}
}
// Card styles
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.card-title {
font-size: $text-lg;
font-weight: $font-semibold;
color: $neutral-800;
}
.card-actions {
display: flex;
gap: $space-2;
}
.card-action {
padding: $space-2 $space-3;
background: transparent;
border: none;
border-radius: $radius-md;
font-size: $text-sm;
color: $neutral-600;
cursor: pointer;
transition: all $transition-base;
&:hover {
background: $neutral-100;
color: $neutral-800;
}
&--active {
background: $primary-500;
color: white;
box-shadow: $shadow-soft-sm;
}
}
.card-link {
background: none;
border: none;
color: $primary-600;
font-size: $text-sm;
font-weight: $font-medium;
cursor: pointer;
&:hover {
color: $primary-700;
text-decoration: underline;
}
}
// Chart
.chart-container {
height: 300px;
padding: $space-4 0;
}
// Activity List
.activity-list {
display: flex;
flex-direction: column;
gap: $space-4;
}
.activity-item {
display: flex;
gap: $space-3;
&-icon {
width: 40px;
height: 40px;
border-radius: $radius-lg;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
svg {
width: 20px;
height: 20px;
}
&--user {
background: $info-100;
color: $info-500;
}
&--event {
background: $warning-100;
color: $warning-500;
}
&--payment {
background: $success-100;
color: $success-500;
}
}
&-content {
flex: 1;
min-width: 0;
}
&-description {
font-size: $text-sm;
color: $neutral-700;
margin-bottom: $space-1;
}
&-time {
font-size: $text-xs;
color: $neutral-500;
}
}
// Table
.table-wrapper {
overflow-x: auto;
}
.data-table {
width: 100%;
thead {
tr {
border-bottom: 2px solid $neutral-200;
}
th {
text-align: left;
padding: $space-3;
font-size: $text-xs;
font-weight: $font-semibold;
text-transform: uppercase;
letter-spacing: 0.05em;
color: $neutral-600;
}
}
tbody {
tr {
border-bottom: 1px solid $neutral-100;
transition: background $transition-base;
&:hover {
background: $neutral-50;
}
}
td {
padding: $space-3;
font-size: $text-sm;
color: $neutral-700;
}
}
}
.member-cell {
display: flex;
align-items: center;
gap: $space-3;
}
.member-avatar {
width: 40px;
height: 40px;
border-radius: $radius-full;
}
.member-name {
font-weight: $font-medium;
color: $neutral-800;
margin-bottom: 2px;
}
.member-email {
font-size: $text-xs;
color: $neutral-500;
}
.status-badge {
display: inline-block;
padding: 4px 12px;
border-radius: $radius-full;
font-size: $text-xs;
font-weight: $font-medium;
&--active {
background: $success-100;
color: $success-500;
}
&--pending {
background: $warning-100;
color: $warning-500;
}
&--inactive {
background: $neutral-100;
color: $neutral-500;
}
}
.table-action {
padding: $space-2;
background: none;
border: none;
cursor: pointer;
color: $neutral-500;
border-radius: $radius-md;
transition: all $transition-base;
&:hover {
background: $neutral-100;
color: $neutral-700;
}
}</style>