Initial commit

This commit is contained in:
2025-08-04 16:33:07 +03:30
commit f798e8e35c
9595 changed files with 1208683 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
<script setup>
import congoImg from '@images/illustrations/congo-illustration.png'
</script>
<template>
<VCard>
<VRow no-gutters>
<VCol cols="8">
<VCardText>
<h5 class="text-h5 text-no-wrap">
Congratulations John! 🎉
</h5>
<p class="mb-2">
Best seller of the month
</p>
<h4 class="text-h4 text-primary mb-1">
$48.9k
</h4>
<VBtn>View Sales</VBtn>
</VCardText>
</VCol>
<VCol cols="4">
<VCardText class="pb-0 px-0 position-relative h-100">
<VImg
:src="congoImg"
:height="$vuetify.display.smAndUp ? 147 : 125"
class="congo-john-img w-100"
/>
</VCardText>
</VCol>
</VRow>
</VCard>
</template>
<style lang="scss" scoped>
.congo-john-img {
position: absolute;
inset-block-end: 0;
inset-inline-end: 1.25rem;
}
</style>

View File

@@ -0,0 +1,199 @@
<script setup>
import { useTheme } from 'vuetify'
import { hexToRgb } from '@layouts/utils'
const vuetifyTheme = useTheme()
const series = [{
data: [
40,
95,
60,
45,
90,
50,
75,
],
}]
const chartOptions = computed(() => {
const currentTheme = vuetifyTheme.current.value.colors
const variableTheme = vuetifyTheme.current.value.variables
const labelColor = `rgba(${ hexToRgb(currentTheme['on-background']) },${ variableTheme['disabled-opacity'] })`
const labelPrimaryColor = `rgba(${ hexToRgb(currentTheme.primary) },0.1)`
return {
chart: {
type: 'bar',
toolbar: { show: false },
},
tooltip: { enabled: false },
plotOptions: {
bar: {
barHeight: '60%',
columnWidth: '60%',
startingShape: 'rounded',
endingShape: 'rounded',
borderRadius: 4,
distributed: true,
},
},
grid: {
show: false,
padding: {
top: -20,
bottom: 0,
left: -10,
right: -10,
},
},
colors: [
labelPrimaryColor,
labelPrimaryColor,
labelPrimaryColor,
labelPrimaryColor,
`rgba(${ hexToRgb(currentTheme.primary) }, 1)`,
labelPrimaryColor,
labelPrimaryColor,
],
dataLabels: { enabled: false },
legend: { show: false },
xaxis: {
categories: [
'Mo',
'Tu',
'We',
'Th',
'Fr',
'Sa',
'Su',
],
axisBorder: { show: false },
axisTicks: { show: false },
labels: {
style: {
colors: labelColor,
fontSize: '13px',
},
},
},
yaxis: { labels: { show: false } },
}
})
const earningReports = [
{
avatarIcon: 'tabler-chart-pie-2',
avatarColor: 'primary',
title: 'Net Profit',
subtitle: '12.4k Sales',
earnings: '$1,619',
percentage: '18.6%',
},
{
avatarIcon: 'tabler-currency-dollar',
avatarColor: 'success',
title: 'Total Income',
subtitle: 'Sales, Affiliation',
earnings: '$3,571',
percentage: '39.6%',
},
{
avatarIcon: 'tabler-credit-card',
avatarColor: 'secondary',
title: 'Total Expenses',
subtitle: 'ADVT, Marketing',
earnings: '$430',
percentage: '52.8%',
},
]
const moreList = [
{
title: 'Refresh',
value: 'refresh',
},
{
title: 'Download',
value: 'Download',
},
{
title: 'View All',
value: 'View All',
},
]
</script>
<template>
<VCard
title="Earning Reports"
subtitle="Weekly Earnings Overview"
>
<template #append>
<div class="mt-n4 me-n2">
<MoreBtn
size="small"
:menu-list="moreList"
/>
</div>
</template>
<VCardText>
<VList class="card-list mb-5">
<VListItem
v-for="report in earningReports"
:key="report.title"
>
<template #prepend>
<VAvatar
rounded
size="34"
variant="tonal"
:color="report.avatarColor"
class="me-1"
>
<VIcon
:icon="report.avatarIcon"
size="22"
/>
</VAvatar>
</template>
<VListItemTitle class="font-weight-medium me-4">
{{ report.title }}
</VListItemTitle>
<VListItemSubtitle class="me-4">
{{ report.subtitle }}
</VListItemSubtitle>
<template #append>
<div class="d-flex align-center text-body-2">
<span class="me-4">{{ report.earnings }}</span>
<VIcon
color="success"
icon="tabler-chevron-up"
size="20"
class="me-1"
/>
<span class="text-disabled">{{ report.percentage }}</span>
</div>
</template>
</VListItem>
</VList>
<div>
<VueApexCharts
:options="chartOptions"
:series="series"
:height="196"
/>
</div>
</VCardText>
</VCard>
</template>
<style lang="scss" scoped>
.card-list {
--v-card-list-gap: 1.25rem;
}
</style>

View File

