@veristone/nuxt-v-app 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/app/app.vue +7 -0
- package/app/assets/css/v-app.css +313 -0
- package/app/components/V/A/Badge.vue +75 -0
- package/app/components/V/A/Btn/Add.vue +17 -0
- package/app/components/V/A/Btn/Back.vue +25 -0
- package/app/components/V/A/Btn/ConfirmDelete.vue +45 -0
- package/app/components/V/A/Btn/Edit.vue +35 -0
- package/app/components/V/A/Btn/Export.vue +28 -0
- package/app/components/V/A/Btn/Refresh.vue +21 -0
- package/app/components/V/A/Btn/Submit.vue +45 -0
- package/app/components/V/A/Btn/View.vue +23 -0
- package/app/components/V/A/Card.legacy.vue +291 -0
- package/app/components/V/A/Card.vue +108 -0
- package/app/components/V/A/CompanyMenu.vue +83 -0
- package/app/components/V/A/Data/KeyValue.vue +98 -0
- package/app/components/V/A/Data/StatusBadge.vue +44 -0
- package/app/components/V/A/DataField.vue +140 -0
- package/app/components/V/A/DataGrid.vue +43 -0
- package/app/components/V/A/DataTable.vue +144 -0
- package/app/components/V/A/EmptyState.vue +154 -0
- package/app/components/V/A/Fmt/Currency.vue +36 -0
- package/app/components/V/A/Fmt/DateTime.vue +34 -0
- package/app/components/V/A/Fmt/Percent.vue +47 -0
- package/app/components/V/A/LoadingState.vue +140 -0
- package/app/components/V/A/MetricCard.vue +129 -0
- package/app/components/V/A/Modal/Base.vue +195 -0
- package/app/components/V/A/Modal/Confirm.vue +92 -0
- package/app/components/V/A/Modal/Form.vue +105 -0
- package/app/components/V/A/Navigation.vue +110 -0
- package/app/components/V/A/QuickActions.vue +169 -0
- package/app/components/V/A/Slide.vue +109 -0
- package/app/components/V/A/Slideover.vue +259 -0
- package/app/components/V/A/State/Empty.vue +20 -0
- package/app/components/V/A/State/Error.vue +34 -0
- package/app/components/V/A/State/Loading.vue +33 -0
- package/app/components/V/A/StatsCard.vue +215 -0
- package/app/components/V/A/StatusBadge.vue +215 -0
- package/app/components/V/A/Table.vue +674 -0
- package/app/components/V/A/UserMenu.vue +127 -0
- package/app/components/V/A/WelcomeHeader.vue +96 -0
- package/app/components/V/Modal.vue +36 -0
- package/app/components/Va/Blocks/VaBlockGridCharts.vue +32 -0
- package/app/components/Va/Blocks/VaBlockGridKPI.vue +32 -0
- package/app/components/Va/Blocks/VaBlockGridTables.vue +23 -0
- package/app/components/Va/Blocks/VaBlockKpiGrid.vue +8 -0
- package/app/components/Va/Blocks/VaBlockSessionFilterBar.vue +8 -0
- package/app/components/Va/Cards/VaCardDonutChart.vue +59 -0
- package/app/components/Va/Cards/VaCardHeader.vue +10 -0
- package/app/components/Va/Cards/VaCardKpi.vue +17 -0
- package/app/components/Va/Cards/VaCardKpi2.vue +55 -0
- package/app/components/Va/Cards/VaCardLatestOrders.vue +82 -0
- package/app/components/Va/Cards/VaCardPopularProducts.vue +88 -0
- package/app/components/Va/Cards/VaCardRevenueBarChart.vue +49 -0
- package/app/components/Va/Cards/VaCardSubtitle.vue +5 -0
- package/app/components/Va/Cards/VaCardTitle.vue +5 -0
- package/app/components/Va/Cards/VaCardWithActiveUsers.vue +41 -0
- package/app/components/Va/Cards/VaCardWithChart.vue +135 -0
- package/app/components/Va/Cards/VaCardWithChartBlock.vue +26 -0
- package/app/components/Va/Cards/VaCardWithIndicator.vue +39 -0
- package/app/components/Va/Cards/VaCardWithProgressCircle.vue +34 -0
- package/app/components/Va/Cards/types.ts +11 -0
- package/app/components/Va/Charts/VaChartAppPerformanceBar.vue +118 -0
- package/app/components/Va/Charts/VaChartAppPerformanceBarChart.vue +118 -0
- package/app/components/Va/Charts/VaChartAreaMini.vue +127 -0
- package/app/components/Va/Charts/VaChartBarMini.vue +68 -0
- package/app/components/Va/Charts/VaChartCardinalMulti.vue +108 -0
- package/app/components/Va/Charts/VaChartColorBarChart.vue +78 -0
- package/app/components/Va/Charts/VaChartDonutHalf.vue +35 -0
- package/app/components/Va/Charts/VaChartDonutMini.vue +77 -0
- package/app/components/Va/Charts/VaChartExpensesBar.vue +58 -0
- package/app/components/Va/Charts/VaChartFinanceSummary.vue +96 -0
- package/app/components/Va/Charts/VaChartGoogleSearchConsole.vue +90 -0
- package/app/components/Va/Charts/VaChartIncomeBar.vue +82 -0
- package/app/components/Va/Charts/VaChartLegend.vue +25 -0
- package/app/components/Va/Charts/VaChartLineMini.vue +205 -0
- package/app/components/Va/Charts/VaChartRealtimeTraffic.vue +182 -0
- package/app/components/Va/Charts/VaChartRevenue.vue +43 -0
- package/app/components/Va/Charts/VaChartRevenueLine.vue +42 -0
- package/app/components/Va/Charts/VaChartRevenuevsCost.vue +84 -0
- package/app/components/Va/Charts/VaChartSearchIntent.vue +179 -0
- package/app/components/Va/Charts/VaChartSpendingTrend.vue +127 -0
- package/app/components/Va/Charts/VaChartStackedHorizontal.vue +64 -0
- package/app/components/Va/Charts/VaChartStepMinimal.vue +109 -0
- package/app/components/Va/Charts/VaChartStockComparisonLine.vue +86 -0
- package/app/components/Va/Charts/VaChartStocksPortfolioLine.vue +161 -0
- package/app/components/Va/Charts/VaChartStocksSectorLine.vue +223 -0
- package/app/components/Va/Charts/VaChartTasksCategories.vue +96 -0
- package/app/components/Va/Charts/VaChartTasksProgress.vue +130 -0
- package/app/components/Va/Charts/VaChartTrafficOverview.vue +112 -0
- package/app/components/Va/Charts/VaChartWebPerformanceLineChart.vue +114 -0
- package/app/components/Va/Charts/VaChartWinLostBar.vue +110 -0
- package/app/components/Va/Charts/VaChartWinLostDonut.vue +107 -0
- package/app/components/Va/Charts/VaChartWinLostLine.vue +111 -0
- package/app/components/Va/Charts/types.ts +10 -0
- package/app/components/Va/Dashboard/Navigation/types.ts +8 -0
- package/app/components/Va/Dashboard/VaDashboardKPICard.vue +31 -0
- package/app/components/Va/Dashboard/VaDashboardNavigation.vue +50 -0
- package/app/components/Va/Dashboard/VaDashboardPricePlan.vue +102 -0
- package/app/components/Va/Dashboard/VaDashboardUsageChart.vue +84 -0
- package/app/components/Va/Dashboard/VaDashboardUsageRequestChart.vue +46 -0
- package/app/components/Va/Layout/NotificationsSlideover.vue +169 -0
- package/app/components/Va/Layout/SideNav/types.ts +5 -0
- package/app/components/Va/Layout/SideNav.vue +108 -0
- package/app/components/Va/Layout/TeamsMenu.vue +57 -0
- package/app/components/Va/Layout/UserMenu.vue +57 -0
- package/app/composables/useDashboard.ts +25 -0
- package/app/composables/useVAAnimation.ts +324 -0
- package/app/composables/useVAUtils.ts +118 -0
- package/app/composables/useVCrud.ts +647 -0
- package/app/composables/useVFetch.ts +46 -0
- package/app/composables/useVFileUpload.ts +45 -0
- package/app/composables/useVToast.ts +73 -0
- package/app/composables/useXATableColumns.ts +456 -0
- package/app/data/BillingStats.ts +65 -0
- package/app/data/SearchData.ts +58 -0
- package/app/data/TasksData.ts +101 -0
- package/app/data/dashboardData.ts +113 -0
- package/app/layouts/default.vue +171 -0
- package/app/layouts/legacy.vue +61 -0
- package/app/pages/playground/base.vue +498 -0
- package/app/pages/playground/blocks.vue +108 -0
- package/app/pages/playground/buttons.vue +237 -0
- package/app/pages/playground/cards.vue +326 -0
- package/app/pages/playground/charts.vue +338 -0
- package/app/pages/playground/dashboard.vue +315 -0
- package/app/pages/playground/formatters.vue +329 -0
- package/app/pages/playground/index.vue +109 -0
- package/app/pages/playground/layout.vue +159 -0
- package/app/pages/playground/modals.vue +606 -0
- package/app/pages/playground/states.vue +282 -0
- package/app/pages/playground/tables.vue +618 -0
- package/app/pages/test-layout.vue +10 -0
- package/nuxt.config.ts +12 -0
- package/package.json +71 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { SpendingData, BulletLegendItemInterface } from './types'
|
|
3
|
+
|
|
4
|
+
const chartData = ref<SpendingData[]>([
|
|
5
|
+
{
|
|
6
|
+
month: 1,
|
|
7
|
+
amount: 2500,
|
|
8
|
+
previous: 1100
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
month: 2,
|
|
12
|
+
amount: 1500,
|
|
13
|
+
previous: 1200
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
month: 3,
|
|
17
|
+
amount: 3000,
|
|
18
|
+
previous: 2800
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
month: 4,
|
|
22
|
+
amount: 4000,
|
|
23
|
+
previous: 3500
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
month: 5,
|
|
27
|
+
amount: 4500,
|
|
28
|
+
previous: 4100
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
month: 6,
|
|
32
|
+
amount: 2800,
|
|
33
|
+
previous: 2500
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
month: 7,
|
|
37
|
+
amount: 3500,
|
|
38
|
+
previous: 3200
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
month: 8,
|
|
42
|
+
amount: 3800,
|
|
43
|
+
previous: 3600
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
month: 9,
|
|
47
|
+
amount: 2000,
|
|
48
|
+
previous: 1800
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
month: 10,
|
|
52
|
+
amount: 4200,
|
|
53
|
+
previous: 3900
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
month: 11,
|
|
57
|
+
amount: 2200,
|
|
58
|
+
previous: 2000
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
month: 12,
|
|
62
|
+
amount: 1800,
|
|
63
|
+
previous: 1500
|
|
64
|
+
}
|
|
65
|
+
])
|
|
66
|
+
|
|
67
|
+
const categories: Record<string, BulletLegendItemInterface> = {
|
|
68
|
+
amount: {
|
|
69
|
+
name: 'Current Spending',
|
|
70
|
+
color: '#22c55e'
|
|
71
|
+
},
|
|
72
|
+
previous: {
|
|
73
|
+
name: 'Last YHear',
|
|
74
|
+
color: '#00BCFF'
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const formatCurrency = (value: number) => {
|
|
79
|
+
return new Intl.NumberFormat('en-US', {
|
|
80
|
+
style: 'currency',
|
|
81
|
+
currency: 'USD',
|
|
82
|
+
minimumFractionDigits: 0,
|
|
83
|
+
maximumFractionDigits: 0
|
|
84
|
+
}).format(value)
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<template>
|
|
89
|
+
<div>
|
|
90
|
+
<AreaChart
|
|
91
|
+
:data="chartData"
|
|
92
|
+
:height="90"
|
|
93
|
+
:categories="categories"
|
|
94
|
+
:y-axis="['amount']"
|
|
95
|
+
:y-num-ticks="2"
|
|
96
|
+
:y-grid-line="true"
|
|
97
|
+
:legend-position="LegendPosition.Bottom"
|
|
98
|
+
:hide-y-axis="true"
|
|
99
|
+
:hide-x-axis="true"
|
|
100
|
+
:hide-legend="true"
|
|
101
|
+
:x-formatter="
|
|
102
|
+
(i) =>
|
|
103
|
+
new Date(`2025-${chartData[i]?.month}-02`).toLocaleDateString(
|
|
104
|
+
'en-US',
|
|
105
|
+
{
|
|
106
|
+
month: 'short'
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
"
|
|
110
|
+
:y-formatter="formatCurrency"
|
|
111
|
+
/>
|
|
112
|
+
|
|
113
|
+
<div class="flex justify-center pt-5 space-x-5">
|
|
114
|
+
<div
|
|
115
|
+
v-for="(category, key) in categories"
|
|
116
|
+
:key="key"
|
|
117
|
+
class="flex items-center"
|
|
118
|
+
>
|
|
119
|
+
<span
|
|
120
|
+
class="w-2 h-2 mr-2 rounded-sm"
|
|
121
|
+
:style="{ 'background-color': category.color }"
|
|
122
|
+
/>
|
|
123
|
+
<span class="text-xs text-muted">{{ category.name }}</span>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</template>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
interface SpendingData {
|
|
3
|
+
month: string
|
|
4
|
+
amount: number
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const chartData = ref<SpendingData[]>([
|
|
8
|
+
{
|
|
9
|
+
month: formatDate(new Date('2025-01-01')),
|
|
10
|
+
amount: 2500
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
month: formatDate(new Date('2025-02-01')),
|
|
14
|
+
amount: 1500
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
month: formatDate(new Date('2025-03-01')),
|
|
18
|
+
amount: 3000
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
month: formatDate(new Date('2025-04-01')),
|
|
22
|
+
amount: 4000
|
|
23
|
+
}
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
const categories: Record<string, BulletLegendItemInterface> = {
|
|
27
|
+
amount: {
|
|
28
|
+
name: 'Monthly Spending',
|
|
29
|
+
color: '#22c55e'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const formatCurrency = (tick: number) => {
|
|
34
|
+
return new Intl.NumberFormat('en-US', {
|
|
35
|
+
style: 'currency',
|
|
36
|
+
currency: 'USD',
|
|
37
|
+
minimumFractionDigits: 0,
|
|
38
|
+
maximumFractionDigits: 0
|
|
39
|
+
}).format(tick)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function formatDate(dateString: Date) {
|
|
43
|
+
const options = {
|
|
44
|
+
month: 'short'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const date = new Date(dateString)
|
|
48
|
+
return new Intl.DateTimeFormat('en-US', options).format(date)
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<template>
|
|
53
|
+
<div>
|
|
54
|
+
<BarChart
|
|
55
|
+
:data="chartData"
|
|
56
|
+
:height="80"
|
|
57
|
+
:categories="categories"
|
|
58
|
+
:y-axis="['amount']"
|
|
59
|
+
:curve-type="CurveType.Basis"
|
|
60
|
+
:hide-y-axis="true"
|
|
61
|
+
:x-formatter="(i) => `${chartData[i]?.month}`"
|
|
62
|
+
:min-max-ticks-only="false"
|
|
63
|
+
:y-formatter="formatCurrency"
|
|
64
|
+
:hide-legend="true"
|
|
65
|
+
class="pointer-events-none"
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
</template>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
interface TrafficDataItem {
|
|
3
|
+
hour: string
|
|
4
|
+
organic: number
|
|
5
|
+
paid: number
|
|
6
|
+
social: number
|
|
7
|
+
direct: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const TrafficData: TrafficDataItem[] = [
|
|
11
|
+
{
|
|
12
|
+
hour: '06:00',
|
|
13
|
+
organic: 120,
|
|
14
|
+
paid: 45,
|
|
15
|
+
social: 30,
|
|
16
|
+
direct: 85
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
hour: '09:00',
|
|
20
|
+
organic: 280,
|
|
21
|
+
paid: 90,
|
|
22
|
+
social: 65,
|
|
23
|
+
direct: 150
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
hour: '12:00',
|
|
27
|
+
organic: 450,
|
|
28
|
+
paid: 180,
|
|
29
|
+
social: 120,
|
|
30
|
+
direct: 220
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
hour: '15:00',
|
|
34
|
+
organic: 380,
|
|
35
|
+
paid: 165,
|
|
36
|
+
social: 95,
|
|
37
|
+
direct: 190
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
hour: '18:00',
|
|
41
|
+
organic: 520,
|
|
42
|
+
paid: 200,
|
|
43
|
+
social: 140,
|
|
44
|
+
direct: 260
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
hour: '21:00',
|
|
48
|
+
organic: 320,
|
|
49
|
+
paid: 130,
|
|
50
|
+
social: 80,
|
|
51
|
+
direct: 170
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
const TrafficCategories = computed(() => ({
|
|
56
|
+
organic: {
|
|
57
|
+
name: 'Organic',
|
|
58
|
+
color: '#10b981'
|
|
59
|
+
},
|
|
60
|
+
paid: {
|
|
61
|
+
name: 'Paid',
|
|
62
|
+
color: '#6366f1'
|
|
63
|
+
},
|
|
64
|
+
social: {
|
|
65
|
+
name: 'Social',
|
|
66
|
+
color: '#8b5cf6'
|
|
67
|
+
},
|
|
68
|
+
direct: {
|
|
69
|
+
name: 'Direct',
|
|
70
|
+
color: '#f59e0b'
|
|
71
|
+
}
|
|
72
|
+
}))
|
|
73
|
+
|
|
74
|
+
const xFormatter = (i: number): string => `${TrafficData[i]?.hour}`
|
|
75
|
+
const yFormatter = (value: number): string => `${value}`
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<template>
|
|
79
|
+
<UCard>
|
|
80
|
+
<div class="space-y-4">
|
|
81
|
+
<div class="flex items-center justify-between">
|
|
82
|
+
<h2 class="text-lg font-medium text-highlighted">
|
|
83
|
+
Traffic Analytics
|
|
84
|
+
</h2>
|
|
85
|
+
<div class="text-sm text-toned">
|
|
86
|
+
Last Hours
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<AreaChart
|
|
91
|
+
:data="TrafficData"
|
|
92
|
+
:height="240"
|
|
93
|
+
:categories="TrafficCategories"
|
|
94
|
+
:x-formatter="xFormatter"
|
|
95
|
+
:y-formatter="yFormatter"
|
|
96
|
+
:curve-type="CurveType.Cardinal"
|
|
97
|
+
:legend-position="LegendPosition.TopRight"
|
|
98
|
+
:hide-legend="false"
|
|
99
|
+
:x-grid-line="false"
|
|
100
|
+
:y-grid-line="true"
|
|
101
|
+
:x-domain-line="true"
|
|
102
|
+
:y-domain-line="false"
|
|
103
|
+
:x-num-ticks="6"
|
|
104
|
+
y-label="Visitors"
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
107
|
+
</UCard>
|
|
108
|
+
</template>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
const props = defineProps<{
|
|
3
|
+
title: string
|
|
4
|
+
color?: string
|
|
5
|
+
}>()
|
|
6
|
+
|
|
7
|
+
const RevenueData = [
|
|
8
|
+
{
|
|
9
|
+
month: 'January',
|
|
10
|
+
desktop: 186,
|
|
11
|
+
mobile: 80
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
month: 'February',
|
|
15
|
+
desktop: 305,
|
|
16
|
+
mobile: 200
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
month: 'March',
|
|
20
|
+
desktop: 237,
|
|
21
|
+
mobile: 120
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
month: 'April',
|
|
25
|
+
desktop: 73,
|
|
26
|
+
mobile: 190
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
month: 'May',
|
|
30
|
+
desktop: 209,
|
|
31
|
+
mobile: 130
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
month: 'June',
|
|
35
|
+
desktop: 214,
|
|
36
|
+
mobile: 140
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
const RevenueCategories = {
|
|
41
|
+
desktop: {
|
|
42
|
+
name: 'Desktop',
|
|
43
|
+
color: props.color
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const xFormatter = (i: number): string => `${RevenueData[i]?.month}`
|
|
48
|
+
const yFormatter = (i: number) => i.toString()
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<UCard>
|
|
53
|
+
<div class="space-y-4">
|
|
54
|
+
<div class="flex items-center justify-between">
|
|
55
|
+
<h2 class="text-lg font-medium text-highlighted">
|
|
56
|
+
{{ title }}
|
|
57
|
+
</h2>
|
|
58
|
+
<div class="text-sm text-toned">
|
|
59
|
+
Last 6 Months
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<BarChart
|
|
64
|
+
:data="RevenueData"
|
|
65
|
+
:height="240"
|
|
66
|
+
:categories="RevenueCategories"
|
|
67
|
+
:y-axis="['desktop']"
|
|
68
|
+
:x-num-ticks="6"
|
|
69
|
+
:radius="4"
|
|
70
|
+
:y-grid-line="true"
|
|
71
|
+
:x-formatter="xFormatter"
|
|
72
|
+
:y-formatter="yFormatter"
|
|
73
|
+
:legend-position="LegendPosition.TopRight"
|
|
74
|
+
y-label="Visitors"
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
</UCard>
|
|
78
|
+
</template>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
const categories = {
|
|
3
|
+
'Sustainable TECH': {
|
|
4
|
+
name: 'Sustainable TECH',
|
|
5
|
+
color: 'var(--ui-primary)',
|
|
6
|
+
percentage: 85
|
|
7
|
+
},
|
|
8
|
+
'Non-sustainable TECH': {
|
|
9
|
+
name: 'Non-sustainable TECH',
|
|
10
|
+
color: 'transparent',
|
|
11
|
+
percentage: 15
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<DonutChart
|
|
18
|
+
:data="Object.values(categories).map((i) => i.percentage)"
|
|
19
|
+
:height="180"
|
|
20
|
+
:categories="categories"
|
|
21
|
+
:hide-legend="true"
|
|
22
|
+
:type="DonutType.Half"
|
|
23
|
+
:pad-angle="0.1"
|
|
24
|
+
:radius="16"
|
|
25
|
+
>
|
|
26
|
+
<div class="mt-16 text-center space-y-2">
|
|
27
|
+
<div class="text-4xl font-bold">
|
|
28
|
+
85%
|
|
29
|
+
</div>
|
|
30
|
+
<div class="text-muted">
|
|
31
|
+
Sustainable Tech
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</DonutChart>
|
|
35
|
+
</template>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
const marketShareLabels = [
|
|
3
|
+
{
|
|
4
|
+
name: 'Chrome',
|
|
5
|
+
color: 'var(--color-success)',
|
|
6
|
+
percentage: 65.2
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
name: 'Safari',
|
|
10
|
+
color: 'var(--color-indigo-400)',
|
|
11
|
+
percentage: 18.7
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'Edge',
|
|
15
|
+
color: 'var(--color-sky-400)',
|
|
16
|
+
percentage: 4.3
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
const marketShareData = marketShareLabels.map(i => i.percentage)
|
|
21
|
+
|
|
22
|
+
const marketShareCategories = Object.fromEntries(
|
|
23
|
+
marketShareLabels.map(item => [
|
|
24
|
+
item.name,
|
|
25
|
+
{
|
|
26
|
+
name: item.name,
|
|
27
|
+
color: item.color
|
|
28
|
+
}
|
|
29
|
+
])
|
|
30
|
+
)
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<div class="my-4 flex flex-col">
|
|
35
|
+
<DonutChart
|
|
36
|
+
:data="marketShareData"
|
|
37
|
+
:height="110"
|
|
38
|
+
:type="DonutType.Full"
|
|
39
|
+
:radius="2"
|
|
40
|
+
:categories="marketShareCategories"
|
|
41
|
+
:hide-legend="true"
|
|
42
|
+
:arc-width="12"
|
|
43
|
+
:pad-angle="0.1"
|
|
44
|
+
>
|
|
45
|
+
<div class=" text-center">
|
|
46
|
+
<div class="text-muted text-sm">
|
|
47
|
+
Browser
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</DonutChart>
|
|
51
|
+
|
|
52
|
+
<div
|
|
53
|
+
class="max-auto divide-rounded-lg w-full space-y-2 divide-y divide-(--ui-border) px-2"
|
|
54
|
+
>
|
|
55
|
+
<div class="text-muted flex justify-between pb-2 text-xs">
|
|
56
|
+
<div>Browser</div>
|
|
57
|
+
<div>Share</div>
|
|
58
|
+
</div>
|
|
59
|
+
<div
|
|
60
|
+
v-for="(share, shareKey) in marketShareLabels"
|
|
61
|
+
:key="shareKey"
|
|
62
|
+
class="flex w-full items-center justify-between pb-2 text-sm last:pb-0"
|
|
63
|
+
>
|
|
64
|
+
<div class="flex items-center gap-4">
|
|
65
|
+
<div
|
|
66
|
+
class="h-4 w-1"
|
|
67
|
+
:style="{ backgroundColor: share.color }"
|
|
68
|
+
/>
|
|
69
|
+
<div class="text-muted">
|
|
70
|
+
{{ share.name }}
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
<div>{{ share.percentage }}%</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</template>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
interface SalesDataItem {
|
|
3
|
+
product: string;
|
|
4
|
+
sales: number;
|
|
5
|
+
counter: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const SalesData: SalesDataItem[] = [
|
|
9
|
+
{ product: "Widgets", sales: 2400, counter: -600 },
|
|
10
|
+
{ product: "Gadgets", sales: 1800, counter: -350 },
|
|
11
|
+
{ product: "Tools", sales: 3200, counter: -800 },
|
|
12
|
+
{ product: "Parts", sales: 2000, counter: -25 },
|
|
13
|
+
{ product: "Kits", sales: 2800, counter: -700 },
|
|
14
|
+
{ product: "Accessories", sales: 1500, counter: -200 },
|
|
15
|
+
{ product: "Supplies", sales: 1700, counter: -300 },
|
|
16
|
+
{ product: "Components", sales: 2600, counter: -500 },
|
|
17
|
+
{ product: "Devices", sales: 2100, counter: -400 },
|
|
18
|
+
{ product: "Modules", sales: 2300, counter: -450 },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const SalesCategories = computed(() => ({
|
|
22
|
+
sales: {
|
|
23
|
+
name: "Sales Revenue",
|
|
24
|
+
color: "var(--ui-primary)",
|
|
25
|
+
},
|
|
26
|
+
counter: {
|
|
27
|
+
name: "Counter Revenue",
|
|
28
|
+
color: "var(--color-warning-500)",
|
|
29
|
+
},
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
const xFormatter = (i: number): string => `${SalesData[i]?.product}`;
|
|
33
|
+
const yFormatter = (value: number): string => `$${(value / 1000).toFixed(1)}k`;
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<BarChart
|
|
38
|
+
:data="SalesData"
|
|
39
|
+
:height="140"
|
|
40
|
+
:stacked="true"
|
|
41
|
+
:categories="SalesCategories"
|
|
42
|
+
:hide-x-axis="true"
|
|
43
|
+
:y-num-ticks="4"
|
|
44
|
+
:y-axis="['sales' ,'counter']"
|
|
45
|
+
:x-formatter="xFormatter"
|
|
46
|
+
:y-formatter="yFormatter"
|
|
47
|
+
:legend-position="LegendPosition.Top"
|
|
48
|
+
:hide-legend="false"
|
|
49
|
+
:y-grid-line="true"
|
|
50
|
+
:x-grid-line="false"
|
|
51
|
+
:y-domain-line="false"
|
|
52
|
+
:x-domain-line="false"
|
|
53
|
+
:y-tick-line="false"
|
|
54
|
+
:x-tick-line="false"
|
|
55
|
+
:radius="8"
|
|
56
|
+
:bar-padding="0.6"
|
|
57
|
+
/>
|
|
58
|
+
</template>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
interface SpendingCategory {
|
|
3
|
+
color: string;
|
|
4
|
+
name: string;
|
|
5
|
+
value: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const spendingData = ref<SpendingCategory[]>([
|
|
9
|
+
{
|
|
10
|
+
color: "var(--color-emerald-500)", // green
|
|
11
|
+
name: "Food & Drink",
|
|
12
|
+
value: 420,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
color: "var(--color-indigo-500)", // purple
|
|
16
|
+
name: "Grocery",
|
|
17
|
+
value: 320,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
color: "var(--color-pink-500)", // red/pink
|
|
21
|
+
name: "Shopping",
|
|
22
|
+
value: 210,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
color: "var(--color-amber-500)", // warning (amber)
|
|
26
|
+
name: "Transport",
|
|
27
|
+
value: 150,
|
|
28
|
+
},
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const totalSpent = computed(() =>
|
|
32
|
+
spendingData.value.reduce((sum, item) => sum + item.value, 0)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const formatCurrency = (amount: number) => {
|
|
36
|
+
return new Intl.NumberFormat("en-US", {
|
|
37
|
+
style: "currency",
|
|
38
|
+
currency: "USD",
|
|
39
|
+
minimumFractionDigits: 0,
|
|
40
|
+
}).format(amount);
|
|
41
|
+
};
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<template>
|
|
45
|
+
<div class="space-y-8">
|
|
46
|
+
<Flex class="gap-8 space-y-4">
|
|
47
|
+
<div class="relative w-46">
|
|
48
|
+
<DonutChart
|
|
49
|
+
:data="spendingData.map((i) => i.value)"
|
|
50
|
+
:height="200"
|
|
51
|
+
:arc-width="10"
|
|
52
|
+
:labels="
|
|
53
|
+
spendingData.map((item) => ({
|
|
54
|
+
name: item.name,
|
|
55
|
+
color: item.color,
|
|
56
|
+
percentage: item.value,
|
|
57
|
+
}))
|
|
58
|
+
"
|
|
59
|
+
:pad-angle="0.1"
|
|
60
|
+
:hide-legend="true"
|
|
61
|
+
:radius="4"
|
|
62
|
+
>
|
|
63
|
+
<div
|
|
64
|
+
class="absolute text-center w-full top-1/2 left-1/2"
|
|
65
|
+
style="transform: translate(-50%, -50%)"
|
|
66
|
+
>
|
|
67
|
+
<div class="text-xs text-muted">Total Spent</div>
|
|
68
|
+
<div class="text-2xl font-bold">
|
|
69
|
+
{{ formatCurrency(totalSpent) }}
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</DonutChart>
|
|
73
|
+
</div>
|
|
74
|
+
<FlexCol>
|
|
75
|
+
<div class="flex flex-wrap gap-.5">
|
|
76
|
+
<template v-for="(category, index) in spendingData" :key="index">
|
|
77
|
+
<FlexBetween class="w-full">
|
|
78
|
+
<Flex class="gap-4">
|
|
79
|
+
<div
|
|
80
|
+
class="h-2 w-2 rounded-full"
|
|
81
|
+
:style="{ backgroundColor: category.color }"
|
|
82
|
+
/>
|
|
83
|
+
<span class="text-muted whitespace-nowrap">{{ category.name }}</span>
|
|
84
|
+
</Flex>
|
|
85
|
+
<span>{{ ((category.value / totalSpent) * 100).toFixed(1) }}%</span>
|
|
86
|
+
</FlexBetween>
|
|
87
|
+
<div
|
|
88
|
+
v-if="index < spendingData.length - 1"
|
|
89
|
+
class="w-full border-t border-dashed border-default my-2"
|
|
90
|
+
/>
|
|
91
|
+
</template>
|
|
92
|
+
</div>
|
|
93
|
+
</FlexCol>
|
|
94
|
+
</Flex>
|
|
95
|
+
</div>
|
|
96
|
+
</template>
|