@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
+ const props = withDefaults(defineProps<{
3
+ collapsed?: boolean
4
+ user?: {
5
+ name: string
6
+ avatar?: {
7
+ src: string
8
+ alt?: string
9
+ }
10
+ }
11
+ theme?: 'light' | 'dark'
12
+ }>(), {
13
+ collapsed: false,
14
+ user: () => ({
15
+ name: 'User',
16
+ avatar: {
17
+ src: 'https://api.dicebear.com/7.x/avataaars/svg?seed=default',
18
+ alt: 'User'
19
+ }
20
+ }),
21
+ theme: 'light'
22
+ })
23
+
24
+ const buttonUi = computed(() => props.theme === 'dark'
25
+ ? {
26
+ root: 'hover:bg-white/5',
27
+ label: 'text-slate-300 group-hover:text-white',
28
+ trailingIcon: 'text-slate-400 group-hover:text-white'
29
+ }
30
+ : { trailingIcon: 'text-dimmed' }
31
+ )
32
+
33
+ const emit = defineEmits<{
34
+ logout: []
35
+ profile: []
36
+ settings: []
37
+ }>()
38
+
39
+ // Simple menu state
40
+ const isOpen = ref(false)
41
+ const menuRef = ref<HTMLElement | null>(null)
42
+
43
+ const toggleMenu = () => {
44
+ isOpen.value = !isOpen.value
45
+ }
46
+
47
+ const closeMenu = () => {
48
+ isOpen.value = false
49
+ }
50
+
51
+ const menuItems = [
52
+ {
53
+ label: 'Profile',
54
+ icon: 'i-lucide-user',
55
+ action: () => { emit('profile'); closeMenu() }
56
+ },
57
+ {
58
+ label: 'Settings',
59
+ icon: 'i-lucide-settings',
60
+ action: () => { emit('settings'); closeMenu() }
61
+ },
62
+ {
63
+ label: 'Log out',
64
+ icon: 'i-lucide-log-out',
65
+ action: () => { emit('logout'); closeMenu() }
66
+ }
67
+ ]
68
+
69
+ // Close menu when clicking outside
70
+ onMounted(() => {
71
+ const handleClickOutside = (event: MouseEvent) => {
72
+ if (menuRef.value && !menuRef.value.contains(event.target as Node)) {
73
+ closeMenu()
74
+ }
75
+ }
76
+ document.addEventListener('click', handleClickOutside)
77
+ onUnmounted(() => {
78
+ document.removeEventListener('click', handleClickOutside)
79
+ })
80
+ })
81
+ </script>
82
+
83
+ <template>
84
+ <div ref="menuRef" class="relative">
85
+ <UButton
86
+ :avatar="user?.avatar"
87
+ :label="collapsed ? undefined : user?.name"
88
+ :trailing-icon="collapsed ? undefined : 'i-lucide-chevrons-up-down'"
89
+ color="neutral"
90
+ variant="ghost"
91
+ block
92
+ :square="collapsed"
93
+ :class="[
94
+ 'group',
95
+ isOpen && 'bg-white/5',
96
+ theme === 'dark' && 'hover:bg-white/5'
97
+ ]"
98
+ :ui="buttonUi"
99
+ @click.stop="toggleMenu"
100
+ />
101
+
102
+ <!-- Custom dropdown menu -->
103
+ <Transition
104
+ enter-active-class="transition ease-out duration-100"
105
+ enter-from-class="transform opacity-0 scale-95"
106
+ enter-to-class="transform opacity-100 scale-100"
107
+ leave-active-class="transition ease-in duration-75"
108
+ leave-from-class="transform opacity-100 scale-100"
109
+ leave-to-class="transform opacity-0 scale-95"
110
+ >
111
+ <div
112
+ v-if="isOpen"
113
+ class="absolute bottom-full left-0 mb-2 w-48 rounded-lg bg-white dark:bg-slate-800 shadow-lg ring-1 ring-black/5 dark:ring-white/10 py-1 z-50"
114
+ >
115
+ <button
116
+ v-for="item in menuItems"
117
+ :key="item.label"
118
+ class="w-full flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-slate-700 transition-colors"
119
+ @click="item.action"
120
+ >
121
+ <UIcon :name="item.icon" class="size-4" />
122
+ {{ item.label }}
123
+ </button>
124
+ </div>
125
+ </Transition>
126
+ </div>
127
+ </template>
@@ -0,0 +1,96 @@
1
+ <template>
2
+ <div class="va-dashboard-header animate-fade-in-down">
3
+ <div class="va-dashboard-header__content">
4
+ <h1 class="va-dashboard-header__title">
5
+ {{ greeting }}<span v-if="userName">, {{ userName }}</span>
6
+ </h1>
7
+ <p class="va-dashboard-header__date">{{ formattedDate }}</p>
8
+ </div>
9
+
10
+ <div class="va-dashboard-header__actions">
11
+ <slot name="actions" />
12
+ </div>
13
+ </div>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ const props = withDefaults(defineProps<{
18
+ userName?: string
19
+ showDate?: boolean
20
+ }>(), {
21
+ userName: '',
22
+ showDate: true
23
+ })
24
+
25
+ const greeting = computed(() => {
26
+ const hour = new Date().getHours()
27
+ if (hour < 12) return 'Good morning'
28
+ if (hour < 17) return 'Good afternoon'
29
+ return 'Good evening'
30
+ })
31
+
32
+ const formattedDate = computed(() => {
33
+ if (!props.showDate) return ''
34
+ return new Date().toLocaleDateString('en-US', {
35
+ weekday: 'long',
36
+ year: 'numeric',
37
+ month: 'long',
38
+ day: 'numeric'
39
+ })
40
+ })
41
+ </script>
42
+
43
+ <style scoped>
44
+ .va-dashboard-header {
45
+ display: flex;
46
+ justify-content: space-between;
47
+ align-items: flex-start;
48
+ gap: 1.5rem;
49
+ flex-wrap: wrap;
50
+ }
51
+
52
+ .va-dashboard-header__content {
53
+ flex: 1;
54
+ min-width: 0;
55
+ }
56
+
57
+ .va-dashboard-header__title {
58
+ font-family: var(--font-display);
59
+ font-size: 1.75rem;
60
+ font-weight: 700;
61
+ color: var(--color-text-primary);
62
+ letter-spacing: -0.02em;
63
+ line-height: 1.3;
64
+ margin: 0;
65
+ }
66
+
67
+ .dark .va-dashboard-header__title {
68
+ color: #ffffff;
69
+ }
70
+
71
+ .va-dashboard-header__date {
72
+ font-size: 0.9375rem;
73
+ color: var(--color-text-muted);
74
+ margin-top: 0.375rem;
75
+ }
76
+
77
+ .va-dashboard-header__actions {
78
+ flex-shrink: 0;
79
+ display: flex;
80
+ gap: 0.75rem;
81
+ }
82
+
83
+ @media (max-width: 640px) {
84
+ .va-dashboard-header {
85
+ flex-direction: column;
86
+ }
87
+
88
+ .va-dashboard-header__actions {
89
+ width: 100%;
90
+ }
91
+
92
+ .va-dashboard-header__actions :deep(button) {
93
+ width: 100%;
94
+ }
95
+ }
96
+ </style>
@@ -0,0 +1,36 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ title?: string
4
+ modelValue?: boolean
5
+ }>()
6
+
7
+ const emit = defineEmits(['update:modelValue'])
8
+
9
+ const isOpen = computed({
10
+ get: () => props.modelValue,
11
+ set: (val) => emit('update:modelValue', val)
12
+ })
13
+ </script>
14
+
15
+ <template>
16
+ <UModal v-model="isOpen">
17
+ <UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
18
+ <template #header>
19
+ <div class="flex items-center justify-between">
20
+ <h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
21
+ {{ title }}
22
+ </h3>
23
+ <UButton color="gray" variant="ghost" icon="i-lucide-x" class="-my-1" @click="isOpen = false" />
24
+ </div>
25
+ </template>
26
+
27
+ <div class="p-4">
28
+ <slot />
29
+ </div>
30
+
31
+ <template v-if="$slots.footer" #footer>
32
+ <slot name="footer" />
33
+ </template>
34
+ </UCard>
35
+ </UModal>
36
+ </template>
@@ -0,0 +1,32 @@
1
+ <script setup lang="ts">
2
+ import { RevenueData, RevenueCategoriesMultiple, xFormatter, yFormatter, DonutData } from '~/data/dashboardData';
3
+ import { computed } from 'vue';
4
+
5
+ const props = defineProps<{ childOrder: [0 | 1, 0 | 1] }>();
6
+
7
+ const flexDirection = computed(() =>
8
+ props.childOrder[0] === 1 ? 'md:flex-row-reverse' : 'md:flex-row'
9
+ );
10
+ </script>
11
+ <template>
12
+ <div :class="`flex flex-col ${flexDirection} w-full px-4 md:px-8 mb-6 gap-6`">
13
+ <div class="w-full md:w-8/12">
14
+ <VaCardsRevenueBarChartCard
15
+ title="Revenue"
16
+ total-value="$4589.00"
17
+ :data="RevenueData"
18
+ :categories="RevenueCategoriesMultiple"
19
+ :y-axis="['total']"
20
+ :x-formatter="xFormatter"
21
+ :y-formatter="yFormatter"
22
+ />
23
+ </div>
24
+ <div class="w-full md:w-4/12 relative">
25
+ <VaCardsDonutChartCard
26
+ title="Revenue"
27
+ total-value="$4589.00"
28
+ :data="DonutData"
29
+ />
30
+ </div>
31
+ </div>
32
+ </template>
@@ -0,0 +1,32 @@
1
+ <script setup lang="ts">
2
+ import { SalesData, RevenueDataCard, ReturnsData } from '~/data/dashboardData';
3
+
4
+ </script>
5
+ <template>
6
+ <div class="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-6 w-full px-4 md:px-8 mb-6">
7
+ <div class="w-full md:col-span-1 xl:col-span-2">
8
+ <VaCardsSalesCard
9
+ title="Sales"
10
+ value="1,245"
11
+ :chart-data="SalesData.chartData"
12
+ :categories="SalesData.categories"
13
+ />
14
+ </div>
15
+ <div class="w-full md:col-span-1 xl:col-span-2">
16
+ <VaCardsRevenueCard
17
+ title="Revenue"
18
+ value="$58,981.00"
19
+ :chart-data="RevenueDataCard.chartData"
20
+ :categories="RevenueDataCard.categories"
21
+ />
22
+ </div>
23
+ <div class="w-full md:col-span-2">
24
+ <VaCardsReturnsCard
25
+ title="Returns"
26
+ value="32"
27
+ :chart-data="ReturnsData.chartData"
28
+ :categories="ReturnsData.categories"
29
+ />
30
+ </div>
31
+ </div>
32
+ </template>
@@ -0,0 +1,23 @@
1
+ <script setup lang="ts">
2
+ import VaCardsLatestOrdersCard from '../Cards/VaCardsLatestOrdersCard.vue';
3
+ import VaCardsPopularProductsCard from '../Cards/VaCardsPopularProductsCard.vue';
4
+ import { orders, products } from '~/data/dashboardData';
5
+
6
+ defineProps<{ childOrder: [0 | 1, 0 | 1] }>();
7
+
8
+ const cardComponents = [VaCardsLatestOrdersCard, VaCardsPopularProductsCard] as const;
9
+ </script>
10
+ <template>
11
+ <div class="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-6 w-full px-4 md:px-8 mb-6">
12
+ <component
13
+ :is="cardComponents[childOrder[0]]"
14
+ v-bind="childOrder[0] === 0 ? { orders } : { products }"
15
+ class="col-span-1 md:col-span-2 xl:col-span-3"
16
+ />
17
+ <component
18
+ :is="cardComponents[childOrder[1]]"
19
+ v-bind="childOrder[1] === 0 ? { orders } : { products }"
20
+ class="col-span-1 md:col-span-2 xl:col-span-3"
21
+ />
22
+ </div>
23
+ </template>
@@ -0,0 +1,8 @@
1
+ <script setup lang="ts">
2
+ // ...
3
+ </script>
4
+ <template>
5
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4">
6
+ <slot />
7
+ </div>
8
+ </template>
@@ -0,0 +1,8 @@
1
+ <script setup lang="ts">
2
+ // ...
3
+ </script>
4
+ <template>
5
+ <div class="flex space-x-2 p-1 rounded-md">
6
+ <slot />
7
+ </div>
8
+ </template>
@@ -0,0 +1,59 @@
1
+ <script setup lang="ts">
2
+ interface DonutChartData {
3
+ name: string;
4
+ value: number;
5
+ color: string;
6
+ }
7
+
8
+ defineProps<{
9
+ data: DonutChartData[];
10
+ title: string;
11
+ totalValue: string;
12
+ }>();
13
+ </script>
14
+ <template>
15
+ <UCard>
16
+ <div>
17
+ <div class="flex items-center justify-between">
18
+ <h3 class="text-base font-normal text-lg text-highlighted mb-1">{{ title }}</h3>
19
+ </div>
20
+ <div class="text-default font-medium dark:font-semibold text-2xl">
21
+ {{ totalValue }}
22
+ </div>
23
+ </div>
24
+ <DonutChart
25
+ :data="data.map((i) => i.value)"
26
+ :height="200"
27
+ :labels="data"
28
+ :hide-legend="true"
29
+ :radius="0"
30
+ >
31
+ <div class="absolute text-center">
32
+ <div class="font-semibold">Categories</div>
33
+ <div class="">Last 24 hours</div>
34
+ </div>
35
+ </DonutChart>
36
+ <div class="w-full space-y-2 divide-y divide-default">
37
+ <div
38
+ class="flex justify-between text-sm text-left py-4 font-medium text-toned tracking-tight"
39
+ >
40
+ <div>Category</div>
41
+ <div>Share</div>
42
+ </div>
43
+ <div
44
+ v-for="(cat, idx) in data"
45
+ :key="idx"
46
+ class="flex w-full items-center justify-between pb-2 text-sm text-toned dark:text-muted"
47
+ >
48
+ <div class="flex items-center gap-4">
49
+ <div
50
+ class="h-4 w-1 rounded"
51
+ :style="{ backgroundColor: cat.color }"
52
+ />
53
+ <div>{{ cat.name }}</div>
54
+ </div>
55
+ <div>{{ cat.value }}%</div>
56
+ </div>
57
+ </div>
58
+ </UCard>
59
+ </template>
@@ -0,0 +1,10 @@
1
+ <template>
2
+ <div class="space-y-2">
3
+ <div class="flex items-start justify-between">
4
+ <div class="space-y-2">
5
+ <slot />
6
+ </div>
7
+ <slot name="action" />
8
+ </div>
9
+ </div>
10
+ </template>
@@ -0,0 +1,17 @@
1
+ <template>
2
+ <UCard>
3
+ <div class="flex items-center justify-between">
4
+ <div class="mb-2"><slot name="label" /></div>
5
+ <slot name="icon"/>
6
+ </div>
7
+ <div class="flex items-baseline mb-2">
8
+ <span class="text-3xl font-medium dark:font-semibold"><slot name="value" /></span>
9
+ <span class="ml-2 text-sm font-medium flex items-center">
10
+ <slot name="trend" />
11
+ </span>
12
+ </div>
13
+ <div class="w-full rounded-md">
14
+ <slot name="chart" />
15
+ </div>
16
+ </UCard>
17
+ </template>
@@ -0,0 +1,55 @@
1
+ <script setup lang="ts">
2
+ interface KpiCategory {
3
+ name: string;
4
+ value: number;
5
+ budget: number;
6
+ color: string;
7
+ trend?: number;
8
+ }
9
+
10
+ defineProps<KpiCategory>();
11
+
12
+ const formatCurrency = (value: number) => {
13
+ return new Intl.NumberFormat("en-US", {
14
+ style: "currency",
15
+ currency: "USD",
16
+ minimumFractionDigits: 0,
17
+ maximumFractionDigits: 0,
18
+ }).format(value);
19
+ };
20
+ </script>
21
+
22
+ <template>
23
+ <UCard>
24
+ <div class="mb-2 flex items-center justify-between">
25
+ <div class="flex items-center gap-4">
26
+ <div class="h-2 w-2 rounded-full" :style="{ backgroundColor: color }" />
27
+ <span class="font-medium text-default">{{ name }}</span>
28
+ </div>
29
+ <div v-if="trend" class="flex items-center gap-2 text-sm">
30
+ <span :class="trend >= 0 ? 'text-red-500' : 'text-(--ui-primary)'">
31
+ {{ trend >= 0 ? "+" : "" }}{{ trend }}%
32
+ </span>
33
+ </div>
34
+ </div>
35
+
36
+ <div class="flex items-center justify-between">
37
+ <div class="text-lg font-semibold text-default">
38
+ {{ formatCurrency(value) }}
39
+ </div>
40
+ <div class="text-sm text-toned dark:text-muted">
41
+ {{ Math.round((value / budget) * 100) }}% of total
42
+ </div>
43
+ </div>
44
+
45
+ <div class="mt-2 h-1.5 overflow-hidden rounded-full bg-elevated">
46
+ <div
47
+ class="h-full rounded-full transition-all duration-300"
48
+ :style="{
49
+ backgroundColor: color,
50
+ width: `${Math.min((value / budget) * 100, 100)}%`,
51
+ }"
52
+ />
53
+ </div>
54
+ </UCard>
55
+ </template>
@@ -0,0 +1,82 @@
1
+ <script setup lang="ts">
2
+ import type { TableColumn } from "@nuxt/ui";
3
+
4
+ export interface Order {
5
+ id: number;
6
+ customer: string;
7
+ date: string;
8
+ total: number;
9
+ status: 'Paid' | 'Pending' | 'Failed';
10
+ }
11
+
12
+ defineProps<{
13
+ orders: Order[];
14
+ }>();
15
+
16
+ const columns: TableColumn<Order>[] = [
17
+ {
18
+ accessorKey: 'id',
19
+ header: 'Order',
20
+ cell: (ctx) => `#${ctx.row.getValue('id')}`
21
+ },
22
+ {
23
+ accessorKey: 'customer',
24
+ header: 'Customer',
25
+ cell: (ctx) => ctx.row.getValue('customer')
26
+ },
27
+ {
28
+ accessorKey: 'date',
29
+ header: 'Date',
30
+ cell: (ctx) => {
31
+ return new Date(ctx.row.getValue('date')).toLocaleString('en-US', {
32
+ day: 'numeric',
33
+ month: 'short',
34
+ hour: '2-digit',
35
+ minute: '2-digit',
36
+ hour12: false
37
+ });
38
+ }
39
+ },
40
+ {
41
+ accessorKey: 'total',
42
+ header: 'Total',
43
+ cell: (ctx) => `${ctx.row.getValue('total')}`
44
+ },
45
+ {
46
+ accessorKey: 'status',
47
+ header: 'Status',
48
+ cell: (ctx) => {
49
+ const status = ctx.row.getValue('status') as Order['status'];
50
+ return h(
51
+ resolveComponent('UBadge'),
52
+ {
53
+ label: status,
54
+ color: status === 'Paid' ? 'success' : status === 'Pending' ? 'warning' : 'error'
55
+ }
56
+ );
57
+ }
58
+ }
59
+ ];
60
+ </script>
61
+ <template>
62
+ <UCard class="w-full">
63
+ <div class="flex items-center justify-between">
64
+ <h3 class="text-base font-semibold text-highlighted">Latest Orders</h3>
65
+ </div>
66
+ <UTable
67
+ :columns="columns"
68
+ :data="orders"
69
+ :ui="{ root: 'bg-transparent dark:bg-transparent !border-hidden dark:border-hidden' }"
70
+ >
71
+ <template #id="{ row }">
72
+ #{{ row.id }}
73
+ </template>
74
+ <template #status="{ row }">
75
+ <UBadge
76
+ :label="row.status"
77
+ :color="row.status === 'Paid' ? 'success' : row.status === 'Pending' ? 'warning' : 'error'"
78
+ />
79
+ </template>
80
+ </UTable>
81
+ </UCard>
82
+ </template>
@@ -0,0 +1,88 @@
1
+ <script setup lang="ts">
2
+ import { h, resolveComponent } from "vue";
3
+ import type { TableColumn } from "@nuxt/ui";
4
+
5
+ interface Product {
6
+ id: number;
7
+ name: string;
8
+ category: string;
9
+ stock: number;
10
+ image: string;
11
+ }
12
+
13
+ defineProps<{
14
+ products: Product[];
15
+ }>();
16
+
17
+ const columns: TableColumn<Product>[] = [
18
+ {
19
+ accessorKey: "image",
20
+ header: "Image",
21
+ cell: (ctx) => {
22
+ const image = ctx.row.getValue("image") as string;
23
+ const name = ctx.row.getValue("name") as string;
24
+ if (image) {
25
+ return h(
26
+ "div",
27
+ {
28
+ class:
29
+ "w-10 h-10 rounded bg-muted flex items-center justify-center overflow-hidden",
30
+ },
31
+ [h("img", { src: image, alt: name, class: "object-contain" })]
32
+ );
33
+ }
34
+ return h(
35
+ "div",
36
+ {
37
+ class:
38
+ "w-10 h-10 rounded bg-muted flex items-center justify-center overflow-hidden",
39
+ },
40
+ [h("span", { class: "i-lucide-image text-2xl text-dimmed" })]
41
+ );
42
+ },
43
+ },
44
+ {
45
+ accessorKey: "name",
46
+ header: "Product",
47
+ cell: (ctx) => ctx.row.getValue("name"),
48
+ },
49
+ {
50
+ accessorKey: "stock",
51
+ header: "Stock",
52
+ cell: (ctx) => {
53
+ const stock = ctx.row.getValue("stock") as number;
54
+ let label = "In stock",
55
+ color = "success";
56
+ if (stock === 0) {
57
+ label = "Out of stock";
58
+ color = "error";
59
+ } else if (stock < 10) {
60
+ label = "Low stock";
61
+ color = "warning";
62
+ }
63
+ return h(resolveComponent("UBadge"), {
64
+ label,
65
+ color,
66
+ variant: "subtle",
67
+ class: "font-medium rounded-full px-3 py-1 capitalize",
68
+ });
69
+ },
70
+ },
71
+ ];
72
+ </script>
73
+
74
+ <template>
75
+ <UCard class="w-full">
76
+ <div class="flex items-center justify-between">
77
+ <h3 class="text-base font-semibold text-highlighted">Popular Products</h3>
78
+ </div>
79
+ <UTable
80
+ :columns="columns"
81
+ :data="products"
82
+ class="w-full"
83
+ :ui="{
84
+ root: 'bg-transparent dark:bg-transparent !border-hidden dark:border-hidden',
85
+ }"
86
+ />
87
+ </UCard>
88
+ </template>