@@ -0,0 +1,115 @@
<script setup>
import { useTheme } from 'vuetify'
import { hexToRgb } from '@layouts/utils'
const vuetifyTheme = useTheme()
const series = [78]
const chartOptions = computed(() => {
const currentTheme = vuetifyTheme.current.value.colors
const variableTheme = vuetifyTheme.current.value.variables
return {
chart: {
sparkline: { enabled: true },
parentHeightOffset: 0,
type: 'radialBar',
},
colors: ['rgba(var(--v-theme-warning), 1)'],
plotOptions: {
radialBar: {
offsetY: 0,
startAngle: -90,
endAngle: 90,
hollow: { size: '65%' },
track: {
strokeWidth: '45%',
background: 'rgba(var(--v-track-bg))',
},
dataLabels: {
name: { show: false },
value: {
fontSize: '24px',
color: `rgba(${ hexToRgb(currentTheme['on-background']) },${ variableTheme['high-emphasis-opacity'] })`,
fontWeight: 600,
offsetY: -5,
},
},
},
},
grid: {
show: false,
padding: { bottom: 5 },
},
stroke: { lineCap: 'round' },
labels: ['Progress'],
responsive: [
{
breakpoint: 1442,
options: {
chart: { height: 140 },
plotOptions: {
radialBar: {
dataLabels: { value: { fontSize: '24px' } },
hollow: { size: '60%' },
},
},
},
},
{
breakpoint: 1370,
options: { chart: { height: 120 } },
},
{
breakpoint: 1280,
options: {
chart: { height: 200 },
plotOptions: {
radialBar: {
dataLabels: { value: { fontSize: '18px' } },
hollow: { size: '70%' },
},
},
},
},
{
breakpoint: 960,
options: {
chart: { height: 250 },
plotOptions: {
radialBar: {
hollow: { size: '70%' },
dataLabels: { value: { fontSize: '24px' } },
},
},
},
},
],
}
})
</script>
<template>
<VCard>
<VCardItem class="pb-3">
<VCardTitle>
82.5K
</VCardTitle>
<VCardSubtitle>
Expenses
</VCardSubtitle>
</VCardItem>
<VCardText>
<VueApexCharts
:options="chartOptions"
:series="series"
type="radialBar"
:height="135"
/>
<div class="text-sm text-center clamp-text text-disabled mt-3">
$21k Expenses more than last month
</div>
</VCardText>
</VCard>
</template>

View File

