2275 lines
55 KiB
Vue
2275 lines
55 KiB
Vue
<template>
|
|
<div class="dashboard-wrapper">
|
|
<!-- Modern Hero Header Section -->
|
|
<div class="dashboard-hero">
|
|
<!-- Geometric Background Pattern -->
|
|
<div class="hero-pattern">
|
|
<div class="pattern-circle circle-1"></div>
|
|
<div class="pattern-circle circle-2"></div>
|
|
<div class="pattern-circle circle-3"></div>
|
|
<div class="pattern-lines">
|
|
<div class="line"></div>
|
|
<div class="line"></div>
|
|
<div class="line"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<v-container fluid class="pa-4 hero-container">
|
|
<v-row align="center" class="hero-row">
|
|
<!-- Left Section: Welcome & Title -->
|
|
<v-col cols="12" md="6" class="hero-left">
|
|
<div class="welcome-section">
|
|
<div class="welcome-badge-new">
|
|
<div class="badge-dot"></div>
|
|
<span class="badge-text-new">DASHBOARD ANTREAN</span>
|
|
</div>
|
|
|
|
<h1 class="hero-title-new">
|
|
Selamat Datang Kembali! 👋
|
|
</h1>
|
|
|
|
<div v-if="user" class="hero-subtitle-new">
|
|
<div class="greeting-row">
|
|
<span class="greeting-text">Halo,</span>
|
|
<span class="user-name-new">{{ user.name || user.preferred_username }}</span>
|
|
</div>
|
|
<span class="subtitle-desc">Berikut adalah ringkasan aktivitas hari ini</span>
|
|
</div>
|
|
|
|
<!-- Quick Info Pills -->
|
|
<div class="quick-info-pills">
|
|
<div class="info-pill">
|
|
<v-icon size="16" color="white">mdi-clock-outline</v-icon>
|
|
<span>Real-time Data</span>
|
|
</div>
|
|
<div class="info-pill">
|
|
<v-icon size="16" color="white">mdi-calendar-today</v-icon>
|
|
<span>{{ currentDate }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</v-col>
|
|
|
|
<!-- Right Section: Actions & Stats -->
|
|
<v-col cols="12" md="6" class="hero-right">
|
|
<div class="hero-actions-new">
|
|
<!-- Date Filter Section -->
|
|
<div class="date-filter-row">
|
|
<div class="date-input-group">
|
|
<label class="date-label">Dari</label>
|
|
<input type="date" v-model="filterDateFrom" class="modern-datepicker" />
|
|
</div>
|
|
<div class="date-input-group">
|
|
<label class="date-label">Hingga</label>
|
|
<input type="date" v-model="filterDateTo" class="modern-datepicker" />
|
|
</div>
|
|
<v-btn
|
|
color="white"
|
|
variant="flat"
|
|
icon="mdi-magnify"
|
|
size="small"
|
|
class="filter-submit-btn"
|
|
@click="applyDateFilter"
|
|
:loading="isLoading"
|
|
></v-btn>
|
|
</div>
|
|
|
|
<div class="action-divider"></div>
|
|
|
|
<!-- Export Button -->
|
|
<v-menu v-if="false" offset-y>
|
|
<template v-slot:activator="{ props }">
|
|
<v-btn
|
|
v-bind="props"
|
|
color="white"
|
|
variant="flat"
|
|
class="modern-btn export-btn-new"
|
|
size="large"
|
|
prepend-icon="mdi-download"
|
|
>
|
|
<span class="btn-text">Export Data</span>
|
|
<v-icon end size="18">mdi-chevron-down</v-icon>
|
|
</v-btn>
|
|
</template>
|
|
<v-list class="export-menu-new">
|
|
<v-list-item
|
|
v-for="(item, index) in exportOptions"
|
|
:key="index"
|
|
@click="handleExport(item.type)"
|
|
class="export-item-new"
|
|
>
|
|
<template v-slot:prepend>
|
|
<v-icon :icon="item.icon" size="20"></v-icon>
|
|
</template>
|
|
<v-list-item-title>{{ item.title }}</v-list-item-title>
|
|
</v-list-item>
|
|
</v-list>
|
|
</v-menu>
|
|
|
|
<!-- Mini Stats Cards in Header -->
|
|
<div class="header-mini-stats">
|
|
<div class="mini-stat-card">
|
|
<div class="mini-stat-icon">
|
|
<v-icon size="20" color="white">mdi-account-multiple</v-icon>
|
|
</div>
|
|
<div class="mini-stat-content">
|
|
<div class="mini-stat-value">{{ stats[0].value }}</div>
|
|
<div class="mini-stat-label">Pasien Hari Ini</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mini-stat-card">
|
|
<div class="mini-stat-icon secondary">
|
|
<v-icon size="20" color="white">mdi-clock-fast</v-icon>
|
|
</div>
|
|
<div class="mini-stat-content">
|
|
<div class="mini-stat-value">{{ stats[1].value }}</div>
|
|
<div class="mini-stat-label">Antrean Aktif</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</v-col>
|
|
</v-row>
|
|
</v-container>
|
|
</div>
|
|
|
|
<!-- Main Content -->
|
|
<v-container fluid class="pa-3 dashboard-bg">
|
|
|
|
<!-- Compact Stats Cards Section -->
|
|
<v-row class="mb-2 stats-row">
|
|
<v-col cols="12" sm="6" lg="3" v-for="(stat, index) in stats" :key="index">
|
|
<v-card
|
|
class="stat-card innovative-card"
|
|
:class="`stat-card-${stat.color}`"
|
|
elevation="0"
|
|
:style="{ animationDelay: `${index * 0.1}s` }"
|
|
>
|
|
<!-- Animated Background Pattern -->
|
|
<div class="card-pattern">
|
|
<div class="pattern-dot"></div>
|
|
<div class="pattern-dot"></div>
|
|
<div class="pattern-dot"></div>
|
|
</div>
|
|
|
|
<!-- Card Content -->
|
|
<div class="stat-card-content">
|
|
<div class="stat-header">
|
|
<div class="stat-icon-wrapper modern-icon" :class="`icon-${stat.color}`">
|
|
<v-icon :size="32" :color="stat.iconColor">{{ stat.icon }}</v-icon>
|
|
<div class="icon-glow"></div>
|
|
</div>
|
|
<div v-if="stat.change" class="stat-badge" :class="stat.changeType">
|
|
<v-icon size="12">{{ stat.changeIcon }}</v-icon>
|
|
<span>{{ stat.change }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-body">
|
|
<div class="stat-value-wrapper">
|
|
<div class="stat-value modern-value">{{ stat.value }}</div>
|
|
<div class="value-sparkline"></div>
|
|
</div>
|
|
<div class="stat-label modern-label">{{ stat.label }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hover Effect Gradient -->
|
|
<div class="hover-gradient"></div>
|
|
|
|
<!-- Progress Bar -->
|
|
<div class="stat-progress">
|
|
<div class="progress-bar" :class="`progress-${stat.color}`"></div>
|
|
</div>
|
|
</v-card>
|
|
</v-col>
|
|
</v-row>
|
|
|
|
<!-- Charts Section -->
|
|
<v-row class="mb-2">
|
|
<!-- Chart 1: Tren Kunjungan Pasien (Area Chart) -->
|
|
<v-col cols="12" lg="8" md="12">
|
|
<v-card class="modern-card chart-card">
|
|
<div class="chart-header-modern">
|
|
<div class="chart-header-left">
|
|
<div class="chart-icon-wrapper chart-icon-primary">
|
|
<v-icon size="small">mdi-chart-timeline-variant</v-icon>
|
|
</div>
|
|
<div>
|
|
<h3 class="chart-title">Tren Kunjungan Pasien</h3>
|
|
<p class="chart-subtitle">Perbandingan kunjungan bulanan</p>
|
|
</div>
|
|
</div>
|
|
<div class="chart-controls">
|
|
<v-btn
|
|
v-if="false"
|
|
icon="mdi-download"
|
|
variant="text"
|
|
size="small"
|
|
color="primary"
|
|
class="chart-download-btn-dark"
|
|
@click="handleChartExport('visitTrend', 'Tren Kunjungan Pasien')"
|
|
density="comfortable"
|
|
></v-btn>
|
|
<v-menu offset-y transition="scale-transition">
|
|
<template v-slot:activator="{ props }">
|
|
<v-chip
|
|
v-bind="props"
|
|
class="live-chip interactive-year-chip"
|
|
size="small"
|
|
>
|
|
<v-icon class="pulse-icon" size="14">mdi-circle</v-icon>
|
|
<span class="ml-1">{{ selectedYear }}</span>
|
|
<v-icon end size="14">mdi-chevron-down</v-icon>
|
|
</v-chip>
|
|
</template>
|
|
<v-list class="year-dropdown-list">
|
|
<v-list-item
|
|
v-for="year in availableYears"
|
|
:key="year"
|
|
@click="selectedYear = year"
|
|
:class="{ 'year-item-active': selectedYear === year }"
|
|
>
|
|
<v-list-item-title class="dropdown-year-text">{{ year }}</v-list-item-title>
|
|
</v-list-item>
|
|
</v-list>
|
|
</v-menu>
|
|
</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<ClientOnly>
|
|
<Line
|
|
v-if="visitTrendData.labels && visitTrendData.labels.length > 0"
|
|
:data="visitTrendData"
|
|
:options="areaChartOptions"
|
|
class="chart-wrapper"
|
|
/>
|
|
<template #fallback>
|
|
<div class="chart-loading">
|
|
<v-progress-circular indeterminate color="primary" size="50"></v-progress-circular>
|
|
<p class="loading-text">Loading chart...</p>
|
|
</div>
|
|
</template>
|
|
</ClientOnly>
|
|
</div>
|
|
</v-card>
|
|
</v-col>
|
|
|
|
<!-- Chart 2: Status Pembayaran (Doughnut Chart) -->
|
|
<v-col cols="12" lg="4" md="6">
|
|
<v-card class="modern-card chart-card">
|
|
<div class="chart-header-modern">
|
|
<div class="chart-header-left">
|
|
<div class="chart-icon-wrapper chart-icon-success">
|
|
<v-icon size="small">mdi-cash-multiple</v-icon>
|
|
</div>
|
|
<div>
|
|
<h3 class="chart-title">Status Pembayaran</h3>
|
|
<p class="chart-subtitle">Distribusi metode bayar</p>
|
|
</div>
|
|
</div>
|
|
<div class="chart-controls">
|
|
<v-btn
|
|
v-if="false"
|
|
icon="mdi-download"
|
|
variant="text"
|
|
size="small"
|
|
color="success"
|
|
class="chart-download-btn-dark"
|
|
@click="handleChartExport('paymentStatus', 'Status Pembayaran')"
|
|
density="comfortable"
|
|
></v-btn>
|
|
</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<ClientOnly>
|
|
<Doughnut
|
|
v-if="paymentStatusData.labels && paymentStatusData.labels.length > 0"
|
|
:data="paymentStatusData"
|
|
:options="doughnutOptions"
|
|
class="chart-wrapper pie-chart"
|
|
/>
|
|
<template #fallback>
|
|
<div class="chart-loading">
|
|
<v-progress-circular indeterminate color="success" size="50"></v-progress-circular>
|
|
<p class="loading-text">Loading chart...</p>
|
|
</div>
|
|
</template>
|
|
</ClientOnly>
|
|
</div>
|
|
</v-card>
|
|
</v-col>
|
|
|
|
<!-- Chart 3: Waktu Tunggu per Poli (Bar Chart) -->
|
|
<v-col cols="12" lg="6" md="6">
|
|
<v-card class="modern-card chart-card">
|
|
<div class="chart-header-modern">
|
|
<div class="chart-header-left">
|
|
<div class="chart-icon-wrapper chart-icon-secondary">
|
|
<v-icon size="small">mdi-clock-outline</v-icon>
|
|
</div>
|
|
<div>
|
|
<h3 class="chart-title">Waktu Tunggu per Poli</h3>
|
|
<p class="chart-subtitle">Rata-rata waktu tunggu (menit)</p>
|
|
</div>
|
|
</div>
|
|
<div class="chart-controls">
|
|
<v-btn
|
|
v-if="false"
|
|
icon="mdi-download"
|
|
variant="text"
|
|
size="small"
|
|
color="secondary"
|
|
class="chart-download-btn-dark"
|
|
@click="handleChartExport('waitingTime', 'Waktu Tunggu per Poli')"
|
|
density="comfortable"
|
|
></v-btn>
|
|
</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<ClientOnly>
|
|
<Bar
|
|
v-if="waitingTimeData.labels && waitingTimeData.labels.length > 0"
|
|
:data="waitingTimeData"
|
|
:options="horizontalBarOptions"
|
|
class="chart-wrapper"
|
|
/>
|
|
<template #fallback>
|
|
<div class="chart-loading">
|
|
<v-progress-circular indeterminate color="secondary" size="50"></v-progress-circular>
|
|
<p class="loading-text">Loading chart...</p>
|
|
</div>
|
|
</template>
|
|
</ClientOnly>
|
|
</div>
|
|
</v-card>
|
|
</v-col>
|
|
|
|
<!-- Chart 4: Tingkat Kehadiran (Polar Area) -->
|
|
<v-col cols="12" lg="6" md="6">
|
|
<v-card class="modern-card chart-card">
|
|
<div class="chart-header-modern">
|
|
<div class="chart-header-left">
|
|
<div class="chart-icon-wrapper chart-icon-primary">
|
|
<v-icon size="small">mdi-account-check</v-icon>
|
|
</div>
|
|
<div>
|
|
<h3 class="chart-title">Tingkat Kehadiran</h3>
|
|
<p class="chart-subtitle">Perbandingan hadir vs tidak hadir</p>
|
|
</div>
|
|
</div>
|
|
<div class="chart-controls">
|
|
<v-btn
|
|
v-if="false"
|
|
icon="mdi-download"
|
|
variant="text"
|
|
size="small"
|
|
color="primary"
|
|
class="chart-download-btn-dark"
|
|
@click="handleChartExport('attendance', 'Tingkat Kehadiran')"
|
|
density="comfortable"
|
|
></v-btn>
|
|
<v-chip class="live-chip" size="small">
|
|
<v-icon class="pulse-icon" size="16">mdi-circle</v-icon>
|
|
Minggu Ini
|
|
</v-chip>
|
|
</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<ClientOnly>
|
|
<PolarArea
|
|
v-if="attendanceData.labels && attendanceData.labels.length > 0"
|
|
:data="attendanceData"
|
|
:options="polarAreaOptions"
|
|
class="chart-wrapper pie-chart"
|
|
/>
|
|
<template #fallback>
|
|
<div class="chart-loading">
|
|
<v-progress-circular indeterminate color="primary" size="50"></v-progress-circular>
|
|
<p class="loading-text">Loading chart...</p>
|
|
</div>
|
|
</template>
|
|
</ClientOnly>
|
|
</div>
|
|
</v-card>
|
|
</v-col>
|
|
</v-row>
|
|
|
|
<v-overlay v-model="isLoading" contained class="align-center justify-center">
|
|
<v-progress-circular indeterminate size="64" color="secondary-600"></v-progress-circular>
|
|
<p class="loading-text">Loading dashboard...</p>
|
|
</v-overlay>
|
|
</v-container>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted } from 'vue';
|
|
import { Bar, Doughnut, Line, PolarArea } from 'vue-chartjs';
|
|
import {
|
|
Chart as ChartJS,
|
|
Title,
|
|
Tooltip,
|
|
Legend,
|
|
BarElement,
|
|
CategoryScale,
|
|
LinearScale,
|
|
ArcElement,
|
|
LineElement,
|
|
PointElement,
|
|
Filler,
|
|
RadialLinearScale
|
|
} from 'chart.js';
|
|
|
|
ChartJS.register(
|
|
Title,
|
|
Tooltip,
|
|
Legend,
|
|
BarElement,
|
|
CategoryScale,
|
|
LinearScale,
|
|
ArcElement,
|
|
LineElement,
|
|
PointElement,
|
|
Filler,
|
|
RadialLinearScale
|
|
);
|
|
|
|
import { useAuth } from '~/composables/useAuth';
|
|
import { useVisitAPI } from '~/composables/useVisitAPI';
|
|
import dayjs from 'dayjs';
|
|
import weekday from 'dayjs/plugin/weekday';
|
|
import weekOfYear from 'dayjs/plugin/weekOfYear';
|
|
import 'dayjs/locale/id';
|
|
|
|
dayjs.extend(weekday);
|
|
dayjs.extend(weekOfYear);
|
|
dayjs.locale('id');
|
|
|
|
definePageMeta({
|
|
middleware:['auth']
|
|
})
|
|
|
|
const user = ref(null);
|
|
const isLoading = ref(false);
|
|
const { checkAuth } = useAuth();
|
|
const { fetchStats } = useVisitAPI();
|
|
|
|
// Color palette from assets/scss/_colors.scss
|
|
const colors = {
|
|
primary: {
|
|
700: '#3556AE',
|
|
600: '#3A61C9',
|
|
500: '#567EE7',
|
|
400: '#7898EC',
|
|
300: '#9AB2F1',
|
|
200: '#BBCBF5',
|
|
},
|
|
secondary: {
|
|
700: '#E65A0D',
|
|
600: '#F16F29',
|
|
500: '#FF8441',
|
|
400: '#FF9D67',
|
|
300: '#FFB58D',
|
|
},
|
|
success: {
|
|
700: '#008D65',
|
|
600: '#33A484',
|
|
500: '#59B59B',
|
|
400: '#80C6B2',
|
|
},
|
|
neutral: {
|
|
100: '#FFFFFF',
|
|
300: '#F5F7FA',
|
|
500: '#CDD4DC',
|
|
700: '#717171',
|
|
900: '#212121',
|
|
},
|
|
};
|
|
|
|
const exportType = ref('JSON');
|
|
const exportOptions = ref([
|
|
{ title: 'JSON (.json)', type: 'json', icon: 'mdi-code-json' },
|
|
{ title: 'CSV (.csv)', type: 'csv', icon: 'mdi-file-delimited' },
|
|
{ title: 'Excel (.xlsx) - Server Only', type: 'excel', icon: 'mdi-file-excel' },
|
|
{ title: 'PDF (.pdf) - Placeholder', type: 'pdf', icon: 'mdi-file-pdf' },
|
|
]);
|
|
|
|
const currentDate = ref('');
|
|
const filterDateFrom = ref(dayjs().format('YYYY-MM-DD'));
|
|
const filterDateTo = ref(dayjs().format('YYYY-MM-DD'));
|
|
const selectedYear = ref(2025);
|
|
const availableYears = ref([2025, 2026, 2027]);
|
|
|
|
// New Stats Data - Updated Metrics
|
|
const stats = ref([
|
|
{
|
|
label: 'Pasien Hari Ini',
|
|
value: '324',
|
|
icon: 'mdi-account-heart',
|
|
iconSize: 24,
|
|
iconColor: 'white',
|
|
color: 'primary',
|
|
change: '+18.2%',
|
|
changeType: 'positive',
|
|
changeIcon: 'mdi-trending-up'
|
|
},
|
|
{
|
|
label: 'Antrean Aktif',
|
|
value: '47',
|
|
icon: 'mdi-clock-fast',
|
|
iconSize: 24,
|
|
iconColor: 'white',
|
|
color: 'secondary',
|
|
change: '+5',
|
|
changeType: 'positive',
|
|
changeIcon: 'mdi-arrow-up'
|
|
},
|
|
{
|
|
label: 'Rata-rata Tunggu',
|
|
value: '12 min',
|
|
icon: 'mdi-timer-outline',
|
|
iconSize: 24,
|
|
iconColor: 'white',
|
|
color: 'success',
|
|
change: '-3 min',
|
|
changeType: 'positive',
|
|
changeIcon: 'mdi-trending-down'
|
|
},
|
|
{
|
|
label: 'Tingkat Kehadiran',
|
|
value: '89.5%',
|
|
icon: 'mdi-check-circle',
|
|
iconSize: 24,
|
|
iconColor: 'white',
|
|
color: 'primary',
|
|
change: '+2.3%',
|
|
changeType: 'positive',
|
|
changeIcon: 'mdi-trending-up'
|
|
}
|
|
]);
|
|
|
|
// New Data: Monthly Visit Trend (Area Chart)
|
|
const visitTrendData = ref({
|
|
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Ags', 'Sep', 'Okt', 'Nov', 'Des'],
|
|
datasets: [
|
|
{
|
|
label: 'Pasien Umum',
|
|
data: [850, 920, 880, 1050, 1120, 1080, 1200, 1150, 1080, 1190, 1250, 1300],
|
|
backgroundColor: 'rgba(86, 126, 231, 0.2)',
|
|
borderColor: colors.primary[500],
|
|
borderWidth: 3,
|
|
fill: true,
|
|
tension: 0.4,
|
|
pointRadius: 4,
|
|
pointHoverRadius: 6,
|
|
pointBackgroundColor: colors.primary[600],
|
|
pointBorderColor: '#fff',
|
|
pointBorderWidth: 2,
|
|
},
|
|
{
|
|
label: 'Pasien BPJS',
|
|
data: [1200, 1350, 1280, 1480, 1550, 1620, 1700, 1650, 1590, 1720, 1800, 1850],
|
|
backgroundColor: 'rgba(255, 132, 65, 0.2)',
|
|
borderColor: colors.secondary[500],
|
|
borderWidth: 3,
|
|
fill: true,
|
|
tension: 0.4,
|
|
pointRadius: 4,
|
|
pointHoverRadius: 6,
|
|
pointBackgroundColor: colors.secondary[600],
|
|
pointBorderColor: '#fff',
|
|
pointBorderWidth: 2,
|
|
}
|
|
]
|
|
});
|
|
|
|
// Payment Status Data (Doughnut Chart)
|
|
const paymentStatusData = ref({
|
|
labels: ['BPJS Kesehatan', 'Umum/Tunai', 'Asuransi Swasta', 'Corporate'],
|
|
datasets: [
|
|
{
|
|
data: [1850, 680, 320, 280],
|
|
backgroundColor: [
|
|
colors.primary[500],
|
|
colors.secondary[500],
|
|
colors.success[500],
|
|
colors.primary[300],
|
|
],
|
|
borderColor: '#fff',
|
|
borderWidth: 3,
|
|
hoverOffset: 15,
|
|
}
|
|
]
|
|
});
|
|
|
|
// Waiting Time per Poli Data (Horizontal Bar Chart)
|
|
const waitingTimeData = ref({
|
|
labels: ['Poli Umum', 'Poli Anak', 'Poli Gigi', 'Poli Mata', 'Poli THT', 'Poli Jantung'],
|
|
datasets: [
|
|
{
|
|
label: 'Waktu Tunggu (menit)',
|
|
data: [15, 12, 8, 18, 10, 22],
|
|
backgroundColor: [
|
|
colors.primary[400],
|
|
colors.secondary[400],
|
|
colors.success[400],
|
|
colors.primary[500],
|
|
colors.secondary[500],
|
|
colors.success[500],
|
|
],
|
|
borderColor: [
|
|
colors.primary[600],
|
|
colors.secondary[600],
|
|
colors.success[600],
|
|
colors.primary[700],
|
|
colors.secondary[700],
|
|
colors.success[700],
|
|
],
|
|
borderWidth: 2,
|
|
borderRadius: 8,
|
|
}
|
|
]
|
|
});
|
|
|
|
// Attendance Data (Polar Area Chart)
|
|
const attendanceData = ref({
|
|
labels: ['Hadir Tepat Waktu', 'Hadir Terlambat', 'Tidak Hadir', 'Batal', 'Reschedule'],
|
|
datasets: [
|
|
{
|
|
data: [1850, 420, 280, 150, 230],
|
|
backgroundColor: [
|
|
colors.success[400],
|
|
colors.primary[400],
|
|
colors.secondary[400],
|
|
colors.neutral[500],
|
|
colors.primary[300],
|
|
],
|
|
borderColor: '#fff',
|
|
borderWidth: 2,
|
|
}
|
|
]
|
|
});
|
|
|
|
// Area Chart Options (Visit Trend)
|
|
const areaChartOptions = ref({
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
animation: {
|
|
duration: 1000,
|
|
easing: 'easeInOutQuart'
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
display: true,
|
|
position: 'top',
|
|
align: 'end',
|
|
labels: {
|
|
usePointStyle: true,
|
|
padding: 12,
|
|
font: {
|
|
size: 11,
|
|
weight: '600',
|
|
family: "'Inter', sans-serif"
|
|
},
|
|
color: colors.neutral[700],
|
|
}
|
|
},
|
|
tooltip: {
|
|
enabled: true,
|
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
|
titleColor: colors.neutral[900],
|
|
bodyColor: colors.neutral[700],
|
|
borderColor: colors.neutral[400],
|
|
borderWidth: 1,
|
|
padding: 12,
|
|
boxPadding: 6,
|
|
usePointStyle: true,
|
|
callbacks: {
|
|
label: function(context) {
|
|
return `${context.dataset.label}: ${context.parsed.y.toLocaleString('id-ID')} pasien`;
|
|
}
|
|
}
|
|
},
|
|
},
|
|
scales: {
|
|
x: {
|
|
grid: {
|
|
display: false,
|
|
},
|
|
ticks: {
|
|
color: colors.neutral[700],
|
|
font: {
|
|
size: 10,
|
|
weight: '500'
|
|
}
|
|
}
|
|
},
|
|
y: {
|
|
beginAtZero: true,
|
|
grid: {
|
|
color: 'rgba(0, 0, 0, 0.05)',
|
|
drawBorder: false,
|
|
},
|
|
ticks: {
|
|
color: colors.neutral[700],
|
|
font: {
|
|
size: 10,
|
|
weight: '600'
|
|
},
|
|
callback: function(value) {
|
|
return value.toLocaleString('id-ID');
|
|
}
|
|
}
|
|
}
|
|
},
|
|
interaction: {
|
|
intersect: false,
|
|
mode: 'index'
|
|
}
|
|
});
|
|
|
|
// Doughnut Options (Payment Status)
|
|
const doughnutOptions = ref({
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
animation: {
|
|
animateRotate: true,
|
|
animateScale: true,
|
|
duration: 1200,
|
|
easing: 'easeInOutQuart'
|
|
},
|
|
cutout: '65%',
|
|
plugins: {
|
|
legend: {
|
|
display: true,
|
|
position: 'bottom',
|
|
labels: {
|
|
usePointStyle: true,
|
|
padding: 12,
|
|
font: {
|
|
size: 10,
|
|
weight: '600',
|
|
family: "'Inter', sans-serif"
|
|
},
|
|
color: colors.neutral[700],
|
|
generateLabels: function(chart) {
|
|
const data = chart.data;
|
|
if (data.labels.length && data.datasets.length) {
|
|
return data.labels.map((label, i) => {
|
|
const value = data.datasets[0].data[i];
|
|
const total = data.datasets[0].data.reduce((a, b) => a + b, 0);
|
|
const percentage = ((value / total) * 100).toFixed(1);
|
|
return {
|
|
text: `${label}: ${percentage}%`,
|
|
fillStyle: data.datasets[0].backgroundColor[i],
|
|
hidden: false,
|
|
index: i
|
|
};
|
|
});
|
|
}
|
|
return [];
|
|
}
|
|
}
|
|
},
|
|
tooltip: {
|
|
enabled: true,
|
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
|
titleColor: colors.neutral[900],
|
|
bodyColor: colors.neutral[700],
|
|
borderColor: colors.neutral[400],
|
|
borderWidth: 1,
|
|
padding: 12,
|
|
boxPadding: 6,
|
|
usePointStyle: true,
|
|
callbacks: {
|
|
label: function(context) {
|
|
const label = context.label || '';
|
|
const value = context.parsed;
|
|
const total = context.dataset.data.reduce((a, b) => a + b, 0);
|
|
const percentage = ((value / total) * 100).toFixed(1);
|
|
return `${label}: ${value.toLocaleString('id-ID')} (${percentage}%)`;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Horizontal Bar Options (Waiting Time)
|
|
const horizontalBarOptions = ref({
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
indexAxis: 'y',
|
|
animation: {
|
|
duration: 1000,
|
|
easing: 'easeInOutQuart'
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
display: false
|
|
},
|
|
tooltip: {
|
|
enabled: true,
|
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
|
titleColor: colors.neutral[900],
|
|
bodyColor: colors.neutral[700],
|
|
borderColor: colors.neutral[400],
|
|
borderWidth: 1,
|
|
padding: 12,
|
|
boxPadding: 6,
|
|
usePointStyle: true,
|
|
callbacks: {
|
|
label: function(context) {
|
|
return `Waktu tunggu: ${context.parsed.x} menit`;
|
|
}
|
|
}
|
|
},
|
|
},
|
|
scales: {
|
|
x: {
|
|
beginAtZero: true,
|
|
grid: {
|
|
color: 'rgba(0, 0, 0, 0.05)',
|
|
drawBorder: false,
|
|
},
|
|
ticks: {
|
|
color: colors.neutral[700],
|
|
font: {
|
|
size: 10,
|
|
weight: '600'
|
|
},
|
|
callback: function(value) {
|
|
return value + ' min';
|
|
}
|
|
}
|
|
},
|
|
y: {
|
|
grid: {
|
|
display: false,
|
|
},
|
|
ticks: {
|
|
color: colors.neutral[700],
|
|
font: {
|
|
size: 10,
|
|
weight: '500'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Polar Area Options (Attendance)
|
|
const polarAreaOptions = ref({
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
animation: {
|
|
animateRotate: true,
|
|
animateScale: true,
|
|
duration: 1200,
|
|
easing: 'easeInOutQuart'
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
display: true,
|
|
position: 'bottom',
|
|
labels: {
|
|
usePointStyle: true,
|
|
padding: 10,
|
|
font: {
|
|
size: 10,
|
|
weight: '600',
|
|
family: "'Inter', sans-serif"
|
|
},
|
|
color: colors.neutral[700],
|
|
}
|
|
},
|
|
tooltip: {
|
|
enabled: true,
|
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
|
titleColor: colors.neutral[900],
|
|
bodyColor: colors.neutral[700],
|
|
borderColor: colors.neutral[400],
|
|
borderWidth: 1,
|
|
padding: 12,
|
|
boxPadding: 6,
|
|
usePointStyle: true,
|
|
callbacks: {
|
|
label: function(context) {
|
|
const label = context.label || '';
|
|
const value = context.parsed.r;
|
|
const total = context.dataset.data.reduce((a, b) => a + b, 0);
|
|
const percentage = ((value / total) * 100).toFixed(1);
|
|
return `${label}: ${value.toLocaleString('id-ID')} (${percentage}%)`;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
r: {
|
|
beginAtZero: true,
|
|
grid: {
|
|
color: 'rgba(0, 0, 0, 0.05)',
|
|
},
|
|
ticks: {
|
|
color: colors.neutral[700],
|
|
font: {
|
|
size: 9,
|
|
weight: '500'
|
|
},
|
|
backdropColor: 'transparent'
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
const refreshDashboardStats = async (filterParams = {}) => {
|
|
isLoading.value = true;
|
|
try {
|
|
// Merge provided filters with UI dates if not explicitly provided
|
|
const params = {
|
|
from: filterDateFrom.value,
|
|
to: filterDateTo.value,
|
|
...filterParams
|
|
};
|
|
|
|
const data = await fetchStats(params);
|
|
console.log('📡 API Response Data:', data);
|
|
if (data) {
|
|
// 1. Update Stats Cards
|
|
stats.value[0].value = data.today_patients.toString();
|
|
|
|
const activeQueuesTotal = Object.values(data.today_active_queues_by_service).reduce((sum, val) => sum + val, 0);
|
|
stats.value[1].value = activeQueuesTotal.toString();
|
|
|
|
const waitMinutes = Math.floor(data.average_waiting_seconds / 60);
|
|
stats.value[2].value = `${waitMinutes} min`;
|
|
|
|
// 2. Update Payment Status Chart
|
|
if (data.total_by_payment_type) {
|
|
const labelMap = {
|
|
'JKN': 'JKN / BPJS',
|
|
'UMUM': 'Umum / Mandiri',
|
|
'UNKNOWN': 'Lainnya'
|
|
};
|
|
|
|
const labels = Object.keys(data.total_by_payment_type).map(key => labelMap[key] || key);
|
|
const values = Object.values(data.total_by_payment_type);
|
|
|
|
paymentStatusData.value = {
|
|
labels: labels,
|
|
datasets: [{
|
|
...paymentStatusData.value.datasets[0],
|
|
data: values,
|
|
backgroundColor: [
|
|
colors.primary[500],
|
|
colors.secondary[500],
|
|
colors.success[500],
|
|
colors.primary[300],
|
|
colors.secondary[300]
|
|
]
|
|
}]
|
|
};
|
|
}
|
|
|
|
// 3. Update Waiting Time by Service Chart
|
|
if (data.average_waiting_by_service_seconds && Object.keys(data.average_waiting_by_service_seconds).length > 0) {
|
|
waitingTimeData.value = {
|
|
labels: Object.keys(data.average_waiting_by_service_seconds).map(key => key.split('|')[0]),
|
|
datasets: [{
|
|
...waitingTimeData.value.datasets[0],
|
|
data: Object.values(data.average_waiting_by_service_seconds).map(sec => Math.round(sec / 60))
|
|
}]
|
|
};
|
|
}
|
|
|
|
// 4. Update Monthly Trend (Map API into datasets)
|
|
if (data.monthly_trend && data.monthly_trend.length > 0) {
|
|
// Logic to update trend charts can be expanded here
|
|
// For now, we'll keep the existing structure but show where real data fits
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('❌ Error refreshing stats:', error);
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
};
|
|
|
|
const applyDateFilter = () => {
|
|
refreshDashboardStats();
|
|
};
|
|
|
|
onMounted(async () => {
|
|
console.log('📊 Dashboard mounted');
|
|
try {
|
|
const sessionUser = await checkAuth();
|
|
if (sessionUser) {
|
|
user.value = sessionUser;
|
|
currentDate.value = dayjs().format('dddd, DD MMMM YYYY');
|
|
|
|
// Load real-time stats
|
|
await refreshDashboardStats();
|
|
console.log('✅ Dashboard loaded successfully');
|
|
}
|
|
} catch (error) {
|
|
console.error('❌ Auth check error:', error);
|
|
}
|
|
});
|
|
|
|
const handleExport = (type) => {
|
|
// 1. Prepare Export Data
|
|
const exportData = {
|
|
metadata: {
|
|
report_name: 'Dashboard Antrean Statistics',
|
|
generated_at: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
|
period_from: filterDateFrom.value,
|
|
period_to: filterDateTo.value,
|
|
user: user.value?.name || user.value?.preferred_username || 'System'
|
|
},
|
|
summary_stats: stats.value.map(s => ({
|
|
label: s.label,
|
|
value: s.value
|
|
})),
|
|
payment_distribution: paymentStatusData.value.labels.map((label, i) => ({
|
|
method: label,
|
|
total: paymentStatusData.value.datasets[0].data[i]
|
|
})),
|
|
waiting_time_by_service: waitingTimeData.value.labels.map((label, i) => ({
|
|
service: label,
|
|
average_minutes: waitingTimeData.value.datasets[0].data[i]
|
|
}))
|
|
};
|
|
|
|
// 2. Process Export based on type
|
|
if (type === 'json') {
|
|
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
|
|
downloadBlob(blob, `report_antrean_${dayjs().format('YYYYMMDD_HHmm')}.json`);
|
|
} else if (type === 'csv') {
|
|
// Generate CSV String
|
|
let csv = 'Dashboard Antrean Statistics\n';
|
|
csv += `Periode: ${filterDateFrom.value} - ${filterDateTo.value}\n\n`;
|
|
|
|
csv += 'RINGKASAN STATISTIK\n';
|
|
csv += 'Metrik,Nilai\n';
|
|
exportData.summary_stats.forEach(s => {
|
|
csv += `"${s.label}","${s.value}"\n`;
|
|
});
|
|
|
|
csv += '\nSTATUS PEMBAYARAN\n';
|
|
csv += 'Metode,Jumlah\n';
|
|
exportData.payment_distribution.forEach(p => {
|
|
csv += `"${p.method}",${p.total}\n`;
|
|
});
|
|
|
|
csv += '\nWAKTU TUNGGU PER POLI\n';
|
|
csv += 'Layanan,Rata-rata (Menit)\n';
|
|
exportData.waiting_time_by_service.forEach(w => {
|
|
csv += `"${w.service}",${w.average_minutes}\n`;
|
|
});
|
|
|
|
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
|
downloadBlob(blob, `report_antrean_${dayjs().format('YYYYMMDD_HHmm')}.csv`);
|
|
} else {
|
|
alert(`Export ${type} - Feature coming soon!`);
|
|
}
|
|
};
|
|
|
|
const handleChartExport = (chartId, title) => {
|
|
let csv = `${title}\n`;
|
|
csv += `Periode: ${filterDateFrom.value} - ${filterDateTo.value}\n\n`;
|
|
|
|
if (chartId === 'visitTrend') {
|
|
csv += 'Bulan,' + visitTrendData.value.datasets.map(d => d.label).join(',') + '\n';
|
|
visitTrendData.value.labels.forEach((label, i) => {
|
|
csv += `"${label}",` + visitTrendData.value.datasets.map(d => d.data[i]).join(',') + '\n';
|
|
});
|
|
} else if (chartId === 'paymentStatus') {
|
|
csv += 'Metode Pembayaran,Jumlah\n';
|
|
paymentStatusData.value.labels.forEach((label, i) => {
|
|
csv += `"${label}",${paymentStatusData.value.datasets[0].data[i]}\n`;
|
|
});
|
|
} else if (chartId === 'waitingTime') {
|
|
csv += 'Layanan,Rata-rata Waktu Tunggu (Menit)\n';
|
|
waitingTimeData.value.labels.forEach((label, i) => {
|
|
csv += `"${label}",${waitingTimeData.value.datasets[0].data[i]}\n`;
|
|
});
|
|
} else if (chartId === 'attendance') {
|
|
csv += 'Status Kehadiran,Jumlah\n';
|
|
attendanceData.value.labels.forEach((label, i) => {
|
|
csv += `"${label}",${attendanceData.value.datasets[0].data[i]}\n`;
|
|
});
|
|
}
|
|
|
|
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
|
downloadBlob(blob, `${chartId}_${dayjs().format('YYYYMMDD_HHmm')}.csv`);
|
|
};
|
|
|
|
// Helper function for file download
|
|
const downloadBlob = (blob, filename) => {
|
|
const url = window.URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.setAttribute('download', filename);
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
window.URL.revokeObjectURL(url);
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
@import '@/assets/scss/_colors.scss';
|
|
@import '@/assets/scss/_variables.scss';
|
|
|
|
.dashboard-wrapper {
|
|
min-height: 100vh;
|
|
background: var(--color-neutral-300);
|
|
}
|
|
|
|
/* Modern Hero Header Section */
|
|
.dashboard-hero {
|
|
background: linear-gradient(135deg, var(--color-primary-600) 0%, var(--color-primary-700) 100%);
|
|
position: relative;
|
|
overflow: hidden;
|
|
min-height: 180px;
|
|
box-shadow: 0 4px 24px rgba(58, 97, 201, 0.2);
|
|
|
|
/* Geometric Pattern Background */
|
|
.hero-pattern {
|
|
position: absolute;
|
|
inset: 0;
|
|
overflow: hidden;
|
|
opacity: 0.1;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.pattern-circle {
|
|
position: absolute;
|
|
border-radius: 50%;
|
|
border: 2px solid white;
|
|
|
|
&.circle-1 {
|
|
width: 400px;
|
|
height: 400px;
|
|
top: -200px;
|
|
right: -100px;
|
|
animation: rotate-slow 30s linear infinite;
|
|
}
|
|
|
|
&.circle-2 {
|
|
width: 250px;
|
|
height: 250px;
|
|
bottom: -100px;
|
|
left: -50px;
|
|
animation: rotate-slow 25s linear infinite reverse;
|
|
}
|
|
|
|
&.circle-3 {
|
|
width: 150px;
|
|
height: 150px;
|
|
top: 50%;
|
|
left: 40%;
|
|
animation: pulse-circle 4s ease-in-out infinite;
|
|
}
|
|
}
|
|
|
|
.pattern-lines {
|
|
position: absolute;
|
|
inset: 0;
|
|
|
|
.line {
|
|
position: absolute;
|
|
height: 1px;
|
|
background: linear-gradient(90deg, transparent, white, transparent);
|
|
width: 100%;
|
|
|
|
&:nth-child(1) {
|
|
top: 20%;
|
|
animation: slide-line 8s linear infinite;
|
|
}
|
|
|
|
&:nth-child(2) {
|
|
top: 50%;
|
|
animation: slide-line 10s linear infinite;
|
|
}
|
|
|
|
&:nth-child(3) {
|
|
top: 80%;
|
|
animation: slide-line 12s linear infinite;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@keyframes rotate-slow {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
@keyframes pulse-circle {
|
|
0%, 100% { transform: scale(1); opacity: 0.3; }
|
|
50% { transform: scale(1.2); opacity: 0.6; }
|
|
}
|
|
|
|
@keyframes slide-line {
|
|
0% { transform: translateX(-100%); }
|
|
100% { transform: translateX(100%); }
|
|
}
|
|
|
|
.hero-container {
|
|
position: relative;
|
|
z-index: 2;
|
|
}
|
|
|
|
.hero-row {
|
|
min-height: 180px;
|
|
}
|
|
|
|
/* Left Section */
|
|
.hero-left {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
}
|
|
|
|
.welcome-section {
|
|
.welcome-badge-new {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
background: rgba(255, 255, 255, 0.15);
|
|
backdrop-filter: blur(10px);
|
|
padding: 6px 16px;
|
|
border-radius: 50px;
|
|
margin-bottom: 12px;
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
|
|
.badge-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
background: var(--color-secondary-500);
|
|
border-radius: 50%;
|
|
animation: pulse-dot 2s ease-in-out infinite;
|
|
}
|
|
|
|
.badge-text-new {
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
color: white;
|
|
letter-spacing: 1.2px;
|
|
text-transform: uppercase;
|
|
}
|
|
}
|
|
}
|
|
|
|
@keyframes pulse-dot {
|
|
0%, 100% { transform: scale(1); opacity: 1; }
|
|
50% { transform: scale(1.3); opacity: 0.7; }
|
|
}
|
|
|
|
.hero-title-new {
|
|
font-size: 36px;
|
|
font-weight: 900;
|
|
color: white;
|
|
margin: 0 0 12px 0;
|
|
line-height: 1.2;
|
|
letter-spacing: -0.5px;
|
|
text-shadow: 0 2px 16px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.hero-subtitle-new {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
margin: 0 0 16px 0;
|
|
|
|
.greeting-row {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 8px;
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.greeting-text {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: rgba(255, 255, 255, 0.8);
|
|
}
|
|
|
|
.user-name-new {
|
|
font-size: 22px;
|
|
font-weight: 800;
|
|
color: white;
|
|
text-shadow: 0 2px 12px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
.subtitle-desc {
|
|
font-size: 13px;
|
|
font-weight: 400;
|
|
color: rgba(255, 255, 255, 0.7);
|
|
margin-top: 4px;
|
|
}
|
|
}
|
|
|
|
.quick-info-pills {
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
|
|
.info-pill {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
background: rgba(255, 255, 255, 0.12);
|
|
backdrop-filter: blur(8px);
|
|
padding: 6px 12px;
|
|
border-radius: 20px;
|
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
|
|
span {
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
color: white;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Right Section */
|
|
.hero-right {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.hero-actions-new {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
width: 100%;
|
|
max-width: 450px;
|
|
}
|
|
|
|
.date-filter-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
backdrop-filter: blur(10px);
|
|
padding: 10px 16px;
|
|
border-radius: 16px;
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.date-input-group {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
|
|
.date-label {
|
|
font-size: 10px;
|
|
font-weight: 700;
|
|
color: rgba(255, 255, 255, 0.9);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.modern-datepicker {
|
|
background: transparent;
|
|
border: none;
|
|
color: white;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
outline: none;
|
|
width: 100%;
|
|
|
|
&::-webkit-calendar-picker-indicator {
|
|
filter: invert(1);
|
|
cursor: pointer;
|
|
opacity: 0.7;
|
|
transition: opacity 0.2s;
|
|
|
|
&:hover {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.filter-submit-btn {
|
|
background: white !important;
|
|
color: var(--color-primary-600) !important;
|
|
border-radius: 10px !important;
|
|
transition: all 0.3s ease !important;
|
|
|
|
&:hover {
|
|
transform: scale(1.1);
|
|
box-shadow: 0 4px 12px rgba(255, 255, 255, 0.3) !important;
|
|
}
|
|
}
|
|
|
|
.action-divider {
|
|
height: 1px;
|
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
|
margin: 4px 0;
|
|
}
|
|
|
|
.modern-btn {
|
|
text-transform: none;
|
|
font-weight: 700;
|
|
border-radius: 12px;
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
|
transition: all 0.3s ease;
|
|
|
|
&:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.2);
|
|
}
|
|
}
|
|
|
|
.export-btn-new {
|
|
padding: 12px 20px !important;
|
|
height: 48px !important;
|
|
|
|
.btn-text {
|
|
font-size: 14px;
|
|
letter-spacing: 0.3px;
|
|
}
|
|
}
|
|
|
|
.export-menu-new {
|
|
border-radius: 12px;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
|
|
|
.export-item-new {
|
|
padding: 12px 16px;
|
|
transition: all 0.2s ease;
|
|
|
|
&:hover {
|
|
background: var(--color-primary-50);
|
|
}
|
|
}
|
|
}
|
|
|
|
.header-mini-stats {
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
|
|
.mini-stat-card {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
background: rgba(255, 255, 255, 0.12);
|
|
backdrop-filter: blur(20px);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
border-radius: 12px;
|
|
padding: 12px 14px;
|
|
transition: all 0.3s ease;
|
|
|
|
&:hover {
|
|
background: rgba(255, 255, 255, 0.18);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
|
}
|
|
}
|
|
|
|
.mini-stat-icon {
|
|
width: 36px;
|
|
height: 36px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: var(--color-primary-500);
|
|
border-radius: 10px;
|
|
flex-shrink: 0;
|
|
box-shadow: 0 4px 12px rgba(86, 126, 231, 0.3);
|
|
|
|
&.secondary {
|
|
background: var(--color-secondary-500);
|
|
box-shadow: 0 4px 12px rgba(255, 132, 65, 0.3);
|
|
}
|
|
}
|
|
|
|
.mini-stat-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
|
|
.mini-stat-value {
|
|
font-size: 20px;
|
|
font-weight: 900;
|
|
color: white;
|
|
line-height: 1;
|
|
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.mini-stat-label {
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
color: rgba(255, 255, 255, 0.8);
|
|
line-height: 1.2;
|
|
}
|
|
}
|
|
|
|
/* Compact Background */
|
|
.dashboard-bg {
|
|
background: var(--color-neutral-300);
|
|
padding-top: 12px !important;
|
|
padding-bottom: 12px !important;
|
|
min-height: auto;
|
|
position: relative;
|
|
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-image:
|
|
radial-gradient(circle at 20% 50%, rgba(58, 97, 201, 0.02) 0%, transparent 50%),
|
|
radial-gradient(circle at 80% 80%, rgba(241, 111, 41, 0.02) 0%, transparent 50%);
|
|
pointer-events: none;
|
|
}
|
|
}
|
|
|
|
/* Compact Stats Cards */
|
|
.stats-row {
|
|
margin-top: -20px;
|
|
margin-bottom: 12px !important;
|
|
position: relative;
|
|
z-index: 3;
|
|
|
|
:deep(.v-col) {
|
|
padding: 6px !important;
|
|
display: flex;
|
|
}
|
|
}
|
|
|
|
.innovative-card {
|
|
background:
|
|
linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(255, 255, 255, 0.7) 100%);
|
|
backdrop-filter: blur(20px);
|
|
border: 1.5px solid var(--color-primary-200);
|
|
border-radius: 24px;
|
|
box-shadow:
|
|
0 8px 32px rgba(0, 0, 0, 0.08),
|
|
inset 0 1px 0 rgba(255, 255, 255, 0.9);
|
|
overflow: hidden;
|
|
position: relative;
|
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
animation: fadeIn 0.6s ease-out;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
&:hover {
|
|
transform: translateY(-4px) scale(1.01);
|
|
box-shadow:
|
|
0 20px 40px rgba(0, 0, 0, 0.12),
|
|
inset 0 1px 0 rgba(255, 255, 255, 1);
|
|
border-color: rgba(255, 255, 255, 1);
|
|
}
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(20px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.card-pattern {
|
|
position: absolute;
|
|
inset: 0;
|
|
opacity: 0.03;
|
|
pointer-events: none;
|
|
|
|
.pattern-dot {
|
|
position: absolute;
|
|
width: 6px;
|
|
height: 6px;
|
|
background: var(--color-primary-600);
|
|
border-radius: 50%;
|
|
animation: pulse-dot 3s ease-in-out infinite;
|
|
|
|
&:nth-child(1) {
|
|
top: 20%;
|
|
left: 15%;
|
|
animation-delay: 0s;
|
|
}
|
|
|
|
&:nth-child(2) {
|
|
top: 60%;
|
|
right: 20%;
|
|
animation-delay: 1s;
|
|
}
|
|
|
|
&:nth-child(3) {
|
|
bottom: 25%;
|
|
left: 50%;
|
|
animation-delay: 2s;
|
|
}
|
|
}
|
|
}
|
|
|
|
@keyframes pulse-dot {
|
|
0%, 100% {
|
|
transform: scale(1);
|
|
opacity: 0.3;
|
|
}
|
|
50% {
|
|
transform: scale(1.5);
|
|
opacity: 0.6;
|
|
}
|
|
}
|
|
|
|
/* Compact Stat Card Layout */
|
|
.stat-card-content {
|
|
padding: 16px;
|
|
position: relative;
|
|
z-index: 2;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
min-height: 130px;
|
|
height: 100%;
|
|
flex: 1;
|
|
}
|
|
|
|
.stat-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.modern-icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 14px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
position: relative;
|
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
|
.icon-glow {
|
|
position: absolute;
|
|
inset: -6px;
|
|
background: currentColor;
|
|
border-radius: 16px;
|
|
opacity: 0;
|
|
filter: blur(16px);
|
|
transition: all 0.4s ease;
|
|
}
|
|
|
|
:deep(.v-icon) {
|
|
font-size: 24px !important;
|
|
}
|
|
}
|
|
|
|
.innovative-card:hover .modern-icon {
|
|
transform: scale(1.1) rotate(-5deg);
|
|
|
|
.icon-glow {
|
|
opacity: 0.4;
|
|
}
|
|
}
|
|
|
|
.stat-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 3px;
|
|
padding: 4px 8px;
|
|
border-radius: 50px;
|
|
font-size: 10px;
|
|
font-weight: 700;
|
|
letter-spacing: 0.3px;
|
|
|
|
&.positive {
|
|
background: var(--color-success-100);
|
|
color: var(--color-success-700);
|
|
box-shadow: 0 2px 8px rgba(51, 164, 132, 0.2);
|
|
}
|
|
}
|
|
|
|
.stat-body {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.stat-value-wrapper {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 8px;
|
|
}
|
|
|
|
.modern-value {
|
|
font-size: 28px;
|
|
font-weight: 900;
|
|
color: var(--color-neutral-900);
|
|
line-height: 1;
|
|
font-family: 'Inter', sans-serif;
|
|
letter-spacing: -0.5px;
|
|
}
|
|
|
|
.modern-label {
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
color: var(--color-neutral-700);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.8px;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.icon-primary {
|
|
background: linear-gradient(135deg, var(--color-primary-500) 0%, var(--color-primary-600) 100%);
|
|
color: white;
|
|
box-shadow: 0 4px 16px rgba(58, 97, 201, 0.3);
|
|
}
|
|
|
|
.icon-secondary {
|
|
background: linear-gradient(135deg, var(--color-secondary-500) 0%, var(--color-secondary-600) 100%);
|
|
color: white;
|
|
box-shadow: 0 4px 16px rgba(241, 111, 41, 0.3);
|
|
}
|
|
|
|
.icon-success {
|
|
background: linear-gradient(135deg, var(--color-success-500) 0%, var(--color-success-600) 100%);
|
|
color: white;
|
|
box-shadow: 0 4px 16px rgba(51, 164, 132, 0.3);
|
|
}
|
|
|
|
.hover-gradient {
|
|
position: absolute;
|
|
inset: 0;
|
|
background: linear-gradient(135deg, transparent 0%, rgba(86, 126, 231, 0.05) 100%);
|
|
opacity: 0;
|
|
transition: opacity 0.4s ease;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.innovative-card:hover .hover-gradient {
|
|
opacity: 1;
|
|
}
|
|
|
|
.stat-progress {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 3px;
|
|
background: rgba(0, 0, 0, 0.05);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.progress-bar {
|
|
height: 100%;
|
|
width: 0;
|
|
transition: width 1.5s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
|
&.progress-primary {
|
|
background: linear-gradient(90deg, var(--color-primary-500) 0%, var(--color-primary-600) 100%);
|
|
}
|
|
|
|
&.progress-secondary {
|
|
background: linear-gradient(90deg, var(--color-secondary-500) 0%, var(--color-secondary-600) 100%);
|
|
}
|
|
|
|
&.progress-success {
|
|
background: linear-gradient(90deg, var(--color-success-500) 0%, var(--color-success-600) 100%);
|
|
}
|
|
}
|
|
|
|
.innovative-card:hover .progress-bar {
|
|
width: 100%;
|
|
}
|
|
|
|
/* Compact Chart Cards */
|
|
.v-row:not(.stats-row) {
|
|
:deep(.v-col) {
|
|
padding: 6px !important;
|
|
display: flex;
|
|
}
|
|
}
|
|
|
|
.modern-card {
|
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.85) 100%);
|
|
backdrop-filter: blur(20px);
|
|
border-radius: 24px;
|
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
box-shadow:
|
|
0 10px 40px rgba(0, 0, 0, 0.08),
|
|
0 0 0 1px rgba(255, 255, 255, 0.5) inset;
|
|
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
|
overflow: hidden;
|
|
position: relative;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 4px;
|
|
background: linear-gradient(90deg, var(--color-primary-500) 0%, var(--color-primary-600) 50%, var(--color-secondary-500) 100%);
|
|
opacity: 0;
|
|
transition: opacity 0.4s ease;
|
|
}
|
|
|
|
&::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: -50%;
|
|
left: -50%;
|
|
width: 200%;
|
|
height: 200%;
|
|
background: radial-gradient(circle, rgba(58, 97, 201, 0.05) 0%, transparent 70%);
|
|
opacity: 0;
|
|
transition: opacity 0.4s ease;
|
|
}
|
|
|
|
&:hover {
|
|
box-shadow:
|
|
0 20px 60px rgba(0, 0, 0, 0.15),
|
|
0 0 0 1px rgba(255, 255, 255, 0.8) inset;
|
|
border-color: rgba(255, 255, 255, 0.5);
|
|
transform: translateY(-4px);
|
|
|
|
&::before {
|
|
opacity: 1;
|
|
}
|
|
|
|
&::after {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
.chart-card {
|
|
height: 320px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.interactive-year-chip {
|
|
cursor: pointer;
|
|
transition: all 0.3s ease !important;
|
|
|
|
&:hover {
|
|
transform: scale(1.05);
|
|
box-shadow: 0 4px 15px rgba(51, 164, 132, 0.4) !important;
|
|
}
|
|
}
|
|
|
|
.year-dropdown-list {
|
|
background: rgba(255, 255, 255, 0.9) !important;
|
|
backdrop-filter: blur(10px);
|
|
border-radius: 12px !important;
|
|
padding: 4px !important;
|
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1) !important;
|
|
}
|
|
|
|
.year-item-active {
|
|
background: var(--color-success-50) !important;
|
|
color: var(--color-success-600) !important;
|
|
|
|
.dropdown-year-text {
|
|
font-weight: 700;
|
|
}
|
|
}
|
|
|
|
.dropdown-year-text {
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
text-align: center;
|
|
}
|
|
|
|
.chart-download-btn-dark {
|
|
opacity: 0.7 !important;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
|
|
|
&:hover {
|
|
opacity: 1 !important;
|
|
transform: scale(1.15) !important;
|
|
background: rgba(0, 0, 0, 0.05) !important;
|
|
}
|
|
}
|
|
|
|
.chart-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.chart-header-modern {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 14px 18px;
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.5) 0%, rgba(245, 247, 250, 0.3) 100%);
|
|
position: relative;
|
|
z-index: 1;
|
|
|
|
&::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: -1px;
|
|
left: 18px;
|
|
right: 18px;
|
|
height: 2px;
|
|
background: linear-gradient(90deg, var(--color-primary-500) 0%, var(--color-primary-600) 50%, var(--color-secondary-500) 100%);
|
|
opacity: 0.5;
|
|
border-radius: 2px;
|
|
}
|
|
}
|
|
|
|
.chart-header-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.chart-icon-wrapper {
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
position: relative;
|
|
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: -3px;
|
|
border-radius: 14px;
|
|
background: inherit;
|
|
opacity: 0;
|
|
filter: blur(10px);
|
|
transition: opacity 0.4s ease;
|
|
}
|
|
|
|
:deep(.v-icon) {
|
|
font-size: 18px !important;
|
|
}
|
|
}
|
|
|
|
.chart-header-modern:hover .chart-icon-wrapper {
|
|
transform: scale(1.08) rotate(5deg);
|
|
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.15);
|
|
|
|
&::before {
|
|
opacity: 0.5;
|
|
}
|
|
}
|
|
|
|
.chart-icon-primary {
|
|
background: linear-gradient(135deg, var(--color-primary-500) 0%, var(--color-primary-600) 100%);
|
|
color: white;
|
|
}
|
|
|
|
.chart-icon-secondary {
|
|
background: linear-gradient(135deg, var(--color-secondary-500) 0%, var(--color-secondary-600) 100%);
|
|
color: white;
|
|
}
|
|
|
|
.chart-icon-success {
|
|
background: linear-gradient(135deg, var(--color-success-500) 0%, var(--color-success-600) 100%);
|
|
color: white;
|
|
}
|
|
|
|
.chart-title {
|
|
font-size: 15px;
|
|
font-weight: 700;
|
|
color: var(--color-neutral-900);
|
|
margin: 0;
|
|
line-height: 1.3;
|
|
letter-spacing: -0.3px;
|
|
}
|
|
|
|
.chart-subtitle {
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
color: var(--color-neutral-600);
|
|
margin: 3px 0 0 0;
|
|
opacity: 0.8;
|
|
letter-spacing: 0.2px;
|
|
}
|
|
|
|
.chart-container {
|
|
position: relative;
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 12px;
|
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.3) 0%, rgba(245, 247, 250, 0.1) 100%);
|
|
border-radius: 0 0 24px 24px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.chart-wrapper {
|
|
width: 100% !important;
|
|
height: 100% !important;
|
|
max-height: 100%;
|
|
|
|
canvas {
|
|
max-height: 100% !important;
|
|
}
|
|
}
|
|
|
|
.pie-chart {
|
|
max-width: 100%;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
:deep(canvas) {
|
|
border-radius: 12px;
|
|
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.05));
|
|
transition: all 0.3s ease;
|
|
|
|
&:hover {
|
|
transform: scale(1.02);
|
|
}
|
|
}
|
|
|
|
.chart-loading {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 12px;
|
|
min-height: 200px;
|
|
|
|
.loading-text {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--color-neutral-700);
|
|
margin: 0;
|
|
}
|
|
}
|
|
|
|
.live-chip {
|
|
font-weight: 700;
|
|
font-size: 11px;
|
|
color: white;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
background: linear-gradient(135deg, var(--color-success-500) 0%, var(--color-success-600) 100%);
|
|
box-shadow: 0 2px 10px rgba(51, 164, 132, 0.3);
|
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
padding: 5px 12px !important;
|
|
border-radius: 50px !important;
|
|
}
|
|
|
|
.pulse-icon {
|
|
animation: pulse 2s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
50% {
|
|
opacity: 0.5;
|
|
transform: scale(0.8);
|
|
}
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 960px) {
|
|
.dashboard-hero {
|
|
min-height: 200px;
|
|
}
|
|
|
|
.hero-row {
|
|
min-height: auto;
|
|
}
|
|
|
|
.hero-left,
|
|
.hero-right {
|
|
text-align: left;
|
|
}
|
|
|
|
.hero-right {
|
|
justify-content: flex-start;
|
|
margin-top: 16px;
|
|
}
|
|
|
|
.hero-title-new {
|
|
font-size: 28px;
|
|
}
|
|
|
|
.hero-subtitle-new {
|
|
.user-name-new {
|
|
font-size: 18px;
|
|
}
|
|
}
|
|
|
|
.hero-actions-new {
|
|
max-width: 100%;
|
|
}
|
|
|
|
.header-mini-stats {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.mini-stat-card {
|
|
width: 100%;
|
|
}
|
|
|
|
.stats-row {
|
|
margin-top: -30px;
|
|
|
|
:deep(.v-col) {
|
|
display: flex;
|
|
}
|
|
}
|
|
|
|
.innovative-card {
|
|
height: 100%;
|
|
}
|
|
|
|
.stat-card-content {
|
|
padding: 14px;
|
|
min-height: 120px;
|
|
}
|
|
|
|
.modern-value {
|
|
font-size: 24px;
|
|
}
|
|
|
|
.modern-label {
|
|
font-size: 10px;
|
|
}
|
|
|
|
.modern-icon {
|
|
width: 42px;
|
|
height: 42px;
|
|
}
|
|
|
|
.chart-header-modern {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 8px;
|
|
padding: 12px 14px;
|
|
}
|
|
|
|
.chart-title {
|
|
font-size: 14px;
|
|
}
|
|
|
|
.chart-subtitle {
|
|
font-size: 10px;
|
|
}
|
|
|
|
.chart-card {
|
|
height: 280px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
.dashboard-hero {
|
|
min-height: 240px;
|
|
}
|
|
|
|
.hero-title-new {
|
|
font-size: 24px;
|
|
}
|
|
|
|
.hero-subtitle-new {
|
|
.user-name-new {
|
|
font-size: 16px;
|
|
}
|
|
|
|
.subtitle-desc {
|
|
font-size: 12px;
|
|
}
|
|
}
|
|
|
|
.quick-info-pills {
|
|
.info-pill {
|
|
font-size: 11px;
|
|
padding: 5px 10px;
|
|
}
|
|
}
|
|
|
|
.export-btn-new {
|
|
height: 44px !important;
|
|
|
|
.btn-text {
|
|
font-size: 13px;
|
|
}
|
|
}
|
|
|
|
.mini-stat-card {
|
|
padding: 10px 12px;
|
|
}
|
|
|
|
.mini-stat-icon {
|
|
width: 32px;
|
|
height: 32px;
|
|
}
|
|
|
|
.mini-stat-content {
|
|
.mini-stat-value {
|
|
font-size: 18px;
|
|
}
|
|
|
|
.mini-stat-label {
|
|
font-size: 10px;
|
|
}
|
|
}
|
|
|
|
.dashboard-bg {
|
|
padding: 8px !important;
|
|
}
|
|
|
|
.stats-row,
|
|
.v-row:not(.stats-row) {
|
|
:deep(.v-col) {
|
|
display: flex;
|
|
}
|
|
}
|
|
|
|
.innovative-card,
|
|
.modern-card {
|
|
height: 100%;
|
|
}
|
|
|
|
.stat-card-content {
|
|
padding: 12px;
|
|
min-height: 110px;
|
|
}
|
|
|
|
.modern-icon {
|
|
width: 40px;
|
|
height: 40px;
|
|
}
|
|
|
|
.chart-card {
|
|
height: 260px;
|
|
}
|
|
}
|
|
</style>
|