@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,114 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import {
|
|
3
|
+
getDailyWebsiteStatistics,
|
|
4
|
+
getWeeklyWebsiteStatistics,
|
|
5
|
+
getMonthlyWebsiteStatistics
|
|
6
|
+
} from '~/data/WebsiteStatistics'
|
|
7
|
+
|
|
8
|
+
defineProps<{
|
|
9
|
+
categories: Record<string, BulletLegendItemInterface>
|
|
10
|
+
}>()
|
|
11
|
+
|
|
12
|
+
const isSettingsOpen = ref<boolean>(false)
|
|
13
|
+
|
|
14
|
+
function toggleSettings() {
|
|
15
|
+
isSettingsOpen.value = !isSettingsOpen.value
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const dataViewOptions = [
|
|
19
|
+
{
|
|
20
|
+
label: 'Daily',
|
|
21
|
+
value: 'daily'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
label: 'Weekly',
|
|
25
|
+
value: 'weekly'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
label: 'Monthly',
|
|
29
|
+
value: 'monthly'
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
const selectedDataView = ref('daily')
|
|
34
|
+
|
|
35
|
+
const WebsiteStatistics = computed(() => {
|
|
36
|
+
switch (selectedDataView.value) {
|
|
37
|
+
case 'weekly':
|
|
38
|
+
return getWeeklyWebsiteStatistics.value
|
|
39
|
+
case 'monthly':
|
|
40
|
+
return getMonthlyWebsiteStatistics.value
|
|
41
|
+
case 'daily':
|
|
42
|
+
default:
|
|
43
|
+
return getDailyWebsiteStatistics.value
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const xFormatter = (i: number): string =>
|
|
48
|
+
String(WebsiteStatistics.value[i]?.date ?? '')
|
|
49
|
+
|
|
50
|
+
function handleDataViewChange(value: string) {
|
|
51
|
+
console.log('Data view changed to:', value)
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template>
|
|
56
|
+
<UCard
|
|
57
|
+
:ui="{
|
|
58
|
+
body: 'sm:p-4 p-0'
|
|
59
|
+
}"
|
|
60
|
+
>
|
|
61
|
+
<template #header>
|
|
62
|
+
<div class="w-full flex items-center justify-between">
|
|
63
|
+
<h2 class="text-lg font-medium text-highlighted">
|
|
64
|
+
Web Performance
|
|
65
|
+
</h2>
|
|
66
|
+
<UButton
|
|
67
|
+
variant="ghost"
|
|
68
|
+
:icon="`i-lucide-${!isSettingsOpen ? 'settings' : 'x'}`"
|
|
69
|
+
@click="toggleSettings"
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
</template>
|
|
73
|
+
|
|
74
|
+
<div
|
|
75
|
+
v-if="isSettingsOpen"
|
|
76
|
+
class="h-[304px] p-4"
|
|
77
|
+
>
|
|
78
|
+
<div class="w-full space-y-4">
|
|
79
|
+
<label class="block text-sm font-medium text-highlighted mb-2">
|
|
80
|
+
Settings
|
|
81
|
+
</label>
|
|
82
|
+
<USelect
|
|
83
|
+
v-model="selectedDataView"
|
|
84
|
+
:items="dataViewOptions"
|
|
85
|
+
placeholder="Select data view"
|
|
86
|
+
class="w-full focus:ring-0"
|
|
87
|
+
@update:model-value="handleDataViewChange"
|
|
88
|
+
/>
|
|
89
|
+
<UButton
|
|
90
|
+
variant="soft"
|
|
91
|
+
class="w-full flex items-center justify-center"
|
|
92
|
+
label="Filter data"
|
|
93
|
+
@click="toggleSettings"
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div
|
|
99
|
+
v-if="!isSettingsOpen"
|
|
100
|
+
class="pt-2"
|
|
101
|
+
>
|
|
102
|
+
<LineChart
|
|
103
|
+
:data="WebsiteStatistics"
|
|
104
|
+
:height="240"
|
|
105
|
+
y-label="Performance"
|
|
106
|
+
:x-num-ticks="6"
|
|
107
|
+
:categories="categories"
|
|
108
|
+
:x-formatter="xFormatter"
|
|
109
|
+
:legend-position="LegendPosition.TopRight"
|
|
110
|
+
:curve-type="CurveType.MonotoneX"
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
</UCard>
|
|
114
|
+
</template>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { TabsItem } from '@nuxt/ui'
|
|
3
|
+
|
|
4
|
+
interface DailyData {
|
|
5
|
+
day: string
|
|
6
|
+
Income: number
|
|
7
|
+
Expense: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const items = ref<TabsItem[]>([
|
|
11
|
+
{
|
|
12
|
+
label: 'Year'
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
label: 'Month'
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
label: 'Week'
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
label: 'Day'
|
|
22
|
+
}
|
|
23
|
+
])
|
|
24
|
+
|
|
25
|
+
const chartData = ref<DailyData[]>([
|
|
26
|
+
{
|
|
27
|
+
day: 'Jan',
|
|
28
|
+
Income: 8500,
|
|
29
|
+
Expense: 4000
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
day: 'Feb',
|
|
33
|
+
Income: 6500,
|
|
34
|
+
Expense: 9000
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
day: 'Mar',
|
|
38
|
+
Income: 7500,
|
|
39
|
+
Expense: 3500
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
day: 'Apr',
|
|
43
|
+
Income: 5800,
|
|
44
|
+
Expense: 7000
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
day: 'May',
|
|
48
|
+
Income: 9200,
|
|
49
|
+
Expense: 6500
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
day: 'Jun',
|
|
53
|
+
Income: 9800,
|
|
54
|
+
Expense: 4500
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
day: 'Jul',
|
|
58
|
+
Income: 5000,
|
|
59
|
+
Expense: 8500
|
|
60
|
+
}
|
|
61
|
+
])
|
|
62
|
+
|
|
63
|
+
const chartCategories: Record<string, BulletLegendItemInterface> = {
|
|
64
|
+
Income: {
|
|
65
|
+
name: 'Income',
|
|
66
|
+
color: '#10b981'
|
|
67
|
+
},
|
|
68
|
+
Expense: {
|
|
69
|
+
name: 'Expense',
|
|
70
|
+
color: '#f59e0b'
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const xAxisFormatter = (tick: number): string => {
|
|
75
|
+
// tick is the index of the data point
|
|
76
|
+
const months = chartData.value.map(item => item.day)
|
|
77
|
+
return months[tick] || ''
|
|
78
|
+
}
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<template>
|
|
82
|
+
<div class="space-y-8">
|
|
83
|
+
<BarChart
|
|
84
|
+
:data="chartData"
|
|
85
|
+
:categories="chartCategories"
|
|
86
|
+
:y-axis="['Income', 'Expense']"
|
|
87
|
+
:x-formatter="xAxisFormatter"
|
|
88
|
+
:height="160"
|
|
89
|
+
:stacked="false"
|
|
90
|
+
:radius="3"
|
|
91
|
+
:group-padding="0.2"
|
|
92
|
+
:bar-padding="0.2"
|
|
93
|
+
:hide-legend="true"
|
|
94
|
+
:x-grid-line="true"
|
|
95
|
+
:y-grid-line="true"
|
|
96
|
+
:y-num-ticks="4"
|
|
97
|
+
/>
|
|
98
|
+
<UTabs
|
|
99
|
+
:content="false"
|
|
100
|
+
:items="items"
|
|
101
|
+
color="neutral"
|
|
102
|
+
class="w-full"
|
|
103
|
+
/>
|
|
104
|
+
<p class="text-center text-dimmed px-16">
|
|
105
|
+
Your win % is higher on <span class="text-primary">8%</span> compared to
|
|
106
|
+
<span class="text-primary">47 winning</span> / <span>33 losing</span> past
|
|
107
|
+
month
|
|
108
|
+
</p>
|
|
109
|
+
</div>
|
|
110
|
+
</template>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { TabsItem } from '@nuxt/ui'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
hideText?: boolean
|
|
6
|
+
}>()
|
|
7
|
+
|
|
8
|
+
const items = ref<TabsItem[]>([
|
|
9
|
+
{
|
|
10
|
+
label: 'Year'
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
label: 'Month'
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
label: 'Week'
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
label: 'Day'
|
|
20
|
+
}
|
|
21
|
+
])
|
|
22
|
+
|
|
23
|
+
const win = ref(32)
|
|
24
|
+
const loss = ref(18)
|
|
25
|
+
|
|
26
|
+
const winLossData = computed(() => [
|
|
27
|
+
{
|
|
28
|
+
color: '#10b981',
|
|
29
|
+
name: 'Winning',
|
|
30
|
+
value: win.value
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
color: '#ef4444',
|
|
34
|
+
name: 'Lost',
|
|
35
|
+
value: loss.value
|
|
36
|
+
}
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
const percentage = computed(() => {
|
|
40
|
+
const total = win.value + loss.value
|
|
41
|
+
if (total === 0) return '0%'
|
|
42
|
+
return Math.round((win.value / total) * 100)
|
|
43
|
+
})
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<UCard>
|
|
48
|
+
<div class="space-y-8">
|
|
49
|
+
<h2 class="text-lg font-semibold">
|
|
50
|
+
Spending Trend
|
|
51
|
+
</h2>
|
|
52
|
+
|
|
53
|
+
<Flex class="gap-8 space-y-4">
|
|
54
|
+
<div class="relative w-46">
|
|
55
|
+
<DonutChart
|
|
56
|
+
:data="winLossData.map((i) => i.value)"
|
|
57
|
+
:height="160"
|
|
58
|
+
:arc-width="10"
|
|
59
|
+
:labels="
|
|
60
|
+
winLossData.map((item) => ({
|
|
61
|
+
name: item.name,
|
|
62
|
+
color: item.color,
|
|
63
|
+
percentage: item.value
|
|
64
|
+
}))
|
|
65
|
+
"
|
|
66
|
+
:pad-angle="0.1"
|
|
67
|
+
:hide-legend="true"
|
|
68
|
+
:radius="4"
|
|
69
|
+
>
|
|
70
|
+
<div
|
|
71
|
+
class="absolute text-center w-full top-1/2 left-1/2"
|
|
72
|
+
style="transform: translate(-50%, -50%)"
|
|
73
|
+
>
|
|
74
|
+
<div class="text-2xl font-bold">
|
|
75
|
+
{{ percentage }}<span class="text-toned">%</span>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</DonutChart>
|
|
79
|
+
</div>
|
|
80
|
+
<FlexCol class="ml-6 h-[160px] w-[120px]">
|
|
81
|
+
<FlexCol class="h-1/2 space-y-2">
|
|
82
|
+
<div class="text-muted">
|
|
83
|
+
Winning trades
|
|
84
|
+
</div>
|
|
85
|
+
<div class="text-3xl font-bold">
|
|
86
|
+
{{ winLossData[0]?.value ?? 0 }}
|
|
87
|
+
</div>
|
|
88
|
+
</FlexCol>
|
|
89
|
+
<FlexCol class="h-1/2 space-y-2">
|
|
90
|
+
<div class="text-muted">
|
|
91
|
+
Losing trades
|
|
92
|
+
</div>
|
|
93
|
+
<div class="text-3xl font-bold">
|
|
94
|
+
{{ winLossData[1]?.value ?? 0 }}
|
|
95
|
+
</div>
|
|
96
|
+
</FlexCol>
|
|
97
|
+
</FlexCol>
|
|
98
|
+
</Flex>
|
|
99
|
+
<UTabs
|
|
100
|
+
:content="false"
|
|
101
|
+
:items="items"
|
|
102
|
+
color="neutral"
|
|
103
|
+
class="w-full"
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
</UCard>
|
|
107
|
+
</template>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { TabsItem } from '@nuxt/ui'
|
|
3
|
+
|
|
4
|
+
interface DailyData {
|
|
5
|
+
day: string
|
|
6
|
+
Income: number
|
|
7
|
+
Expense: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const items = ref<TabsItem[]>([
|
|
11
|
+
{
|
|
12
|
+
label: 'Year'
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
label: 'Month'
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
label: 'Week'
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
label: 'Day'
|
|
22
|
+
}
|
|
23
|
+
])
|
|
24
|
+
|
|
25
|
+
const chartData = ref<DailyData[]>([
|
|
26
|
+
{
|
|
27
|
+
day: 'Jan',
|
|
28
|
+
Income: 8500,
|
|
29
|
+
Expense: 4000
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
day: 'Feb',
|
|
33
|
+
Income: 6500,
|
|
34
|
+
Expense: 9000
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
day: 'Mar',
|
|
38
|
+
Income: 7500,
|
|
39
|
+
Expense: 3500
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
day: 'Apr',
|
|
43
|
+
Income: 5800,
|
|
44
|
+
Expense: 7000
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
day: 'May',
|
|
48
|
+
Income: 9200,
|
|
49
|
+
Expense: 6500
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
day: 'Jun',
|
|
53
|
+
Income: 9800,
|
|
54
|
+
Expense: 4500
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
day: 'Jul',
|
|
58
|
+
Income: 5000,
|
|
59
|
+
Expense: 8500
|
|
60
|
+
}
|
|
61
|
+
])
|
|
62
|
+
|
|
63
|
+
const chartCategories: Record<string, BulletLegendItemInterface> = {
|
|
64
|
+
Income: {
|
|
65
|
+
name: 'Income',
|
|
66
|
+
color: '#10b981'
|
|
67
|
+
},
|
|
68
|
+
Expense: {
|
|
69
|
+
name: 'Expense',
|
|
70
|
+
color: '#f59e0b'
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const xAxisFormatter = (tick: number): string => {
|
|
75
|
+
// tick is the index of the data point
|
|
76
|
+
const months = chartData.value.map(item => item.day)
|
|
77
|
+
return months[tick] || ''
|
|
78
|
+
}
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<template>
|
|
82
|
+
<div class="space-y-8">
|
|
83
|
+
<LineChart
|
|
84
|
+
:data="chartData"
|
|
85
|
+
:categories="chartCategories"
|
|
86
|
+
:y-axis="['Income', 'Expense']"
|
|
87
|
+
:x-formatter="xAxisFormatter"
|
|
88
|
+
:height="160"
|
|
89
|
+
:stacked="false"
|
|
90
|
+
:radius="3"
|
|
91
|
+
:group-padding="0.2"
|
|
92
|
+
:bar-padding="0.2"
|
|
93
|
+
:hide-legend="true"
|
|
94
|
+
:x-grid-line="true"
|
|
95
|
+
:y-grid-line="true"
|
|
96
|
+
:y-num-ticks="4"
|
|
97
|
+
/>
|
|
98
|
+
<UTabs
|
|
99
|
+
:content="false"
|
|
100
|
+
:items="items"
|
|
101
|
+
color="neutral"
|
|
102
|
+
class="w-full"
|
|
103
|
+
/>
|
|
104
|
+
|
|
105
|
+
<p class="text-center text-dimmed px-16">
|
|
106
|
+
Your win % is higher on <span class="text-primary">8%</span> compared to
|
|
107
|
+
<span class="text-primary">47 winning</span> / <span>33 losing</span> past
|
|
108
|
+
month
|
|
109
|
+
</p>
|
|
110
|
+
</div>
|
|
111
|
+
</template>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
export interface KPIStat {
|
|
3
|
+
label: string
|
|
4
|
+
value: string | number
|
|
5
|
+
change?: string | number
|
|
6
|
+
changeType?: 'positive' | 'negative' | 'neutral'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
defineProps<KPIStat>()
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<UCard :key="label">
|
|
14
|
+
<div class="space-y-2">
|
|
15
|
+
<div class="flex items-center justify-between">
|
|
16
|
+
<h2 class="text-xl font-semibold">
|
|
17
|
+
{{ value }}
|
|
18
|
+
</h2>
|
|
19
|
+
<UBadge
|
|
20
|
+
color="neutral"
|
|
21
|
+
size="lg"
|
|
22
|
+
>
|
|
23
|
+
{{ change }}
|
|
24
|
+
</UBadge>
|
|
25
|
+
</div>
|
|
26
|
+
<p class="text-muted">
|
|
27
|
+
{{ label }}
|
|
28
|
+
</p>
|
|
29
|
+
</div>
|
|
30
|
+
</UCard>
|
|
31
|
+
</template>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useRoute, useRouter } from '#imports'
|
|
3
|
+
import type { NavSection } from './Navigation/types'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
sections: NavSection[]
|
|
7
|
+
}>()
|
|
8
|
+
|
|
9
|
+
const route = useRoute()
|
|
10
|
+
const router = useRouter()
|
|
11
|
+
|
|
12
|
+
const items = computed(() =>
|
|
13
|
+
props.sections
|
|
14
|
+
.flatMap(section => section.children)
|
|
15
|
+
.map(child => ({
|
|
16
|
+
label: child.label,
|
|
17
|
+
icon: child.leadingIcon,
|
|
18
|
+
value: child.href
|
|
19
|
+
}))
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
const active = ref<string | undefined>(items.value[0]?.value)
|
|
23
|
+
|
|
24
|
+
watch(
|
|
25
|
+
() => [route.path, route.hash, items.value],
|
|
26
|
+
() => {
|
|
27
|
+
const match = items.value.find(item => item.value === route.path || item.value === route.hash)
|
|
28
|
+
active.value = match ? match.value : items.value[0]?.value
|
|
29
|
+
},
|
|
30
|
+
{ immediate: true }
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
watch(
|
|
34
|
+
active,
|
|
35
|
+
(val) => {
|
|
36
|
+
if (val && val !== route.path) {
|
|
37
|
+
router.push(val)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<template>
|
|
44
|
+
<UTabs
|
|
45
|
+
v-model="active"
|
|
46
|
+
:items="items"
|
|
47
|
+
color="neutral"
|
|
48
|
+
:content="false"
|
|
49
|
+
/>
|
|
50
|
+
</template>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
interface Feature {
|
|
3
|
+
label: string
|
|
4
|
+
available: boolean
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface PricePlanProps {
|
|
8
|
+
name: string
|
|
9
|
+
description: string
|
|
10
|
+
price: string
|
|
11
|
+
priceSuffix?: string
|
|
12
|
+
per?: string
|
|
13
|
+
features: Feature[]
|
|
14
|
+
current?: boolean
|
|
15
|
+
badge?: string
|
|
16
|
+
buttonLabel?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
defineProps<PricePlanProps>()
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<UCard
|
|
24
|
+
:class="[
|
|
25
|
+
'relative transition-all duration-200',
|
|
26
|
+
current
|
|
27
|
+
? 'ring-2 ring-success bg-success/5'
|
|
28
|
+
: 'hover:shadow-lg hover:-translate-y-1'
|
|
29
|
+
]"
|
|
30
|
+
>
|
|
31
|
+
<div
|
|
32
|
+
v-if="badge"
|
|
33
|
+
class="absolute -top-3 left-1/2 -translate-x-1/2"
|
|
34
|
+
>
|
|
35
|
+
<UBadge
|
|
36
|
+
:label="badge"
|
|
37
|
+
variant="solid"
|
|
38
|
+
color="success"
|
|
39
|
+
size="sm"
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="text-center">
|
|
44
|
+
<div>
|
|
45
|
+
<h3 class="text-xl font-bold text-highlighted">
|
|
46
|
+
{{ name }}
|
|
47
|
+
</h3>
|
|
48
|
+
<p class="text-dimmed mt-1">
|
|
49
|
+
{{ description }}
|
|
50
|
+
</p>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div class="border-y border-default my-4 py-4">
|
|
54
|
+
<div class="mb-4">
|
|
55
|
+
<div class="text-3xl font-bold text-success space-x-2">
|
|
56
|
+
<span>{{ price }}</span>
|
|
57
|
+
<span
|
|
58
|
+
v-if="priceSuffix"
|
|
59
|
+
class="text-base text-toned"
|
|
60
|
+
>{{
|
|
61
|
+
priceSuffix
|
|
62
|
+
}}</span>
|
|
63
|
+
</div>
|
|
64
|
+
<div
|
|
65
|
+
v-if="per"
|
|
66
|
+
class="text-sm text-muted"
|
|
67
|
+
>
|
|
68
|
+
{{ per }}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<UButton
|
|
73
|
+
:label="buttonLabel"
|
|
74
|
+
:variant="current ? 'outline' : 'solid'"
|
|
75
|
+
:color="current ? 'success' : 'success'"
|
|
76
|
+
class="w-full justify-center"
|
|
77
|
+
:disabled="current"
|
|
78
|
+
:icon="current ? 'i-lucide-check' : 'i-lucide-arrow-up-right'"
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<div class="space-y-4 mt-8">
|
|
83
|
+
<div
|
|
84
|
+
v-for="(feature, i) in features"
|
|
85
|
+
:key="i"
|
|
86
|
+
class="flex items-center justify-start gap-2 text-sm"
|
|
87
|
+
>
|
|
88
|
+
<UIcon
|
|
89
|
+
:name="feature.available ? 'i-lucide-check' : 'i-lucide-x'"
|
|
90
|
+
:class="feature.available ? 'text-success' : 'text-error'"
|
|
91
|
+
size="16"
|
|
92
|
+
/>
|
|
93
|
+
<span
|
|
94
|
+
:class="{
|
|
95
|
+
'text-dimmed': !feature.available
|
|
96
|
+
}"
|
|
97
|
+
>{{ feature.label }}</span>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</UCard>
|
|
102
|
+
</template>
|