@@ -0,0 +1,447 @@
<script setup>
import { useDisplay, useTheme } from 'vuetify'
import { hexToRgb } from '@layouts/utils'
import { computed, onMounted, onUnmounted, ref } from 'vue'
const props = defineProps({
donutColors: {
type: Array,
default: () => []
},
progress: {
type: Number,
default: 75
}
})
const vuetifyTheme = useTheme()
const display = useDisplay()
const cardRef = ref(null)
const cardWidth = ref(0)
const updateCardWidth = () => {
if (cardRef.value) {
cardWidth.value = cardRef.value.offsetWidth
}
}
onMounted(() => {
updateCardWidth()
window.addEventListener('resize', updateCardWidth)
})
onUnmounted(() => {
window.removeEventListener('resize', updateCardWidth)
})
const chartSeries = computed(() => [props.progress, 100 - props.progress])
const processColors = (colors) => {
const currentTheme = vuetifyTheme.current.value.colors
return colors.map(color => {
if (typeof color === 'string' && currentTheme[color]) {
return currentTheme[color]
}
return color
})
}
const chartSize = computed(() => {
const width = cardWidth.value || 300
let size
if (width < 250) {
size = Math.max(120, width * 0.5)
} else if (width < 350) {
size = Math.max(150, width * 0.5)
} else if (width < 450) {
size = Math.max(180, width * 0.45)
} else if (width < 600) {
size = Math.max(200, width * 0.4)
} else if (width < 800) {
size = Math.max(240, width * 0.35)
} else {
size = Math.min(300, width * 0.32)
}
if (display.xs.value) size = Math.max(size, 120)
if (display.sm.value) size = Math.max(size, 160)
if (display.md.value) size = Math.max(size, 200)
return { width: size, height: size }
})
const textSizes = computed(() => {
const width = cardWidth.value || 300
if (width < 250 || display.xs.value) {
return {
title: 'text-body-2',
subtitle: 'text-caption',
mainNumber: 'text-h6',
percentage: 'text-body-2'
}
} else if (width < 350 || display.sm.value) {
return {
title: 'text-body-1',
subtitle: 'text-body-2',
mainNumber: 'text-h5',
percentage: 'text-body-2'
}
} else if (width < 450) {
return {
title: 'text-h6',
subtitle: 'text-body-2',
mainNumber: 'text-h4',
percentage: 'text-body-1'
}
} else {
return {
title: display.md.value ? 'text-h5' : 'text-h4',
subtitle: 'text-body-1',
mainNumber: display.lg.value ? 'text-h2' : 'text-h3',
percentage: 'text-body-1'
}
}
})
const chartOptions = computed(() => {
const currentTheme = vuetifyTheme.current.value.colors
const variableTheme = vuetifyTheme.current.value.variables
const width = cardWidth.value || 300
const defaultDonutColors = [
currentTheme.success,
]
const usedDonutColors = props.donutColors.length
? processColors(props.donutColors)
: defaultDonutColors
const headingColor = `rgba(${hexToRgb(currentTheme['on-background'])},${variableTheme['high-emphasis-opacity']})`
const backgroundCircleColor = `rgba(${hexToRgb(usedDonutColors[0])}, 0.15)`
const chartSizeValue = chartSize.value.width
const valueFontSize = width < 250 ? '1.1rem' :
width < 350 ? '1.3rem' :
width < 450 ? '1.5rem' :
chartSizeValue < 160 ? '1.3rem' :
chartSizeValue < 200 ? '1.6rem' :
chartSizeValue < 250 ? '1.8rem' : '2rem'
const labelFontSize = width < 250 ? '0.8rem' :
width < 350 ? '0.9rem' :
chartSizeValue < 160 ? '0.9rem' :
chartSizeValue < 200 ? '1rem' : '1.1rem'
const donutSize = width < 300 ? '65%' :
width < 400 ? '70%' :
chartSizeValue < 180 ? '70%' :
chartSizeValue < 220 ? '75%' : '80%'
return {
chart: {
parentHeightOffset: 0,
type: 'donut',
sparkline: {
enabled: false
},
},
labels: ['Progress', 'Remaining'],
colors: [...usedDonutColors, backgroundCircleColor],
stroke: {
width: 0
},
dataLabels: {
enabled: false
},
legend: {
show: false
},
tooltip: {
enabled: true,
theme: 'dark',
style: {
fontSize: width < 300 ? '12px' : '14px'
}
},
grid: {
padding: {
top: 0,
bottom: 0,
right: 0,
left: 0,
},
},
states: {
hover: {
filter: {
type: 'none'
}
}
},
plotOptions: {
pie: {
startAngle: -90,
endAngle: 270,
donut: {
size: donutSize,
background: 'transparent',
labels: {
show: true,
value: {
show: true,
fontSize: valueFontSize,
fontFamily: 'Public Sans',
color: headingColor,
fontWeight: 600,
offsetY: chartSizeValue < 140 ? -2 : 0,
formatter(val) {
return `${Math.round(val)}%`
},
},
name: {
show: false
},
total: {
show: true,
showAlways: true,
color: usedDonutColors[0],
fontSize: labelFontSize,
label: 'Progress',
fontFamily: 'Public Sans',
fontWeight: 500,
formatter() {
return `${props.progress}%`
},
},
},
},
},
},
responsive: [
{
breakpoint: 280,
options: {
chart: {
width: 120,
height: 120
},
plotOptions: {
pie: {
donut: {
size: '65%',
labels: {
value: {
fontSize: '1.1rem',
offsetY: -2
},
total: {
fontSize: '0.8rem'
}
}
}
}
}
}
},
{
breakpoint: 400,
options: {
chart: {
width: 150,
height: 150
},
plotOptions: {
pie: {
donut: {
size: '70%',
labels: {
value: {
fontSize: '1.3rem',
offsetY: -1
},
total: {
fontSize: '0.9rem'
}
}
}
}
}
}
},
{
breakpoint: 600,
options: {
chart: {
width: 180,
height: 180
},
plotOptions: {
pie: {
donut: {
size: '75%',
labels: {
value: {
fontSize: '1.5rem'
},
total: {
fontSize: '1rem'
}
}
}
}
}
}
},
{
breakpoint: 1024,
options: {
chart: {
width: 240,
height: 240
},
plotOptions: {
pie: {
donut: {
size: '80%',
labels: {
value: {
fontSize: '1.8rem'
},
total: {
fontSize: '1.1rem'
}
}
}
}
}
}
}
]
}
})
const cardBackgroundStyle = computed(() => {
const defaultDonutColors = [
vuetifyTheme.current.value.colors.success,
]
const colors = props.donutColors.length ? processColors(props.donutColors) : defaultDonutColors
const createGradientColor = (color, opacity = 0.08) => {
if (color.includes('rgba')) {
return color.replace(/[\d\.]+\)$/g, `${opacity})`)
}
if (color.startsWith('#')) {
return `rgba(${hexToRgb(color)}, ${opacity})`
}
if (color.includes('rgb')) {
return color.replace('rgb', 'rgba').replace(')', `, ${opacity})`)
}
return `rgba(${hexToRgb(color)}, ${opacity})`
}
const gradientColors = colors.map((color, index) =>
createGradientColor(color, index === 0 ? 0.12 : 0.04)
)
return {
background: `linear-gradient(135deg,
${gradientColors[0]} 0%,
${gradientColors[1] || gradientColors[0]} 50%,
${gradientColors[2] || gradientColors[0]} 100%)`
}
})
const layoutClasses = computed(() => {
const width = cardWidth.value || 300
return {
cardText: width < 250 ? 'pa-2' :
width < 350 ? 'pa-3' :
width < 500 ? 'pa-4' : 'pa-5',
textSection: width < 250 ? 'me-2' :
width < 350 ? 'me-3' :
width < 500 ? 'me-4' : 'me-5',
chartSection: 'flex-shrink-0',
iconSize: width < 250 ? 12 :
width < 350 ? 14 :
width < 500 ? 16 : 18,
spacing: width < 300 ? 'mb-2' :
width < 450 ? 'mb-3' : 'mb-4'
}
})
</script>
<template>
<VCard
ref="cardRef"
class="overflow-visible"
:style="cardBackgroundStyle"
>
<VCardText
class="d-flex align-center justify-space-between"
:class="layoutClasses.cardText"
>
<div
class="d-flex flex-column flex-grow-1"
:class="layoutClasses.textSection"
>
<div :class="layoutClasses.spacing">
<h5
class="text-no-wrap font-weight-medium"
:class="textSizes.title"
style="line-height: 1.2;"
>
Generated Leads
</h5>
<div
class="text-medium-emphasis"
:class="textSizes.subtitle"
style="line-height: 1.1;"
>
Monthly Report
</div>
</div>
<div>
<h3
class="font-weight-bold"
:class="[textSizes.mainNumber, layoutClasses.spacing]"
style="line-height: 1.1;"
>
4,350
</h3>
<div class="d-flex align-center">
<VIcon
icon="tabler-chevron-up"
:color="(props.donutColors.length ? processColors(props.donutColors) : [vuetifyTheme.current.value.colors.success])[0]"
:size="layoutClasses.iconSize"
class="me-1"
/>
<span
class="font-weight-medium"
:class="textSizes.percentage"
:style="{ color: (props.donutColors.length ? processColors(props.donutColors) : [vuetifyTheme.current.value.colors.success])[0] }"
>
15.8%
</span>
</div>
</div>
</div>
<div
class="d-flex align-center justify-center"
:class="layoutClasses.chartSection"
>
<VueApexCharts
:options="chartOptions"
:series="chartSeries"
:height="chartSize.height"
:width="chartSize.width"
/>
</div>
</VCardText>
</VCard>
</template>

View File

