@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.
Files changed (136) hide show
  1. package/README.md +42 -0
  2. package/app/app.vue +7 -0
  3. package/app/assets/css/v-app.css +313 -0
  4. package/app/components/V/A/Badge.vue +75 -0
  5. package/app/components/V/A/Btn/Add.vue +17 -0
  6. package/app/components/V/A/Btn/Back.vue +25 -0
  7. package/app/components/V/A/Btn/ConfirmDelete.vue +45 -0
  8. package/app/components/V/A/Btn/Edit.vue +35 -0
  9. package/app/components/V/A/Btn/Export.vue +28 -0
  10. package/app/components/V/A/Btn/Refresh.vue +21 -0
  11. package/app/components/V/A/Btn/Submit.vue +45 -0
  12. package/app/components/V/A/Btn/View.vue +23 -0
  13. package/app/components/V/A/Card.legacy.vue +291 -0
  14. package/app/components/V/A/Card.vue +108 -0
  15. package/app/components/V/A/CompanyMenu.vue +83 -0
  16. package/app/components/V/A/Data/KeyValue.vue +98 -0
  17. package/app/components/V/A/Data/StatusBadge.vue +44 -0
  18. package/app/components/V/A/DataField.vue +140 -0
  19. package/app/components/V/A/DataGrid.vue +43 -0
  20. package/app/components/V/A/DataTable.vue +144 -0
  21. package/app/components/V/A/EmptyState.vue +154 -0
  22. package/app/components/V/A/Fmt/Currency.vue +36 -0
  23. package/app/components/V/A/Fmt/DateTime.vue +34 -0
  24. package/app/components/V/A/Fmt/Percent.vue +47 -0
  25. package/app/components/V/A/LoadingState.vue +140 -0
  26. package/app/components/V/A/MetricCard.vue +129 -0
  27. package/app/components/V/A/Modal/Base.vue +195 -0
  28. package/app/components/V/A/Modal/Confirm.vue +92 -0
  29. package/app/components/V/A/Modal/Form.vue +105 -0
  30. package/app/components/V/A/Navigation.vue +110 -0
  31. package/app/components/V/A/QuickActions.vue +169 -0
  32. package/app/components/V/A/Slide.vue +109 -0
  33. package/app/components/V/A/Slideover.vue +259 -0
  34. package/app/components/V/A/State/Empty.vue +20 -0
  35. package/app/components/V/A/State/Error.vue +34 -0
  36. package/app/components/V/A/State/Loading.vue +33 -0
  37. package/app/components/V/A/StatsCard.vue +215 -0
  38. package/app/components/V/A/StatusBadge.vue +215 -0
  39. package/app/components/V/A/Table.vue +674 -0
  40. package/app/components/V/A/UserMenu.vue +127 -0
  41. package/app/components/V/A/WelcomeHeader.vue +96 -0
  42. package/app/components/V/Modal.vue +36 -0
  43. package/app/components/Va/Blocks/VaBlockGridCharts.vue +32 -0
  44. package/app/components/Va/Blocks/VaBlockGridKPI.vue +32 -0
  45. package/app/components/Va/Blocks/VaBlockGridTables.vue +23 -0
  46. package/app/components/Va/Blocks/VaBlockKpiGrid.vue +8 -0
  47. package/app/components/Va/Blocks/VaBlockSessionFilterBar.vue +8 -0
  48. package/app/components/Va/Cards/VaCardDonutChart.vue +59 -0
  49. package/app/components/Va/Cards/VaCardHeader.vue +10 -0
  50. package/app/components/Va/Cards/VaCardKpi.vue +17 -0
  51. package/app/components/Va/Cards/VaCardKpi2.vue +55 -0
  52. package/app/components/Va/Cards/VaCardLatestOrders.vue +82 -0
  53. package/app/components/Va/Cards/VaCardPopularProducts.vue +88 -0
  54. package/app/components/Va/Cards/VaCardRevenueBarChart.vue +49 -0
  55. package/app/components/Va/Cards/VaCardSubtitle.vue +5 -0
  56. package/app/components/Va/Cards/VaCardTitle.vue +5 -0
  57. package/app/components/Va/Cards/VaCardWithActiveUsers.vue +41 -0
  58. package/app/components/Va/Cards/VaCardWithChart.vue +135 -0
  59. package/app/components/Va/Cards/VaCardWithChartBlock.vue +26 -0
  60. package/app/components/Va/Cards/VaCardWithIndicator.vue +39 -0
  61. package/app/components/Va/Cards/VaCardWithProgressCircle.vue +34 -0
  62. package/app/components/Va/Cards/types.ts +11 -0
  63. package/app/components/Va/Charts/VaChartAppPerformanceBar.vue +118 -0
  64. package/app/components/Va/Charts/VaChartAppPerformanceBarChart.vue +118 -0
  65. package/app/components/Va/Charts/VaChartAreaMini.vue +127 -0
  66. package/app/components/Va/Charts/VaChartBarMini.vue +68 -0
  67. package/app/components/Va/Charts/VaChartCardinalMulti.vue +108 -0
  68. package/app/components/Va/Charts/VaChartColorBarChart.vue +78 -0
  69. package/app/components/Va/Charts/VaChartDonutHalf.vue +35 -0
  70. package/app/components/Va/Charts/VaChartDonutMini.vue +77 -0
  71. package/app/components/Va/Charts/VaChartExpensesBar.vue +58 -0
  72. package/app/components/Va/Charts/VaChartFinanceSummary.vue +96 -0
  73. package/app/components/Va/Charts/VaChartGoogleSearchConsole.vue +90 -0
  74. package/app/components/Va/Charts/VaChartIncomeBar.vue +82 -0
  75. package/app/components/Va/Charts/VaChartLegend.vue +25 -0
  76. package/app/components/Va/Charts/VaChartLineMini.vue +205 -0
  77. package/app/components/Va/Charts/VaChartRealtimeTraffic.vue +182 -0
  78. package/app/components/Va/Charts/VaChartRevenue.vue +43 -0
  79. package/app/components/Va/Charts/VaChartRevenueLine.vue +42 -0
  80. package/app/components/Va/Charts/VaChartRevenuevsCost.vue +84 -0
  81. package/app/components/Va/Charts/VaChartSearchIntent.vue +179 -0
  82. package/app/components/Va/Charts/VaChartSpendingTrend.vue +127 -0
  83. package/app/components/Va/Charts/VaChartStackedHorizontal.vue +64 -0
  84. package/app/components/Va/Charts/VaChartStepMinimal.vue +109 -0
  85. package/app/components/Va/Charts/VaChartStockComparisonLine.vue +86 -0
  86. package/app/components/Va/Charts/VaChartStocksPortfolioLine.vue +161 -0
  87. package/app/components/Va/Charts/VaChartStocksSectorLine.vue +223 -0
  88. package/app/components/Va/Charts/VaChartTasksCategories.vue +96 -0
  89. package/app/components/Va/Charts/VaChartTasksProgress.vue +130 -0
  90. package/app/components/Va/Charts/VaChartTrafficOverview.vue +112 -0
  91. package/app/components/Va/Charts/VaChartWebPerformanceLineChart.vue +114 -0
  92. package/app/components/Va/Charts/VaChartWinLostBar.vue +110 -0
  93. package/app/components/Va/Charts/VaChartWinLostDonut.vue +107 -0
  94. package/app/components/Va/Charts/VaChartWinLostLine.vue +111 -0
  95. package/app/components/Va/Charts/types.ts +10 -0
  96. package/app/components/Va/Dashboard/Navigation/types.ts +8 -0
  97. package/app/components/Va/Dashboard/VaDashboardKPICard.vue +31 -0
  98. package/app/components/Va/Dashboard/VaDashboardNavigation.vue +50 -0
  99. package/app/components/Va/Dashboard/VaDashboardPricePlan.vue +102 -0
  100. package/app/components/Va/Dashboard/VaDashboardUsageChart.vue +84 -0
  101. package/app/components/Va/Dashboard/VaDashboardUsageRequestChart.vue +46 -0
  102. package/app/components/Va/Layout/NotificationsSlideover.vue +169 -0
  103. package/app/components/Va/Layout/SideNav/types.ts +5 -0
  104. package/app/components/Va/Layout/SideNav.vue +108 -0
  105. package/app/components/Va/Layout/TeamsMenu.vue +57 -0
  106. package/app/components/Va/Layout/UserMenu.vue +57 -0
  107. package/app/composables/useDashboard.ts +25 -0
  108. package/app/composables/useVAAnimation.ts +324 -0
  109. package/app/composables/useVAUtils.ts +118 -0
  110. package/app/composables/useVCrud.ts +647 -0
  111. package/app/composables/useVFetch.ts +46 -0
  112. package/app/composables/useVFileUpload.ts +45 -0
  113. package/app/composables/useVToast.ts +73 -0
  114. package/app/composables/useXATableColumns.ts +456 -0
  115. package/app/data/BillingStats.ts +65 -0
  116. package/app/data/SearchData.ts +58 -0
  117. package/app/data/TasksData.ts +101 -0
  118. package/app/data/dashboardData.ts +113 -0
  119. package/app/layouts/default.vue +171 -0
  120. package/app/layouts/legacy.vue +61 -0
  121. package/app/pages/playground/base.vue +498 -0
  122. package/app/pages/playground/blocks.vue +108 -0
  123. package/app/pages/playground/buttons.vue +237 -0
  124. package/app/pages/playground/cards.vue +326 -0
  125. package/app/pages/playground/charts.vue +338 -0
  126. package/app/pages/playground/dashboard.vue +315 -0
  127. package/app/pages/playground/formatters.vue +329 -0
  128. package/app/pages/playground/index.vue +109 -0
  129. package/app/pages/playground/layout.vue +159 -0
  130. package/app/pages/playground/modals.vue +606 -0
  131. package/app/pages/playground/states.vue +282 -0
  132. package/app/pages/playground/tables.vue +618 -0
  133. package/app/pages/test-layout.vue +10 -0
  134. package/nuxt.config.ts +12 -0
  135. package/package.json +71 -0
  136. 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>