@@ -0,0 +1,327 @@
<script setup>
const searchQuery = ref('')
const selectedStatus = ref(null)
const selectedRows = ref([])
// Data table options
const itemsPerPage = ref(6)
const page = ref(1)
const sortBy = ref()
const orderBy = ref()
const updateOptions = options => {
page.value = options.page
sortBy.value = options.sortBy[0]?.key
orderBy.value = options.sortBy[0]?.order
}
// 👉 headers
const headers = [
{
title: '#',
key: 'id',
},
{
title: 'Status',
key: 'status',
sortable: false,
},
{
title: 'Total',
key: 'total',
},
{
title: 'Issued Date',
key: 'date',
},
{
title: 'Balance',
key: 'balance',
},
{
title: 'Actions',
key: 'actions',
sortable: false,
},
]
const {
data: invoiceData,
execute: fetchInvoices,
} = await useApi(createUrl('/apps/invoice', {
query: {
q: searchQuery,
status: selectedStatus,
itemsPerPage,
page,
sortBy,
orderBy,
},
}))
const invoices = computed(() => invoiceData.value.invoices)
const totalInvoices = computed(() => invoiceData.value.totalInvoices)
// 👉 Invoice balance variant resolver
const resolveInvoiceBalanceVariant = (balance, total) => {
if (balance === total)
return {
status: 'Unpaid',
chip: { color: 'error' },
}
if (balance === 0)
return {
status: 'Paid',
chip: { color: 'success' },
}
return {
status: balance,
chip: { variant: 'text' },
}
}
const resolveInvoiceStatusVariantAndIcon = status => {
if (status === 'Partial Payment')
return {
variant: 'warning',
icon: 'tabler-chart-pie-2',
}
if (status === 'Paid')
return {
variant: 'success',
icon: 'tabler-check',
}
if (status === 'Downloaded')
return {
variant: 'info',
icon: 'tabler-arrow-down',
}
if (status === 'Draft')
return {
variant: 'primary',
icon: 'tabler-folder',
}
if (status === 'Sent')
return {
variant: 'secondary',
icon: 'tabler-mail',
}
if (status === 'Past Due')
return {
variant: 'error',
icon: 'tabler-help',
}
return {
variant: 'secondary',
icon: 'tabler-x',
}
}
const computedMoreList = computed(() => {
return paramId => [
{
title: 'Download',
value: 'download',
prependIcon: 'tabler-download',
},
{
title: 'Edit',
value: 'edit',
prependIcon: 'tabler-pencil',
to: {
name: 'apps-invoice-edit-id',
params: { id: paramId },
},
},
{
title: 'Duplicate',
value: 'duplicate',
prependIcon: 'tabler-layers-intersect',
},
]
})
const deleteInvoice = async id => {
await $api(`/apps/invoice/${ id }`, { method: 'DELETE' })
fetchInvoices()
}
</script>
<template>
<VCard
v-if="invoices"
id="invoice-list"
>
<VCardText>
<div class="d-flex justify-space-between flex-wrap gap-4">
<div class="d-flex gap-4 align-center">
<div class="d-flex align-center gap-x-2">
<div>
Show
</div>
<AppSelect
:model-value="itemsPerPage"
:items="[
{ value: 6, title: '6' },
{ value: 10, title: '10' },
{ value: 25, title: '25' },
{ value: 50, title: '50' },
{ value: 100, title: '100' },
{ value: -1, title: 'All' },
]"
@update:model-value="itemsPerPage = parseInt($event, 10)"
/>
</div>
<!-- 👉 Create invoice -->
<VBtn
prepend-icon="tabler-plus"
:to="{ name: 'apps-invoice-add' }"
>
Create invoice
</VBtn>
</div>
<div class="d-flex align-center flex-wrap gap-4">
<!-- 👉 Search -->
<div class="invoice-list-filter">
<AppTextField
v-model="searchQuery"
placeholder="Search Invoice"
/>
</div>
<!-- 👉 Select status -->
<div class="invoice-list-filter">
<AppSelect
v-model="selectedStatus"
placeholder="Select Status"
clearable
clear-icon="tabler-x"
single-line
:items="['Downloaded', 'Draft', 'Sent', 'Paid', 'Partial Payment', 'Past Due']"
/>
</div>
</div>
</div>
</VCardText>
<VDivider />
<!-- SECTION Datatable -->
<VDataTableServer
v-model="selectedRows"
v-model:items-per-page="itemsPerPage"
v-model:page="page"
show-select
:items-length="totalInvoices"
:headers="headers"
:items="invoices"
item-value="id"
class="text-no-wrap"
@update:options="updateOptions"
>
<!-- id -->
<template #item.id="{ item }">
<RouterLink :to="{ name: 'apps-invoice-preview-id', params: { id: item.id } }">
#{{ item.id }}
</RouterLink>
</template>
<!-- status -->
<template #item.status="{ item }">
<VTooltip>
<template #activator="{ props }">
<VAvatar
:size="28"
v-bind="props"
:color="resolveInvoiceStatusVariantAndIcon(item.invoiceStatus).variant"
variant="tonal"
>
<VIcon
:size="16"
:icon="resolveInvoiceStatusVariantAndIcon(item.invoiceStatus).icon"
/>
</VAvatar>
</template>
<p class="mb-0">
{{ item.invoiceStatus }}
</p>
<p class="mb-0">
Balance: {{ item.balance }}
</p>
<p class="mb-0">
Due date: {{ item.dueDate }}
</p>
</VTooltip>
</template>
<!-- Total -->
<template #item.total="{ item }">
${{ item.total }}
</template>
<!-- Date -->
<template #item.date="{ item }">
{{ item.issuedDate }}
</template>
<!-- Balance -->
<template #item.balance="{ item }">
<VChip
v-if="typeof ((resolveInvoiceBalanceVariant(item.balance, item.total)).status) === 'string'"
:color="resolveInvoiceBalanceVariant(item.balance, item.total).chip.color"
label
size="x-small"
>
{{ (resolveInvoiceBalanceVariant(item.balance, item.total)).status }}
</VChip>
<template v-else>
<span class="text-base text-high-emphasis">
{{ Number((resolveInvoiceBalanceVariant(item.balance, item.total)).status) > 0 ? `$${(resolveInvoiceBalanceVariant(item.balance, item.total)).status}` : `-$${Math.abs(Number((resolveInvoiceBalanceVariant(item.balance, item.total)).status))}` }}
</span>
</template>
</template>
<!-- Actions -->
<template #item.actions="{ item }">
<IconBtn @click="deleteInvoice(item.id)">
<VIcon icon="tabler-trash" />
</IconBtn>
<IconBtn :to="{ name: 'apps-invoice-preview-id', params: { id: item.id } }">
<VIcon icon="tabler-eye" />
</IconBtn>
<MoreBtn
:menu-list="computedMoreList(item.id)"
item-props
color="undefined"
size="small"
/>
</template>
<!-- pagination -->
<template #bottom>
<TablePagination
v-model:page="page"
:items-per-page="itemsPerPage"
:total-items="totalInvoices"
/>
</template>
</VDataTableServer>
<!-- !SECTION -->
</VCard>
</template>
<style lang="scss">
#invoice-list {
.invoice-list-actions {
inline-size: 8rem;
}
.invoice-list-filter {
inline-size: 12rem;
}
}
</style>

View File

@@ -0,0 +1,348 @@
<script setup>
const currentTab = ref('New')
const tabsData = [
'New',
'Preparing',
'Shipping',
]
</script>
<template>
<VCard class="country-order-card">
<VCardItem
title="Orders by countries"
subtitle="62 deliveries in progress"
class="pb-4"
>
<template #append>
<MoreBtn size="small" />
</template>
</VCardItem>
<VTabs
v-model="currentTab"
grow
class="disable-tab-transition"
>
<VTab
v-for="(tab, index) in tabsData"
:key="index"
>
{{ tab }}
</VTab>
</VTabs>
<VCardText>
<VWindow v-model="currentTab">
<VWindowItem>
<div>
<VTimeline
align="start"
truncate-line="both"
side="end"
density="compact"
line-thickness="1"
class="v-timeline--variant-outlined"
>
<VTimelineItem
icon="tabler-circle-check"
dot-color="rgba(var(--v-theme-surface))"
icon-color="success"
fill-dot
size="20"
:elevation="0"
>
<div class="text-body-2 text-uppercase text-success">
Sender
</div>
<div class="app-timeline-title">
Myrtle Ullrich
</div>
<div class="app-timeline-text">
101 Boulder, California(CA), 95959
</div>
</VTimelineItem>
<VTimelineItem
icon="tabler-map-pin"
dot-color="rgba(var(--v-theme-surface))"
icon-color="primary"
fill-dot
size="20"
:elevation="0"
>
<div class="text-body-2 text-primary text-uppercase">
Receiver
</div>
<div class="app-timeline-title">
Barry Schowalter
</div>
<div class="app-timeline-text">
939 Orange, California(CA), 92118
</div>
</VTimelineItem>
</VTimeline>
<VDivider
class="my-4"
style="border-style: dashed;"
/>
<VTimeline
align="start"
truncate-line="both"
side="end"
density="compact"
line-thickness="1"
class="v-timeline--variant-outlined"
>
<VTimelineItem
icon="tabler-circle-check"
dot-color="rgba(var(--v-theme-surface))"
icon-color="success"
fill-dot
size="20"
:elevation="0"
>
<div class="text-body-2 text-uppercase text-success">
Sender
</div>
<div class="app-timeline-title">
Veronica Herman
</div>
<div class="app-timeline-text">
162 Windsor, California(CA), 95492
</div>
</VTimelineItem>
<VTimelineItem
icon="tabler-map-pin"
dot-color="rgba(var(--v-theme-surface))"
icon-color="primary"
fill-dot
size="20"
:elevation="0"
>
<div class="text-body-2 text-primary text-uppercase">
Receiver
</div>
<div class="app-timeline-title">
Helen Jacobs
</div>
<div class="app-timeline-text">
487 Sunset, California(CA), 94043
</div>
</VTimelineItem>
</VTimeline>
</div>
</VWindowItem>
<VWindowItem>
<div>
<VTimeline
align="start"
truncate-line="both"
side="end"
density="compact"
line-thickness="1"
class="v-timeline--variant-outlined"
>
<VTimelineItem
icon="tabler-circle-check"
dot-color="rgba(var(--v-theme-surface))"
icon-color="success"
fill-dot
size="20"
:elevation="0"
>
<div class="text-body-2 text-uppercase text-success">
Sender
</div>
<div class="app-timeline-title">
Myrtle Ullrich
</div>
<div class="app-timeline-text">
101 Boulder, California(CA), 95959
</div>
</VTimelineItem>
<VTimelineItem
icon="tabler-map-pin"
dot-color="rgba(var(--v-theme-surface))"
icon-color="primary"
fill-dot
size="20"
:elevation="0"
>
<div class="text-body-2 text-primary text-uppercase">
Receiver
</div>
<div class="app-timeline-title">
Barry Schowalter
</div>
<div class="app-timeline-text">
939 Orange, California(CA), 92118
</div>
</VTimelineItem>
</VTimeline>
<VDivider
class="my-4"
style="border-style: dashed;"
/>
<VTimeline
align="start"
truncate-line="both"
side="end"
density="compact"
line-thickness="1"
class="v-timeline--variant-outlined"
>
<VTimelineItem
icon="tabler-circle-check"
dot-color="rgba(var(--v-theme-surface))"
icon-color="success"
fill-dot
size="20"
:elevation="0"
>
<div class="text-body-2 text-uppercase text-success">
Sender
</div>
<div class="app-timeline-title">
Veronica Herman
</div>
<div class="app-timeline-text">
162 Windsor, California(CA), 95492
</div>
</VTimelineItem>
<VTimelineItem
icon="tabler-map-pin"
dot-color="rgba(var(--v-theme-surface))"
icon-color="primary"
fill-dot
size="20"
:elevation="0"
>
<div class="text-body-2 text-primary text-uppercase">
Receiver
</div>
<div class="app-timeline-title">
Helen Jacobs
</div>
<div class="app-timeline-text">
487 Sunset, California(CA), 94043
</div>
</VTimelineItem>
</VTimeline>
</div>
</VWindowItem>
<VWindowItem>
<div>
<VTimeline
align="start"
truncate-line="both"
side="end"
density="compact"
line-thickness="1"
class="v-timeline--variant-outlined"
>
<VTimelineItem
icon="tabler-circle-check"
dot-color="rgba(var(--v-theme-surface))"
icon-color="success"
fill-dot
size="20"
:elevation="0"
>
<div class="text-body-2 text-uppercase text-success">
Sender
</div>
<div class="app-timeline-title">
Myrtle Ullrich
</div>
<div class="app-timeline-text">
101 Boulder, California(CA), 95959
</div>
</VTimelineItem>
<VTimelineItem
icon="tabler-map-pin"
dot-color="rgba(var(--v-theme-surface))"
icon-color="primary"
fill-dot
size="20"
:elevation="0"
>
<div class="text-body-2 text-primary text-uppercase">
Receiver
</div>
<div class="app-timeline-title">
Barry Schowalter
</div>
<div class="app-timeline-text">
939 Orange, California(CA), 92118
</div>
</VTimelineItem>
</VTimeline>
<VDivider
class="my-4"
style="border-style: dashed;"
/>
<VTimeline
align="start"
truncate-line="both"
side="end"
density="compact"
line-thickness="1"
class="v-timeline--variant-outlined"
>
<VTimelineItem
icon="tabler-circle-check"
dot-color="rgba(var(--v-theme-surface))"
icon-color="success"
fill-dot
size="20"
:elevation="0"
>
<div class="text-body-2 text-uppercase text-success">
Sender
</div>
<div class="app-timeline-title">
Veronica Herman
</div>
<div class="app-timeline-text">
162 Windsor, California(CA), 95492
</div>
</VTimelineItem>
<VTimelineItem
icon="tabler-map-pin"
dot-color="rgba(var(--v-theme-surface))"
icon-color="primary"
fill-dot
size="20"
:elevation="0"
>
<div class="text-body-2 text-primary text-uppercase">
Receiver
</div>
<div class="app-timeline-title">
Helen Jacobs
</div>
<div class="app-timeline-text">
487 Sunset, California(CA), 94043
</div>
</VTimelineItem>
</VTimeline>
</div>
</VWindowItem>
</VWindow>
</VCardText>
</VCard>
</template>
<style lang="scss">
.country-order-card {
.v-timeline .v-timeline-divider__dot .v-timeline-divider__inner-dot {
box-shadow: none !important;
}
}
</style>

View File

@@ -0,0 +1,115 @@
<script setup>
import amazonEchoDot from '@images/eCommerce/amazon-echo-dot.png'
import appleWatch from '@images/eCommerce/apple-watch.png'
import headphone from '@images/eCommerce/headphone.png'
import iphone from '@images/eCommerce/iphone.png'
import nike from '@images/eCommerce/nike.png'
import sonyDualsense from '@images/eCommerce/sony-dualsense.png'
const popularProducts = [
{
avatarImg: iphone,
title: 'Apple iPhone 13',
subtitle: 'Item: #FXZ-4567',
stats: '$999.29',
},
{
avatarImg: nike,
title: 'Nike Air Jordan',
subtitle: 'Item: #FXZ-3456',
stats: '$72.40',
},
{
avatarImg: headphone,
title: 'Beats Studio 2',
subtitle: 'Item: #FXZ-9485',
stats: '$99',
},
{
avatarImg: appleWatch,
title: 'Apple Watch Series 7',
subtitle: 'Item: #FXZ-2345',
stats: '$249.99',
},
{
avatarImg: amazonEchoDot,
title: 'Amazon Echo Dot',
subtitle: 'Item: #FXZ-8959',
stats: '$79.40',
},
{
avatarImg: sonyDualsense,
title: 'Play Station Console',
subtitle: 'Item: #FXZ-7892',
stats: '$129.48',
},
]
const moreList = [
{
title: 'Refresh',
value: 'refresh',
},
{
title: 'Download',
value: 'Download',
},
{
title: 'View All',
value: 'View All',
},
]
</script>
<template>
<VCard
title="Popular Products"
subtitle="Total 10.4k Visitors"
>
<template #append>
<div class="mt-n4 me-n2">
<MoreBtn
size="small"
:menu-list="moreList"
/>
</div>
</template>
<VCardText>
<VList class="card-list">
<VListItem
v-for="product in popularProducts"
:key="product.title"
>
<template #prepend>
<VAvatar
size="46"
rounded
class="me-1"
:image="product.avatarImg"
/>
</template>
<VListItemTitle class="font-weight-medium me-4">
{{ product.title }}
</VListItemTitle>
<VListItemSubtitle class="me-4">
{{ product.subtitle }}
</VListItemSubtitle>
<template #append>
<div class="d-flex align-center">
<span class="text-body-1">{{ product.stats }}</span>
</div>
</template>
</VListItem>
</VList>
</VCardText>
</VCard>
</template>
<style lang="scss" scoped>
.card-list {
--v-card-list-gap: 1.25rem;
}
</style>

View File

@@ -0,0 +1,448 @@
<script setup>
import { useTheme } from 'vuetify'
import { hexToRgb } from '@layouts/utils'
const vuetifyTheme = useTheme()
const series = {
bar: [
{
name: 'Earning',
data: [
270,
210,
180,
200,
250,
280,
250,
270,
150,
],
},
{
name: 'Expense',
data: [
-140,
-160,
-180,
-150,
-100,
-60,
-80,
-100,
-180,
],
},
],
line: [
{
name: 'Last Month',
data: [
20,
10,
30,
16,
24,
5,
40,
23,
28,
5,
30,
],
},
{
name: 'This Month',
data: [
50,
40,
60,
46,
54,
35,
70,
53,
58,
35,
60,
],
},
],
}
const chartOptions = computed(() => {
const currentTheme = vuetifyTheme.current.value.colors
const variableTheme = vuetifyTheme.current.value.variables
const labelColor = `rgba(${ hexToRgb(currentTheme['on-surface']) },${ variableTheme['disabled-opacity'] })`
const legendColor = `rgba(${ hexToRgb(currentTheme['on-background']) },${ variableTheme['high-emphasis-opacity'] })`
const borderColor = `rgba(${ hexToRgb(String(variableTheme['border-color'])) },${ variableTheme['border-opacity'] })`
return {
bar: {
chart: {
parentHeightOffset: 0,
stacked: true,
type: 'bar',
toolbar: { show: false },
},
tooltip: { enabled: false },
plotOptions: {
bar: {
horizontal: false,
columnWidth: '40%',
borderRadius: 8,
borderRadiusApplication: 'around',
borderRadiusWhenStacked: 'all',
},
},
colors: [
'rgba(var(--v-theme-primary),1)',
'rgba(var(--v-theme-warning),1)',
],
dataLabels: { enabled: false },
stroke: {
curve: 'smooth',
width: 6,
lineCap: 'round',
colors: [currentTheme.surface],
},
legend: {
show: true,
horizontalAlign: 'right',
position: 'top',
fontFamily: 'Public Sans',
fontSize: '13px',
markers: {
height: 12,
width: 12,
radius: 12,
offsetX: -3,
offsetY: 2,
},
labels: { colors: legendColor },
itemMargin: { horizontal: 5 },
},
grid: {
show: false,
padding: {
bottom: -8,
top: 20,
},
},
xaxis: {
categories: [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
],
labels: {
style: {
fontSize: '13px',
colors: labelColor,
fontFamily: 'Public Sans',
},
},
axisTicks: { show: false },
axisBorder: { show: false },
},
yaxis: {
labels: {
offsetX: -16,
style: {
fontSize: '13px',
colors: labelColor,
fontFamily: 'Public Sans',
},
},
min: -200,
max: 300,
tickAmount: 5,
},
responsive: [
{
breakpoint: 1700,
options: { plotOptions: { bar: { columnWidth: '43%' } } },
},
{
breakpoint: 1526,
options: {
plotOptions: {
bar: {
columnWidth: '52%',
borderRadius: 8,
},
},
},
},
{
breakpoint: 1359,
options: {
plotOptions: {
bar: {
columnWidth: '60%',
borderRadius: 8,
},
},
},
},
{
breakpoint: 1280,
options: {
plotOptions: {
bar: {
columnWidth: '40%',
borderRadius: 10,
},
},
},
},
{
breakpoint: 1025,
options: {
plotOptions: {
bar: {
columnWidth: '40%',
borderRadius: 8,
},
},
chart: { height: 390 },
},
},
{
breakpoint: 991,
options: {
plotOptions: {
bar: {
columnWidth: '40%',
borderRadius: 8,
},
},
},
},
{
breakpoint: 850,
options: {
plotOptions: {
bar: {
columnWidth: '50%',
borderRadius: 8,
},
},
},
},
{
breakpoint: 776,
options: {
plotOptions: {
bar: {
columnWidth: '50%',
borderRadius: 6,
},
},
},
},
{
breakpoint: 731,
options: {
plotOptions: {
bar: {
columnWidth: '70%',
borderRadius: 8,
},
},
},
},
{
breakpoint: 599,
options: {
plotOptions: {
bar: {
columnWidth: '50%',
borderRadius: 8,
},
},
},
},
{
breakpoint: 500,
options: {
plotOptions: {
bar: {
columnWidth: '55%',
borderRadius: 6,
},
},
},
},
{
breakpoint: 449,
options: {
plotOptions: {
bar: {
columnWidth: '65%',
borderRadius: 6,
},
},
chart: { height: 360 },
xaxis: { labels: { offsetY: -5 } },
},
},
{
breakpoint: 394,
options: {
plotOptions: {
bar: {
columnWidth: '80%',
borderRadius: 6,
},
},
},
},
],
states: {
hover: { filter: { type: 'none' } },
active: { filter: { type: 'none' } },
},
},
line: {
chart: {
toolbar: { show: false },
zoom: { enabled: false },
type: 'line',
},
stroke: {
curve: 'smooth',
dashArray: [
5,
0,
],
width: [
1,
2,
],
},
legend: { show: false },
colors: [
borderColor,
currentTheme.primary,
],
grid: {
show: false,
borderColor,
padding: {
top: -30,
bottom: -15,
left: 25,
},
},
markers: { size: 0 },
xaxis: {
labels: { show: false },
axisTicks: { show: false },
axisBorder: { show: false },
},
yaxis: { show: false },
tooltip: { enabled: false },
},
}
})
</script>
<template>
<VCard class="revenue-report">
<VRow no-gutters>
<VCol
cols="12"
sm="8"
lg="8"
:class="$vuetify.display.smAndUp ? 'border-e' : 'border-b'"
>
<VCardText>
<h6 class="text-h5 mb-sm-n8">
Revenue Report
</h6>
<VueApexCharts
:options="chartOptions.bar"
:series="series.bar"
height="365"
/>
</VCardText>
</VCol>
<VCol
cols="12"
sm="4"
>
<VCardText class="d-flex flex-column justify-center align-center text-center h-100">
<VBtn
variant="tonal"
size="small"
class="d-flex mx-auto"
>
<span>2022</span>
<template #append>
<VIcon
size="16"
icon="tabler-chevron-down"
/>
</template>
<VMenu activator="parent">
<VList>
<VListItem
v-for="(item, index) in ['2021', '2020', '2019']"
:key="index"
:value="index"
>
<VListItemTitle>{{ item }}</VListItemTitle>
</VListItem>
</VList>
</VMenu>
</VBtn>
<div class="d-flex flex-column my-8">
<h5 class="font-weight-medium text-h3">
$25,825
</h5>
<p class="mb-0">
<span class="text-high-emphasis font-weight-medium me-1">Budget:</span>
<span>56,800</span>
</p>
</div>
<VueApexCharts
:options="chartOptions.line"
:series="series.line"
height="100"
/>
<VBtn class="mt-8">
Increase Budget
</VBtn>
</VCardText>
</VCol>
</VRow>
</VCard>
</template>
<style lang="scss">
.revenue-report {
.apexcharts-legend {
gap: 1rem;
}
@media (max-width: 599px) {
.apexcharts-legend.apx-legend-position-top.apexcharts-align-right {
justify-content: flex-start;
padding: 0;
}
}
}
</style>

View File

@@ -0,0 +1,67 @@
<script setup>
const statistics = [
{
title: 'Sales',
stats: '230k',
icon: 'tabler-chart-pie-2',
color: 'primary',
},
{
title: 'Customers',
stats: '8.549k',
icon: 'tabler-users',
color: 'info',
},
{
title: 'Products',
stats: '1.423k',
icon: 'tabler-shopping-cart',
color: 'error',
},
{
title: 'Revenue',
stats: '$9745',
icon: 'tabler-currency-dollar',
color: 'success',
},
]
</script>
<template>
<VCard title="Statistics">
<template #append>
<span class="text-sm text-disabled">Updated 1 month ago</span>
</template>
<VCardText>
<VRow>
<VCol
v-for="item in statistics"
:key="item.title"
cols="6"
md="3"
>
<div class="d-flex align-center gap-4 mt-md-9 mt-0">
<VAvatar
:color="item.color"
variant="tonal"
rounded
size="40"
>
<VIcon :icon="item.icon" />
</VAvatar>
<div class="d-flex flex-column">
<h5 class="text-h5">
{{ item.stats }}
</h5>
<div class="text-sm">
{{ item.title }}
</div>
</div>
</div>
</VCol>
</VRow>
</VCardText>
</VCard>
</template>

View File

@@ -0,0 +1,106 @@
<script setup>
import { useTheme } from 'vuetify'
import { hexToRgb } from '@layouts/utils'
const vuetifyTheme = useTheme()
const series = [{
data: [
0,
25,
10,
40,
25,
55,
],
}]
const chartOptions = computed(() => {
const currentTheme = vuetifyTheme.current.value.colors
const variableTheme = vuetifyTheme.current.value.variables
return {
chart: {
height: 90,
type: 'line',
parentHeightOffset: 0,
toolbar: { show: false },
},
grid: {
borderColor: `rgba(${ hexToRgb(String(variableTheme['border-color'])) },${ variableTheme['border-opacity'] })`,
strokeDashArray: 6,
xaxis: { lines: { show: true } },
yaxis: { lines: { show: false } },
padding: {
top: -18,
left: -4,
right: 7,
bottom: -10,
},
},
colors: [currentTheme.info],
stroke: { width: 2 },
tooltip: {
enabled: false,
shared: false,
intersect: true,
x: { show: false },
},
xaxis: {
labels: { show: false },
axisTicks: { show: false },
axisBorder: { show: false },
},
yaxis: { labels: { show: false } },
markers: {
size: 3.5,
fillColor: currentTheme.info,
strokeColors: 'transparent',
strokeWidth: 3.2,
discrete: [{
seriesIndex: 0,
dataPointIndex: 5,
fillColor: currentTheme.surface,
strokeColor: currentTheme.info,
size: 5,
shape: 'circle',
}],
hover: { size: 5.5 },
},
responsive: [{
breakpoint: 960,
options: { chart: { height: 110 } },
}],
}
})
</script>
<template>
<VCard>
<VCardItem class="pb-3">
<VCardTitle>
Profit
</VCardTitle>
<VCardSubtitle>
Last Month
</VCardSubtitle>
</VCardItem>
<VCardText>
<VueApexCharts
type="line"
:options="chartOptions"
:series="series"
:height="68"
/>
<div class="d-flex align-center justify-space-between gap-x-2 mt-3">
<h4 class="text-h4 text-center font-weight-medium">
624k
</h4>
<span class="text-sm text-success">
+8.24%
</span>
</div>
</VCardText>
</VCard>
</template>

View File

@@ -0,0 +1,134 @@
<script setup>
const transitions = [
{
avatarIcon: 'tabler-wallet',
avatarColor: 'primary',
title: 'Wallet',
subtitle: 'Starbucks',
stats: '-$75',
profit: false,
},
{
avatarIcon: 'tabler-building-bank',
avatarColor: 'success',
title: 'Bank Transfer',
subtitle: 'Add Money',
stats: '+$480',
profit: true,
},
{
avatarIcon: 'tabler-brand-paypal',
avatarColor: 'error',
title: 'PayPal',
subtitle: 'Client Payment',
stats: '+$268',
profit: true,
},
{
avatarIcon: 'tabler-credit-card',
avatarColor: 'secondary',
title: 'Master Card',
subtitle: 'Ordered iPhone 13',
stats: '-$699',
profit: false,
},
{
avatarIcon: 'tabler-currency-dollar',
avatarColor: 'info',
title: 'Bank Transactions',
subtitle: 'Refund',
stats: '+$98',
profit: true,
},
{
avatarIcon: 'tabler-brand-paypal',
avatarColor: 'error',
title: 'PayPal',
subtitle: 'Client Payment',
stats: '+$126',
profit: true,
},
{
avatarIcon: 'tabler-building-bank',
avatarColor: 'success',
title: 'Bank Transfer',
subtitle: 'Pay Office Rent',
stats: '-$1290',
profit: false,
},
]
const moreList = [
{
title: 'Refresh',
value: 'refresh',
},
{
title: 'Download',
value: 'Download',
},
{
title: 'View All',
value: 'View All',
},
]
</script>
<template>
<VCard
title="Transactions"
subtitle="Total 58 Transactions done in this Month"
>
<template #append>
<div class="mt-n4 me-n2">
<MoreBtn
size="small"
:menu-list="moreList"
/>
</div>
</template>
<VCardText>
<VList class="card-list">
<VListItem
v-for="transition in transitions"
:key="transition.title"
>
<template #prepend>
<VAvatar
size="34"
:color="transition.avatarColor"
variant="tonal"
class="me-1"
rounded
>
<VIcon
:icon="transition.avatarIcon"
size="22"
/>
</VAvatar>
</template>
<VListItemTitle>
{{ transition.title }}
</VListItemTitle>
<VListItemSubtitle>
{{ transition.subtitle }}
</VListItemSubtitle>
<template #append>
<div class="d-flex align-center">
<span :class="`${transition.profit ? 'text-success' : 'text-error'} font-weight-medium`">{{ transition.stats }}</span>
</div>
</template>
</VListItem>
</VList>
</VCardText>
</VCard>
</template>
<style lang="scss" scoped>
.card-list {
--v-card-list-gap: 16px;
}
</style>