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,120 @@
<script setup>
import ECommerceAddCustomerDrawer from '@/views/apps/ecommerce/ECommerceAddCustomerDrawer.vue'
import CustomerBioPanel from '@/views/apps/ecommerce/customer/view/CustomerBioPanel.vue'
import CustomerTabAddressAndBilling from '@/views/apps/ecommerce/customer/view/CustomerTabAddressAndBilling.vue'
import CustomerTabNotification from '@/views/apps/ecommerce/customer/view/CustomerTabNotification.vue'
import CustomerTabOverview from '@/views/apps/ecommerce/customer/view/CustomerTabOverview.vue'
import CustomerTabSecurity from '@/views/apps/ecommerce/customer/view/CustomerTabSecurity.vue'
const route = useRoute('apps-ecommerce-customer-details-id')
const customerData = ref()
const userTab = ref(null)
const tabs = [
{
title: 'Overview',
icon: 'tabler-user',
},
{
title: 'Security',
icon: 'tabler-lock',
},
{
title: 'Address & Billing',
icon: 'tabler-map-pin',
},
{
title: 'Notifications',
icon: 'tabler-bell',
},
]
const { data } = await useApi(`/apps/ecommerce/customers/${ route.params.id }`)
if (data.value)
customerData.value = data.value
const isAddCustomerDrawerOpen = ref(false)
</script>
<template>
<div>
<!-- 👉 Header -->
<div class="d-flex justify-space-between align-center flex-wrap gap-y-4 mb-6">
<div>
<h4 class="text-h4 mb-1">
Customer ID #{{ route.params.id }}
</h4>
<div class="text-body-1">
Aug 17, 2020, 5:48 (ET)
</div>
</div>
<div class="d-flex gap-4">
<VBtn
variant="tonal"
color="error"
>
Delete Customer
</VBtn>
</div>
</div>
<!-- 👉 Customer Profile -->
<VRow v-if="customerData">
<VCol
v-if="customerData"
cols="12"
md="5"
lg="4"
>
<CustomerBioPanel :customer-data="customerData" />
</VCol>
<VCol
cols="12"
md="7"
lg="8"
>
<VTabs
v-model="userTab"
class="v-tabs-pill mb-3 disable-tab-transition"
>
<VTab
v-for="tab in tabs"
:key="tab.title"
>
<VIcon
size="20"
start
:icon="tab.icon"
/>
{{ tab.title }}
</VTab>
</VTabs>
<VWindow
v-model="userTab"
class="disable-tab-transition"
:touch="false"
>
<VWindowItem>
<CustomerTabOverview />
</VWindowItem>
<VWindowItem>
<CustomerTabSecurity />
</VWindowItem>
<VWindowItem>
<CustomerTabAddressAndBilling />
</VWindowItem>
<VWindowItem>
<CustomerTabNotification />
</VWindowItem>
</VWindow>
</VCol>
</VRow>
<div v-else>
<VAlert
type="error"
variant="tonal"
>
Invoice with ID {{ route.params.id }} not found!
</VAlert>
</div>
<ECommerceAddCustomerDrawer v-model:is-drawer-open="isAddCustomerDrawerOpen" />
</div>
</template>

View File

@@ -0,0 +1,173 @@
<script setup>
import ECommerceAddCustomerDrawer from '@/views/apps/ecommerce/ECommerceAddCustomerDrawer.vue'
const searchQuery = ref('')
const isAddCustomerDrawerOpen = ref(false)
// Data table options
const itemsPerPage = ref(10)
const page = ref(1)
const sortBy = ref()
const orderBy = ref()
// Data table Headers
const headers = [
{
title: 'Customer',
key: 'customer',
},
{
title: 'Customer Id',
key: 'customerId',
},
{
title: 'Country',
key: 'country',
},
{
title: 'Orders',
key: 'orders',
},
{
title: 'Total Spent',
key: 'totalSpent',
},
]
const updateOptions = options => {
sortBy.value = options.sortBy[0]?.key
orderBy.value = options.sortBy[0]?.order
}
const { data: customerData } = await useApi(createUrl('/apps/ecommerce/customers', {
query: {
q: searchQuery,
itemsPerPage,
page,
sortBy,
orderBy,
},
}))
const customers = computed(() => customerData.value.customers)
const totalCustomers = computed(() => customerData.value.total)
</script>
<template>
<div>
<VCard>
<VCardText>
<div class="d-flex justify-space-between flex-wrap gap-y-4">
<AppTextField
v-model="searchQuery"
style="max-inline-size: 280px; min-inline-size: 280px;"
placeholder="Search Name"
/>
<div class="d-flex flex-row gap-4 align-center flex-wrap">
<AppSelect
v-model="itemsPerPage"
:items="[5, 10, 20, 50, 100]"
/>
<VBtn
prepend-icon="tabler-upload"
variant="tonal"
color="secondary"
>
Export
</VBtn>
<VBtn
prepend-icon="tabler-plus"
@click="isAddCustomerDrawerOpen = !isAddCustomerDrawerOpen"
>
Add Customer
</VBtn>
</div>
</div>
</VCardText>
<VDivider />
<VDataTableServer
v-model:items-per-page="itemsPerPage"
v-model:page="page"
:items="customers"
item-value="customer"
:headers="headers"
:items-length="totalCustomers"
show-select
class="text-no-wrap"
@update:options="updateOptions"
>
<template #item.customer="{ item }">
<div class="d-flex align-center gap-x-3">
<VAvatar
size="34"
:variant="!item.avatar ? 'tonal' : undefined"
>
<VImg
v-if="item.avatar"
:src="item.avatar"
/>
<span v-else>{{ avatarText(item.customer) }}</span>
</VAvatar>
<div class="d-flex flex-column">
<RouterLink
:to="{ name: 'apps-ecommerce-customer-details-id', params: { id: item.customerId } }"
class="text-link font-weight-medium d-inline-block"
style="line-height: 1.375rem;"
>
{{ item.customer }}
</RouterLink>
<div class="text-body-2">
{{ item.email }}
</div>
</div>
</div>
</template>
<template #item.customerId="{ item }">
<div class="text-body-1 text-high-emphasis">
#{{ item.customerId }}
</div>
</template>
<template #item.orders="{ item }">
{{ item.order }}
</template>
<template #item.country="{ item }">
<div class="d-flex gap-x-2">
<img
:src="item.countryFlag"
height="22"
width="22"
>
<span class="text-body-1">{{ item.country }}</span>
</div>
</template>
<template #item.totalSpent="{ item }">
<h6 class="text-h6">
${{ item.totalSpent }}
</h6>
</template>
<template #bottom>
<TablePagination
v-model:page="page"
:items-per-page="itemsPerPage"
:total-items="totalCustomers"
/>
</template>
</VDataTableServer>
</VCard>
<ECommerceAddCustomerDrawer v-model:is-drawer-open="isAddCustomerDrawerOpen" />
</div>
</template>
<style lang="scss" scoped>
.customer-title:hover {
color: rgba(var(--v-theme-primary)) !important;
}
</style>

View File

@@ -0,0 +1,573 @@
<script setup>
const selectedStatus = ref('All')
const searchQuery = ref('')
const itemsPerPage = ref(10)
const page = ref(1)
const sortBy = ref()
const orderBy = ref()
const selectedRows = ref([])
const {
data: ReviewData,
execute: fetchReviews,
} = await useApi(createUrl('/apps/ecommerce/reviews', {
query: {
q: searchQuery,
status: selectedStatus,
page,
itemsPerPage,
sortBy,
orderBy,
},
}))
const reviews = computed(() => ReviewData.value.reviews)
const totalReviews = computed(() => ReviewData.value.total)
const updateOptions = options => {
sortBy.value = options.sortBy[0]?.key
orderBy.value = options.sortBy[0]?.order
}
const deleteReview = async id => {
await $api(`/apps/ecommerce/reviews/${ id }`, { method: 'DELETE' })
const index = selectedRows.value.findIndex(row => row === id)
if (index !== -1)
selectedRows.value.splice(index, 1)
fetchReviews()
}
const reviewCardData = [
{
rating: 5,
value: 124,
},
{
rating: 4,
value: 40,
},
{
rating: 3,
value: 12,
},
{
rating: 2,
value: 7,
},
{
rating: 1,
value: 2,
},
]
const headers = [
{
title: 'Product',
key: 'product',
},
{
title: 'Reviewer',
key: 'reviewer',
},
{
title: 'Review',
key: 'review',
sortable: false,
},
{
title: 'Date',
key: 'date',
},
{
title: 'Status',
key: 'status',
},
{
title: 'Actions',
key: 'actions',
sortable: false,
},
]
const labelColor = 'rgba(var(--v-theme-on-surface), var(--v-disabled-opacity))'
const config = {
colorsLabel: { success: '#28c76f29' },
colors: { success: '#28c76f' },
}
const reviewStatChartSeries = [{
data: [
20,
40,
60,
80,
100,
80,
60,
],
}]
const reviewStatChartConfig = {
chart: {
height: 160,
width: 190,
type: 'bar',
toolbar: { show: false },
},
legend: { show: false },
grid: {
show: false,
padding: {
top: -25,
bottom: -12,
},
},
colors: [
config.colorsLabel.success,
config.colorsLabel.success,
config.colorsLabel.success,
config.colorsLabel.success,
config.colors.success,
config.colorsLabel.success,
config.colorsLabel.success,
],
plotOptions: {
bar: {
barHeight: '75%',
columnWidth: '25%',
borderRadius: 4,
distributed: true,
},
},
dataLabels: { enabled: false },
xaxis: {
categories: [
'M',
'T',
'W',
'T',
'F',
'S',
'S',
],
axisBorder: { show: false },
axisTicks: { show: false },
labels: {
style: {
colors: labelColor,
fontSize: '13px',
},
},
},
yaxis: { labels: { show: false } },
responsive: [
{
breakpoint: 0,
options: {
chart: { width: '100%' },
plotOptions: { bar: { columnWidth: '40%' } },
},
},
{
breakpoint: 1440,
options: {
chart: {
height: 150,
width: 190,
toolbar: { show: !1 },
},
plotOptions: {
bar: {
borderRadius: 4,
columnWidth: '40%',
},
},
},
},
{
breakpoint: 1400,
options: {
plotOptions: {
bar: {
borderRadius: 4,
columnWidth: '40%',
},
},
},
},
{
breakpoint: 1200,
options: {
chart: {
height: 130,
width: 190,
toolbar: { show: !1 },
},
plotOptions: {
bar: {
borderRadius: 6,
columnWidth: '40%',
},
},
},
},
{
breakpoint: 992,
chart: {
height: 150,
width: 190,
toolbar: { show: !1 },
},
options: {
plotOptions: {
bar: {
borderRadius: 5,
columnWidth: '40%',
},
},
},
},
{
breakpoint: 883,
options: {
plotOptions: {
bar: {
borderRadius: 5,
columnWidth: '40%',
},
},
},
},
{
breakpoint: 768,
options: {
chart: {
height: 150,
width: 190,
toolbar: { show: !1 },
},
plotOptions: {
bar: {
borderRadius: 4,
columnWidth: '40%',
},
},
},
},
{
breakpoint: 600,
options: {
chart: {
width: '100%',
height: '200',
type: 'bar',
},
plotOptions: {
bar: {
borderRadius: 6,
columnWidth: '30% ',
},
},
},
},
{
breakpoint: 420,
options: {
plotOptions: {
chart: {
width: '100%',
height: '200',
type: 'bar',
},
bar: {
borderRadius: 4,
columnWidth: '30%',
},
},
},
},
],
}
</script>
<template>
<VRow class="match-height">
<VCol
cols="12"
md="6"
>
<!-- 👉 Total Review Card -->
<VCard>
<VCardText>
<div class="d-flex gap-6 flex-column flex-sm-row">
<div>
<div class="d-flex align-center gap-x-2">
<h3 class="text-h3 text-primary">
4.89
</h3>
<VIcon
icon="tabler-star-filled"
color="primary"
size="32"
/>
</div>
<h6 class="my-2 text-h6">
Total 187 reviews
</h6>
<div class="mb-2 text-wrap">
All reviews are from genuine customers
</div>
<VChip
color="primary"
label
size="small"
>
+5 This week
</VChip>
</div>
<VDivider :vertical="$vuetify.display.smAndUp" />
<div class="flex-grow-1">
<div
v-for="(review, index) in reviewCardData"
:key="index"
class="d-flex align-center gap-x-4"
:class="index !== reviewCardData.length - 1 ? 'mb-3' : ''"
>
<div class="text-no-wrap text-sm">
{{ review.rating }} Star
</div>
<div
class="flex-grow-1"
style="min-inline-size: 150px;"
>
<VProgressLinear
color="primary"
height="8"
:model-value="(review.value / 185) * 100"
rounded
/>
</div>
<div class="text-sm">
{{ review.value }}
</div>
</div>
</div>
</div>
</VCardText>
</VCard>
</VCol>
<VCol
cols="12"
md="6"
>
<VCard>
<VCardText>
<div class="d-flex justify-space-between flex-sm-row flex-column">
<div>
<h5 class="text-h5 mb-2">
Reviews statistics
</h5>
<div class="mb-8 mb-sm-12">
<div class="d-inline-block me-2">
12 New Reviews
</div>
<VChip
color="success"
size="small"
label
>
+8.4%
</VChip>
</div>
<div>
<div class="text-high-emphasis text-body-1 mb-2">
<span class="text-success">87%</span> Positive Reviews
</div>
<div class="text-body-2">
Weekly Report
</div>
</div>
</div>
<div>
<VueApexCharts
id="shipment-statistics"
type="bar"
height="152"
:options="reviewStatChartConfig"
:series="reviewStatChartSeries"
/>
</div>
</div>
</VCardText>
</VCard>
</VCol>
<VCol cols="12">
<VCard>
<VCardText>
<div class="d-flex justify-space-between flex-wrap gap-6 ">
<div>
<AppTextField
v-model="searchQuery"
style="max-inline-size: 200px; min-inline-size: 200px;"
placeholder="Search Review"
/>
</div>
<div class="d-flex flex-row gap-4 align-center flex-wrap">
<AppSelect
v-model="itemsPerPage"
:items="[10, 25, 50, 100]"
style="inline-size: 6.25rem;"
/>
<AppSelect
v-model="selectedStatus"
style="max-inline-size: 7.5rem;min-inline-size: 7.5rem;"
:items="[
{ title: 'All', value: 'All' },
{ title: 'Published', value: 'Published' },
{ title: 'Pending', value: 'Pending' },
]"
/>
<VBtn
prepend-icon="tabler-upload"
variant="tonal"
color="default"
>
Export
</VBtn>
</div>
</div>
</VCardText>
<VDivider />
<VDataTableServer
v-model:items-per-page="itemsPerPage"
v-model:model-value="selectedRows"
v-model:page="page"
:headers="headers"
:items="reviews"
show-select
:items-length="totalReviews"
class="text-no-wrap"
@update:options="updateOptions"
>
<template #item.product="{ item }">
<div class="d-flex gap-x-4 align-center">
<VAvatar
:image="item.productImage"
:size="38"
variant="tonal"
rounded
/>
<div class="d-flex flex-column">
<h6 class="text-h6">
{{ item.product }}
</h6>
<div class="text-body-2 text-wrap clamp-text">
{{ item.companyName }}
</div>
</div>
</div>
</template>
<template #item.reviewer="{ item }">
<div class="d-flex align-center gap-x-4">
<VAvatar
:image="item.avatar"
size="34"
/>
<div class="d-flex flex-column">
<RouterLink
:to="{ name: 'apps-ecommerce-customer-details-id', params: { id: 478426 } }"
class="font-weight-medium"
style="line-height: 1.375rem;"
>
{{ item.reviewer }}
</RouterLink>
<div class="text-body-2">
{{ item.email }}
</div>
</div>
</div>
</template>
<template #item.review="{ item }">
<div class="my-4">
<VRating
:id="item.id"
:name="`${item.id}`"
readonly
:model-value="item.review"
size="24"
class="mb-1"
/>
<h6 class="text-h6 mb-1">
{{ item.head }}
</h6>
<p class="text-sm text-wrap mb-0">
{{ item.para }}
</p>
</div>
</template>
<template #item.date="{ item }">
{{ new Date(item.date).toDateString() }}
</template>
<template #item.status="{ item }">
<VChip
:color="item.status === 'Published' ? 'success' : 'warning'"
label
size="small"
>
{{ item.status }}
</VChip>
</template>
<template #item.actions="{ item }">
<IconBtn>
<VIcon icon="tabler-dots-vertical" />
<VMenu activator="parent">
<VList>
<VListItem
value="view"
:to="{ name: 'apps-ecommerce-order-details-id', params: { id: item.id } }"
>
View
</VListItem>
<VListItem
value="delete"
@click="deleteReview(item.id)"
>
Delete
</VListItem>
</VList>
</VMenu>
</IconBtn>
</template>
<!-- pagination -->
<template #bottom>
<TablePagination
v-model:page="page"
:items-per-page="itemsPerPage"
:total-items="totalReviews"
/>
</template>
</VDataTableServer>
</VCard>
</VCol>
</VRow>
</template>
<style lang="scss">
@use "@core-scss/template/libs/apex-chart";
</style>

View File

@@ -0,0 +1,535 @@
<script setup>
import product21 from '@images/ecommerce-images/product-21.png'
import product22 from '@images/ecommerce-images/product-22.png'
import product23 from '@images/ecommerce-images/product-23.png'
import product24 from '@images/ecommerce-images/product-24.png'
const orderData = ref()
const route = useRoute('apps-ecommerce-order-details-id')
const { data } = await useApi(`/apps/ecommerce/orders/${ route.params.id }`)
if (data.value)
orderData.value = data.value
const isConfirmDialogVisible = ref(false)
const isUserInfoEditDialogVisible = ref(false)
const isEditAddressDialogVisible = ref(false)
const headers = [
{
title: 'Product',
key: 'productName',
},
{
title: 'Price',
key: 'price',
},
{
title: 'Quantity',
key: 'quantity',
},
{
title: 'Total',
key: 'total',
},
]
const resolvePaymentStatus = payment => {
if (payment === 1)
return {
text: 'Paid',
color: 'success',
}
if (payment === 2)
return {
text: 'Pending',
color: 'warning',
}
if (payment === 3)
return {
text: 'Cancelled',
color: 'secondary',
}
if (payment === 4)
return {
text: 'Failed',
color: 'error',
}
}
const resolveStatus = status => {
if (status === 'Delivered')
return {
text: 'Delivered',
color: 'success',
}
if (status === 'Out for Delivery')
return {
text: 'Out for Delivery',
color: 'primary',
}
if (status === 'Ready to Pickup')
return {
text: 'Ready to Pickup',
color: 'info',
}
if (status === 'Dispatched')
return {
text: 'Dispatched',
color: 'warning',
}
}
const userData = {
id: null,
fullName: orderData.value?.customer,
company: 'Pixinvent',
role: 'Web developer',
username: 'T1940',
country: 'United States',
contact: '+1 (609) 972-22-22',
email: orderData.value?.email,
status: 'Active',
taxId: 'Tax-8894',
language: 'English',
currentPlan: '',
avatar: '',
taskDone: null,
projectDone: null,
}
const currentBillingAddress = {
fullName: orderData.value?.customer,
firstName: orderData.value?.customer.split(' ')[0],
lastName: orderData.value?.customer.split(' ')[1],
selectedCountry: 'USA',
addressLine1: '45 Rocker Terrace',
addressLine2: 'Latheronwheel',
landmark: 'KW5 8NW, London',
contact: '+1 (609) 972-22-22',
country: 'USA',
city: 'London',
state: 'London',
zipCode: 110001,
}
const orderDetail = [
{
productName: 'OnePlus 7 Pro',
productImage: product21,
subtitle: 'Storage: 128gb',
price: 799,
quantity: 1,
total: 799,
},
{
productName: 'Face Cream',
productImage: product22,
subtitle: 'Gender: Women',
price: 89,
quantity: 1,
total: 89,
},
{
productName: 'Wooden Chair',
productImage: product23,
subtitle: 'Material: Woodem',
price: 289,
quantity: 2,
total: 578,
},
{
productName: 'Nike Jorden',
productImage: product24,
subtitle: 'Size: 8UK',
price: 299,
quantity: 2,
total: 598,
},
]
</script>
<template>
<div>
<div class="d-flex justify-space-between align-center flex-wrap gap-y-4 mb-6">
<div>
<div class="d-flex gap-2 align-center mb-2 flex-wrap">
<h5 class="text-h5">
Order #{{ route.params.id }}
</h5>
<div class="d-flex gap-x-2">
<VChip
v-if="orderData?.payment"
variant="tonal"
:color="resolvePaymentStatus(orderData.payment)?.color"
label
size="small"
>
{{ resolvePaymentStatus(orderData.payment)?.text }}
</VChip>
<VChip
v-if="orderData?.status"
v-bind="resolveStatus(orderData?.status)"
label
size="small"
/>
</div>
</div>
<div class="text-body-1">
Aug 17, 2020, 5:48 (ET)
</div>
</div>
<VBtn
variant="tonal"
color="error"
@click="isConfirmDialogVisible = !isConfirmDialogVisible"
>
Delete Order
</VBtn>
</div>
<VRow>
<VCol
cols="12"
md="8"
>
<!-- 👉 Order Details -->
<VCard class="mb-6">
<VCardItem>
<template #title>
<h5 class="text-h5">
Order Details
</h5>
</template>
<template #append>
<div class="text-base font-weight-medium text-primary cursor-pointer">
Edit
</div>
</template>
</VCardItem>
<VDivider />
<VDataTable
:headers="headers"
:items="orderDetail"
item-value="productName"
show-select
class="text-no-wrap"
>
<template #item.productName="{ item }">
<div class="d-flex gap-x-3 align-center">
<VAvatar
size="34"
:image="item.productImage"
:rounded="0"
/>
<div class="d-flex flex-column align-start">
<h6 class="text-h6">
{{ item.productName }}
</h6>
<span class="text-body-2">
{{ item.subtitle }}
</span>
</div>
</div>
</template>
<template #item.price="{ item }">
<div class="text-body-1">
${{ item.price }}
</div>
</template>
<template #item.total="{ item }">
<div class="text-body-1">
${{ item.total }}
</div>
</template>
<template #item.quantity="{ item }">
<div class="text-body-1">
{{ item.quantity }}
</div>
</template>
<template #bottom />
</VDataTable>
<VDivider />
<VCardText>
<div class="d-flex align-end flex-column">
<table class="text-high-emphasis">
<tbody>
<tr>
<td width="200px">
Subtotal:
</td>
<td class="font-weight-medium">
$2,093
</td>
</tr>
<tr>
<td>Shipping Total: </td>
<td class="font-weight-medium">
$2
</td>
</tr>
<tr>
<td>Tax: </td>
<td class="font-weight-medium">
$28
</td>
</tr>
<tr>
<td class="text-high-emphasis font-weight-medium">
Total:
</td>
<td class="font-weight-medium">
$2,113
</td>
</tr>
</tbody>
</table>
</div>
</VCardText>
</VCard>
<!-- 👉 Shipping Activity -->
<VCard title="Shipping Activity">
<VCardText>
<VTimeline
truncate-line="both"
line-inset="9"
align="start"
side="end"
line-color="primary"
density="compact"
>
<VTimelineItem
dot-color="primary"
size="x-small"
>
<div class="d-flex justify-space-between align-center">
<div class="app-timeline-title">
Order was placed (Order ID: #32543)
</div>
<div class="app-timeline-meta">
Tuesday 10:20 AM
</div>
</div>
<p class="app-timeline-text mb-0 mt-3">
Your order has been placed successfully
</p>
</VTimelineItem>
<VTimelineItem
dot-color="primary"
size="x-small"
>
<div class="d-flex justify-space-between align-center">
<span class="app-timeline-title">Pick-up</span>
<span class="app-timeline-meta">Wednesday 11:29 AM</span>
</div>
<p class="app-timeline-text mb-0 mt-3">
Pick-up scheduled with courier
</p>
</VTimelineItem>
<VTimelineItem
dot-color="primary"
size="x-small"
>
<div class="d-flex justify-space-between align-center">
<span class="app-timeline-title">Dispatched</span>
<span class="app-timeline-meta">Thursday 8:15 AM</span>
</div>
<p class="app-timeline-text mb-0 mt-3">
Item has been picked up by courier.
</p>
</VTimelineItem>
<VTimelineItem
dot-color="primary"
size="x-small"
>
<div class="d-flex justify-space-between align-center">
<span class="app-timeline-title">Package arrived</span>
<span class="app-timeline-meta">Saturday 15:20 AM</span>
</div>
<p class="app-timeline-text mb-0 mt-3">
Package arrived at an Amazon facility, NY
</p>
</VTimelineItem>
<VTimelineItem
dot-color="primary"
size="x-small"
>
<div class="d-flex justify-space-between align-center">
<span class="app-timeline-title">Dispatched for delivery</span>
<span class="app-timeline-meta">Today 14:12 PM</span>
</div>
<p class="app-timeline-text mb-0 mt-3">
Package has left an Amazon facility , NY
</p>
</VTimelineItem>
<VTimelineItem
dot-color="secondary"
size="x-small"
>
<div class="d-flex justify-space-between align-center">
<span class="app-timeline-title">Delivery</span>
</div>
<p class="app-timeline-text mb-4 mt-3">
Package will be delivered by tomorrow
</p>
</VTimelineItem>
</VTimeline>
</VCardText>
</VCard>
</VCol>
<VCol
cols="12"
md="4"
>
<!-- 👉 Customer Details -->
<VCard class="mb-6">
<VCardText class="d-flex flex-column gap-y-6">
<h5 class="text-h5">
Customer details
</h5>
<div class="d-flex align-center">
<VAvatar
v-if="orderData"
:variant="!orderData?.avatar.length ? 'tonal' : undefined"
:rounded="1"
class="me-3"
>
<VImg
v-if="orderData?.avatar"
:src="orderData?.avatar"
/>
<span
v-else
class="font-weight-medium"
>{{ avatarText(orderData?.customer) }}</span>
</VAvatar>
<div>
<h6 class="text-h6">
{{ orderData?.customer }}
</h6>
<div class="text-body-1">
Customer ID: #{{ orderData?.order }}
</div>
</div>
</div>
<div class="d-flex gap-x-3 align-center">
<VAvatar
variant="tonal"
color="success"
>
<VIcon icon="tabler-shopping-cart" />
</VAvatar>
<h6 class="text-h6">
12 Orders
</h6>
</div>
<div class="d-flex flex-column gap-y-1">
<div class="d-flex justify-space-between align-center">
<h6 class="text-h6">
Contact Info
</h6>
<div
class="text-base text-primary cursor-pointer font-weight-medium"
@click="isUserInfoEditDialogVisible = !isUserInfoEditDialogVisible"
>
Edit
</div>
</div>
<span>Email: {{ orderData?.email }}</span>
<span>Mobile: +1 (609) 972-22-22</span>
</div>
</VCardText>
</VCard>
<!-- 👉 Shipping Address -->
<VCard class="mb-6">
<VCardItem>
<VCardTitle>Shipping Address</VCardTitle>
<template #append>
<div class="d-flex align-center justify-space-between">
<div
class="text-base font-weight-medium text-primary cursor-pointer"
@click="isEditAddressDialogVisible = !isEditAddressDialogVisible"
>
Edit
</div>
</div>
</template>
</VCardItem>
<VCardText>
<div class="text-body-1">
45 Rocker Terrace <br> Latheronwheel <br> KW5 8NW, London <br> UK
</div>
</VCardText>
</VCard>
<!-- 👉 Billing Address -->
<VCard>
<VCardText>
<div class="d-flex align-center justify-space-between mb-2">
<h5 class="text-h5">
Billing Address
</h5>
<div
class="text-base font-weight-medium text-primary cursor-pointer"
@click="isEditAddressDialogVisible = !isEditAddressDialogVisible"
>
Edit
</div>
</div>
<div>
45 Rocker Terrace <br> Latheronwheel <br> KW5 8NW, London <br> UK
</div>
<div class="mt-6">
<h5 class="text-h5 mb-1">
Mastercard
</h5>
<div class="text-body-1">
Card Number: ******{{ orderData?.methodNumber }}
</div>
</div>
</VCardText>
</VCard>
</VCol>
</VRow>
<ConfirmDialog
v-model:is-dialog-visible="isConfirmDialogVisible"
confirmation-question="Are you sure to cancel your Order?"
cancel-msg="Order cancelled!!"
cancel-title="Cancelled"
confirm-msg="Your order cancelled successfully."
confirm-title="Cancelled!"
/>
<UserInfoEditDialog
v-model:is-dialog-visible="isUserInfoEditDialogVisible"
:user-data="userData"
/>
<AddEditAddressDialog
v-model:is-dialog-visible="isEditAddressDialogVisible"
:billing-address="currentBillingAddress"
/>
</div>
</template>

View File

@@ -0,0 +1,386 @@
<script setup>
import masterCardDark from '@images/icons/payments/img/master-dark.png'
import masterCardLight from '@images/icons/payments/img/mastercard.png'
import paypalDark from '@images/icons/payments/img/paypal-dark.png'
import paypalLight from '@images/icons/payments/img/paypal-light.png'
const widgetData = ref([
{
title: 'Pending Payment',
value: 56,
icon: 'tabler-calendar-stats',
},
{
title: 'Completed',
value: 12689,
icon: 'tabler-checks',
},
{
title: 'Refunded',
value: 124,
icon: 'tabler-wallet',
},
{
title: 'Failed',
value: 32,
icon: 'tabler-alert-octagon',
},
])
const mastercard = useGenerateImageVariant(masterCardLight, masterCardDark)
const paypal = useGenerateImageVariant(paypalLight, paypalDark)
const searchQuery = ref('')
// Data table options
const itemsPerPage = ref(10)
const page = ref(1)
const sortBy = ref()
const orderBy = ref()
const selectedRows = ref([])
// Data table Headers
const headers = [
{
title: 'Order',
key: 'order',
},
{
title: 'Date',
key: 'date',
},
{
title: 'Customers',
key: 'customers',
},
{
title: 'Payment',
key: 'payment',
sortable: false,
},
{
title: 'Status',
key: 'status',
},
{
title: 'Method',
key: 'method',
sortable: false,
},
{
title: 'Action',
key: 'actions',
sortable: false,
},
]
const updateOptions = options => {
sortBy.value = options.sortBy[0]?.key
orderBy.value = options.sortBy[0]?.order
}
const resolvePaymentStatus = status => {
if (status === 1)
return {
text: 'Paid',
color: 'success',
}
if (status === 2)
return {
text: 'Pending',
color: 'warning',
}
if (status === 3)
return {
text: 'Cancelled',
color: 'secondary',
}
if (status === 4)
return {
text: 'Failed',
color: 'error',
}
}
const resolveStatus = status => {
if (status === 'Delivered')
return {
text: 'Delivered',
color: 'success',
}
if (status === 'Out for Delivery')
return {
text: 'Out for Delivery',
color: 'primary',
}
if (status === 'Ready to Pickup')
return {
text: 'Ready to Pickup',
color: 'info',
}
if (status === 'Dispatched')
return {
text: 'Dispatched',
color: 'warning',
}
}
const {
data: ordersData,
execute: fetchOrders,
} = await useApi(createUrl('/apps/ecommerce/orders', {
query: {
q: searchQuery,
page,
itemsPerPage,
sortBy,
orderBy,
},
}))
const orders = computed(() => ordersData.value.orders)
const totalOrder = computed(() => ordersData.value.total)
const deleteOrder = async id => {
await $api(`/apps/ecommerce/orders/${ id }`, { method: 'DELETE' })
// Delete from selectedRows
const index = selectedRows.value.findIndex(row => row === id)
if (index !== -1)
selectedRows.value.splice(index, 1)
// Refetch Orders
fetchOrders()
}
</script>
<template>
<div>
<VCard class="mb-6">
<!-- 👉 Widgets -->
<VCardText>
<VRow>
<template
v-for="(data, id) in widgetData"
:key="id"
>
<VCol
cols="12"
sm="6"
md="3"
class="px-6"
>
<div
class="d-flex justify-space-between"
:class="$vuetify.display.xs
? id !== widgetData.length - 1 ? 'border-b pb-4' : ''
: $vuetify.display.sm
? id < (widgetData.length / 2) ? 'border-b pb-4' : ''
: ''"
>
<div class="d-flex flex-column">
<h4 class="text-h4">
{{ data.value }}
</h4>
<div class="text-body-1">
{{ data.title }}
</div>
</div>
<VAvatar
variant="tonal"
rounded
size="42"
>
<VIcon
:icon="data.icon"
size="26"
class="text-high-emphasis"
/>
</VAvatar>
</div>
</VCol>
<VDivider
v-if="$vuetify.display.mdAndUp ? id !== widgetData.length - 1
: $vuetify.display.smAndUp ? id % 2 === 0
: false"
vertical
inset
length="60"
/>
</template>
</VRow>
</VCardText>
</VCard>
<VCard>
<!-- 👉 Filters -->
<VCardText>
<div class="d-flex justify-sm-space-between justify-start flex-wrap gap-4">
<AppTextField
v-model="searchQuery"
placeholder="Search Order"
style=" max-inline-size: 200px; min-inline-size: 200px;"
/>
<div class="d-flex gap-x-4 align-center">
<AppSelect
v-model="itemsPerPage"
style="min-inline-size: 6.25rem;"
:items="[5, 10, 20, 50, 100]"
/>
<VBtn
variant="tonal"
color="secondary"
prepend-icon="tabler-upload"
text="Export"
/>
</div>
</div>
</VCardText>
<VDivider />
<!-- 👉 Order Table -->
<VDataTableServer
v-model:items-per-page="itemsPerPage"
v-model:model-value="selectedRows"
v-model:page="page"
:headers="headers"
:items="orders"
:items-length="totalOrder"
show-select
class="text-no-wrap"
@update:options="updateOptions"
>
<!-- Order ID -->
<template #item.order="{ item }">
<RouterLink :to="{ name: 'apps-ecommerce-order-details-id', params: { id: item.order } }">
#{{ item.order }}
</RouterLink>
</template>
<!-- Date -->
<template #item.date="{ item }">
{{ new Date(item.date).toDateString() }}
</template>
<!-- Customers -->
<template #item.customers="{ item }">
<div class="d-flex align-center gap-x-3">
<VAvatar
size="34"
:color="!item.avatar.length ? 'primary' : ''"
:variant="!item.avatar.length ? 'tonal' : undefined"
>
<VImg
v-if="item.avatar"
:src="item.avatar"
/>
<span
v-else
class="font-weight-medium"
>{{ avatarText(item.customer) }}</span>
</VAvatar>
<div class="d-flex flex-column">
<div class="text-body-1 font-weight-medium">
<RouterLink
:to="{ name: 'pages-user-profile-tab', params: { tab: 'profile' } }"
class="text-link"
>
{{ item.customer }}
</RouterLink>
</div>
<div class="text-body-2">
{{ item.email }}
</div>
</div>
</div>
</template>
<!-- Payments -->
<template #item.payment="{ item }">
<div
:class="`text-${resolvePaymentStatus(item.payment)?.color}`"
class="font-weight-medium d-flex align-center gap-x-2"
>
<VIcon
icon="tabler-circle-filled"
size="10"
/>
<div style="line-height: 22px;">
{{ resolvePaymentStatus(item.payment)?.text }}
</div>
</div>
</template>
<!-- Status -->
<template #item.status="{ item }">
<VChip
v-bind="resolveStatus(item.status)"
label
size="small"
/>
</template>
<!-- Method -->
<template #item.method="{ item }">
<div class="d-flex align-center">
<img
:src="item.method === 'mastercard' ? mastercard : paypal"
height="18"
>
<div class="text-body-1">
...{{ item.method === 'mastercard' ? item.methodNumber : '@gmail.com' }}
</div>
</div>
</template>
<!-- Actions -->
<template #item.actions="{ item }">
<IconBtn>
<VIcon icon="tabler-dots-vertical" />
<VMenu activator="parent">
<VList>
<VListItem
value="view"
:to="{ name: 'apps-ecommerce-order-details-id', params: { id: item.order } }"
>
View
</VListItem>
<VListItem
value="delete"
@click="deleteOrder(item.id)"
>
Delete
</VListItem>
</VList>
</VMenu>
</IconBtn>
</template>
<!-- pagination -->
<template #bottom>
<TablePagination
v-model:page="page"
:items-per-page="itemsPerPage"
:total-items="totalOrder"
/>
</template>
</VDataTableServer>
</VCard>
</div>
</template>
<style lang="scss" scoped>
.customer-title:hover {
color: rgba(var(--v-theme-primary)) !important;
}
.product-widget {
border-block-end: 1px solid rgba(var(--v-theme-on-surface), var(--v-border-opacity));
padding-block-end: 1rem;
}
</style>

View File

@@ -0,0 +1,570 @@
<script setup>
import { ref } from 'vue'
const optionCounter = ref(1)
const activeTab = ref('Restock')
const isTaxChargeToProduct = ref(true)
const shippingList = [
{
desc: 'You\'ll be responsible for product delivery.Any damage or delay during shipping may cost you a Damage fee',
title: 'Fulfilled by Seller',
value: 'Fulfilled by Seller',
},
{
desc: 'Your product, Our responsibility.For a measly fee, we will handle the delivery process for you.',
title: 'Fulfilled by Company name',
value: 'Fulfilled by Company name',
},
]
const shippingType = ref('Fulfilled by Company name')
const deliveryType = ref('Worldwide delivery')
const selectedAttrs = ref([
'Biodegradable',
'Expiry Date',
])
const inventoryTabsData = [
{
icon: 'tabler-cube',
title: 'Restock',
value: 'Restock',
},
{
icon: 'tabler-car',
title: 'Shipping',
value: 'Shipping',
},
{
icon: 'tabler-map-pin',
title: 'Global Delivery',
value: 'Global Delivery',
},
{
icon: 'tabler-world',
title: 'Attributes',
value: 'Attributes',
},
{
icon: 'tabler-lock',
title: 'Advanced',
value: 'Advanced',
},
]
const content = ref(`<p>
Keep your account secure with authentication step.
</p>`)
</script>
<template>
<div>
<div class="d-flex flex-wrap justify-start justify-sm-space-between gap-y-4 gap-x-6 mb-6">
<div class="d-flex flex-column justify-center">
<h4 class="text-h4 font-weight-medium">
Add a new product
</h4>
<div class="text-body-1">
Orders placed across your store
</div>
</div>
<div class="d-flex gap-4 align-center flex-wrap">
<VBtn
variant="tonal"
color="secondary"
>
Discard
</VBtn>
<VBtn
variant="tonal"
color="primary"
>
Save Draft
</VBtn>
<VBtn>Publish Product</VBtn>
</div>
</div>
<VRow>
<VCol md="8">
<!-- 👉 Product Information -->
<VCard
class="mb-6"
title="Product Information"
>
<VCardText>
<VRow>
<VCol cols="12">
<AppTextField
label="Name"
placeholder="iPhone 14"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
label="SKU"
placeholder="FXSK123U"
/>
</VCol>
<VCol
cols="12"
md="6"
>
<AppTextField
label="Barcode"
placeholder="0123-4567"
/>
</VCol>
<VCol>
<span class="mb-1">Description (optional)</span>
<ProductDescriptionEditor
v-model="content"
placeholder="Product Description"
class="border rounded"
/>
</VCol>
</VRow>
</VCardText>
</VCard>
<!-- 👉 Media -->
<VCard class="mb-6">
<VCardItem>
<template #title>
Product Image
</template>
<template #append>
<span class="text-primary font-weight-medium text-sm cursor-pointer">Add Media from URL</span>
</template>
</VCardItem>
<VCardText>
<DropZone />
</VCardText>
</VCard>
<!-- 👉 Variants -->
<VCard
title="Variants"
class="mb-6"
>
<VCardText>
<template
v-for="i in optionCounter"
:key="i"
>
<VRow>
<VCol
cols="12"
md="4"
>
<AppSelect
:items="['Size', 'Color', 'Weight']"
placeholder="Select Variant"
label="Options"
/>
</VCol>
<VCol
cols="12"
md="8"
class="d-flex align-self-end"
>
<AppTextField
placeholder="38"
type="number"
/>
</VCol>
</VRow>
</template>
<VBtn
class="mt-6"
prepend-icon="tabler-plus"
@click="optionCounter++"
>
Add another option
</VBtn>
</VCardText>
</VCard>
<!-- 👉 Inventory -->
<VCard
title="Inventory"
class="inventory-card"
>
<VCardText>
<VRow>
<VCol
cols="12"
md="4"
>
<div class="pe-3">
<VTabs
v-model="activeTab"
direction="vertical"
color="primary"
class="v-tabs-pill"
>
<VTab
v-for="(tab, index) in inventoryTabsData"
:key="index"
>
<VIcon
:icon="tab.icon"
class="me-2"
/>
<div class="text-truncate font-weight-medium text-start">
{{ tab.title }}
</div>
</VTab>
</VTabs>
</div>
</VCol>
<VDivider :vertical="!$vuetify.display.smAndDown" />
<VCol
cols="12"
md="8"
>
<VWindow
v-model="activeTab"
class="w-100"
:touch="false"
>
<VWindowItem value="Restock">
<div class="d-flex flex-column gap-y-4 ps-3">
<p class="mb-0">
Options
</p>
<div class="d-flex gap-x-4 align-center">
<AppTextField
label="Add to Stock"
placeholder="Quantity"
/>
<VBtn class="align-self-end">
Confirm
</VBtn>
</div>
<div>
<div class="text-base text-high-emphasis pb-2">
Product in stock now: 54
</div>
<div class="text-base text-high-emphasis pb-2">
Product in transit: 390
</div>
<div class="text-base text-high-emphasis pb-2">
Last time restocked: 24th June, 2022
</div>
<div class="text-base text-high-emphasis pb-2">
Total stock over lifetime: 2,430
</div>
</div>
</div>
</VWindowItem>
<VWindowItem value="Shipping">
<VRadioGroup
v-model="shippingType"
label="Shipping Type"
class="ms-3"
>
<VRadio
v-for="item in shippingList"
:key="item.value"
:value="item.value"
class="mb-4"
>
<template #label>
<div>
<div class="text-high-emphasis font-weight-medium mb-1">
{{ item.title }}
</div>
<div class="text-sm">
{{ item.desc }}
</div>
</div>
</template>
</VRadio>
</VRadioGroup>
</VWindowItem>
<VWindowItem value="Global Delivery">
<div class="ps-3">
<h5 class="text-h5 mb-6">
Global Delivery
</h5>
<VRadioGroup
v-model="deliveryType"
label="Global Delivery"
>
<VRadio
value="Worldwide delivery"
class="mb-4"
>
<template #label>
<div>
<div class="text-high-emphasis font-weight-medium mb-1">
Worldwide delivery
</div>
<div class="text-sm">
Only available with Shipping method:
<span class="text-primary">
Fulfilled by Company name
</span>
</div>
</div>
</template>
</VRadio>
<VRadio
value="Selected Countries"
class="mb-4"
>
<template #label>
<div>
<div class="text-high-emphasis font-weight-medium mb-1">
Selected Countries
</div>
<VTextField
placeholder="USA"
style="min-inline-size: 200px;"
/>
</div>
</template>
</VRadio>
<VRadio>
<template #label>
<div>
<div class="text-high-emphasis font-weight-medium mb-1">
Local delivery
</div>
<div class="text-sm">
Deliver to your country of residence
<span class="text-primary">
Change profile address
</span>
</div>
</div>
</template>
</VRadio>
</VRadioGroup>
</div>
</VWindowItem>
<VWindowItem value="Attributes">
<div class="ps-3">
<div class="mb-6 text-h6">
Attributes
</div>
<div class="d-flex flex-column gap-y-1">
<VCheckbox
v-model="selectedAttrs"
label="Fragile Product"
value="Fragile Product"
/>
<VCheckbox
v-model="selectedAttrs"
value="Biodegradable"
label="Biodegradable"
/>
<VCheckbox
v-model="selectedAttrs"
value="Frozen Product"
>
<template #label>
<div class="d-flex flex-column mb-1">
<div>Frozen Product</div>
<VTextField
placeholder="40 C"
type="number"
/>
</div>
</template>
</VCheckbox>
<VCheckbox
v-model="selectedAttrs"
value="Expiry Date"
>
<template #label>
<div class="d-flex flex-column mb-1">
<div>Expiry Date of Product</div>
<AppDateTimePicker
model-value="2025-06-14"
placeholder="Select a Date"
/>
</div>
</template>
</VCheckbox>
</div>
</div>
</VWindowItem>
<VWindowItem value="Advanced">
<div class="ps-3">
<h5 class="text-h5 mb-6">
Advanced
</h5>
<div class="d-flex flex-sm-row flex-column flex-wrap justify-space-between gap-x-6 gap-y-4">
<AppSelect
label="Product ID Type"
placeholder="Select Product Type"
:items="['ISBN', 'UPC', 'EAN', 'JAN']"
/>
<AppTextField
label="Product Id"
placeholder="100023"
/>
</div>
</div>
</VWindowItem>
</VWindow>
</VCol>
</VRow>
</VCardText>
</VCard>
</VCol>
<VCol
md="4"
cols="12"
>
<!-- 👉 Pricing -->
<VCard
title="Pricing"
class="mb-6"
>
<VCardText>
<AppTextField
label="Best Price"
placeholder="Price"
class="mb-6"
/>
<AppTextField
label="Discounted Price"
placeholder="$499"
class="mb-6"
/>
<VCheckbox
v-model="isTaxChargeToProduct"
label="Charge Tax on this product"
/>
<VDivider class="my-2" />
<div class="d-flex flex-raw align-center justify-space-between ">
<span>In stock</span>
<VSwitch density="compact" />
</div>
</VCardText>
</VCard>
<!-- 👉 Organize -->
<VCard title="Organize">
<VCardText>
<div class="d-flex flex-column gap-y-4">
<AppSelect
placeholder="Select Vendor"
label="Vendor"
:items="['Men\'s Clothing', 'Women\'s Clothing', 'Kid\'s Clothing']"
/>
<div>
<VLabel class="d-flex">
<div class="d-flex text-sm justify-space-between w-100">
<div class="text-high-emphasis">
Category
</div>
</div>
</VLabel>
<div class="d-flex gap-x-4">
<AppSelect
placeholder="Select Category"
:items="['Household', 'Office', 'Electronics', 'Management', 'Automotive']"
/>
<VBtn
rounded
icon="tabler-plus"
variant="tonal"
/>
</div>
</div>
<AppSelect
placeholder="Select Collection"
label="Collection"
:items="['Men\'s Clothing', 'Women\'s Clothing', 'Kid\'s Clothing']"
/>
<AppSelect
placeholder="Select Status"
label="Status"
:items="['Published', 'Inactive', 'Scheduled']"
/>
<AppTextField
label="Tags"
placeholder="Fashion, Trending, Summer"
/>
</div>
</VCardText>
</VCard>
</VCol>
</VRow>
</div>
</template>
<style lang="scss" scoped>
.drop-zone {
border: 2px dashed rgba(var(--v-theme-on-surface), 0.12);
border-radius: 6px;
}
</style>
<style lang="scss">
.inventory-card {
.v-tabs.v-tabs-pill {
.v-slide-group-item--active.v-tab--selected.text-primary {
h6 {
color: #fff !important;
}
}
}
.v-radio-group,
.v-checkbox {
.v-selection-control {
align-items: start !important;
}
.v-label.custom-input {
border: none !important;
}
}
}
.ProseMirror {
p {
margin-block-end: 0;
}
padding: 0.5rem;
outline: none;
p.is-editor-empty:first-child::before {
block-size: 0;
color: #adb5bd;
content: attr(data-placeholder);
float: inline-start;
pointer-events: none;
}
}
</style>

View File

@@ -0,0 +1,255 @@
<script setup>
import ECommerceAddCategoryDrawer from '@/views/apps/ecommerce/ECommerceAddCategoryDrawer.vue'
import product1 from '@images/ecommerce-images/product-1.png'
import product10 from '@images/ecommerce-images/product-10.png'
import product11 from '@images/ecommerce-images/product-11.png'
import product12 from '@images/ecommerce-images/product-12.png'
import product14 from '@images/ecommerce-images/product-14.png'
import product17 from '@images/ecommerce-images/product-17.png'
import product19 from '@images/ecommerce-images/product-19.png'
import product2 from '@images/ecommerce-images/product-2.png'
import product25 from '@images/ecommerce-images/product-25.png'
import product28 from '@images/ecommerce-images/product-28.png'
import product9 from '@images/ecommerce-images/product-9.png'
const categoryData = ref([
{
id: 1,
categoryTitle: 'Smart Phone',
description: 'Choose from wide range of smartphones online at best prices.',
totalProduct: 12548,
totalEarning: 98784,
image: product1,
},
{
id: 2,
categoryTitle: 'Clothing, Shoes, and jewellery',
description: 'Fashion for a wide selection of clothing, shoes, jewellery and watches.',
totalProduct: 4689,
totalEarning: 45627,
image: product9,
},
{
id: 3,
categoryTitle: 'Home and Kitchen',
description: 'Browse through the wide range of Home and kitchen products.',
totalProduct: 12548,
totalEarning: 98784,
image: product10,
},
{
id: 4,
categoryTitle: 'Beauty and Personal Care',
description: 'Explore beauty and personal care products, shop makeup and etc.',
totalProduct: 12548,
totalEarning: 98784,
image: product19,
},
{
id: 5,
categoryTitle: 'Books',
description: 'Over 25 million titles across categories such as business and etc.',
totalProduct: 12548,
totalEarning: 98784,
image: product25,
},
{
id: 6,
categoryTitle: 'Games',
description: 'Every month, get exclusive in-game loot, free games, a free subscription.',
totalProduct: 12548,
totalEarning: 98784,
image: product12,
},
{
id: 7,
categoryTitle: 'Baby Products',
description: 'Buy baby products across different categories from top brands.',
totalProduct: 12548,
totalEarning: 98784,
image: product14,
},
{
id: 8,
categoryTitle: 'Grocery',
description: 'Shop grocery Items through at best prices in India.',
totalProduct: 12548,
totalEarning: 98784,
image: product28,
},
{
id: 9,
categoryTitle: 'Computer Accessories',
description: 'Enhance your computing experience with our range of computer accessories.',
totalProduct: 9876,
totalEarning: 65421,
image: product17,
},
{
id: 10,
categoryTitle: 'Fitness Tracker',
description: 'Monitor your health and fitness goals with our range of advanced fitness trackers.',
totalProduct: 1987,
totalEarning: 32067,
image: product10,
},
{
id: 11,
categoryTitle: 'Smart Home Devices',
description: 'Transform your home into a smart home with our innovative smart home devices.',
totalProduct: 2345,
totalEarning: 87654,
image: product11,
},
{
id: 12,
categoryTitle: 'Audio Speakers',
description: 'Immerse yourself in rich audio quality with our wide range of speakers.',
totalProduct: 5678,
totalEarning: 32145,
image: product2,
},
])
const headers = [
{
title: 'Categories',
key: 'categoryTitle',
},
{
title: 'Total Products',
key: 'totalProduct',
},
{
title: 'Total Earning',
key: 'totalEarning',
},
{
title: 'Actions',
key: 'actions',
sortable: false,
},
]
const itemsPerPage = ref(10)
const page = ref(1)
const searchQuery = ref('')
const isAddProductDrawerOpen = ref(false)
</script>
<template>
<div>
<VCard>
<VCardText>
<div class="d-flex justify-sm-space-between flex-wrap gap-y-4 gap-x-6 justify-start">
<AppTextField
v-model="searchQuery"
placeholder="Search Category"
style="max-inline-size: 280px; min-inline-size: 280px;"
/>
<div class="d-flex align-center flex-wrap gap-4">
<AppSelect
v-model="itemsPerPage"
:items="[5, 10, 15]"
style="max-inline-size: 100px; min-inline-size: 100px;"
/>
<VBtn
prepend-icon="tabler-plus"
@click="isAddProductDrawerOpen = !isAddProductDrawerOpen"
>
Add Category
</VBtn>
</div>
</div>
</VCardText>
<VDivider />
<div class="category-table">
<VDataTable
v-model:items-per-page="itemsPerPage"
v-model:page="page"
:headers="headers"
:items="categoryData"
item-value="categoryTitle"
:search="searchQuery"
show-select
class="text-no-wrap"
>
<template #item.actions>
<IconBtn>
<VIcon
icon="tabler-edit"
size="22"
/>
</IconBtn>
<IconBtn>
<VIcon
icon="tabler-dots-vertical"
size="22"
/>
</IconBtn>
</template>
<template #item.categoryTitle="{ item }">
<div class="d-flex gap-x-3 align-center">
<VAvatar
variant="tonal"
rounded
size="38"
>
<img
:src="item.image"
:alt="item.categoryTitle"
width="38"
height="38"
>
</VAvatar>
<div>
<h6 class="text-h6">
{{ item.categoryTitle }}
</h6>
<div class="text-body-2">
{{ item.description }}
</div>
</div>
</div>
</template>
<template #item.totalEarning="{ item }">
<div class="text-body-1 text-end pe-4">
{{ (item.totalEarning).toLocaleString("en-IN", { style: "currency", currency: 'USD' }) }}
</div>
</template>
<template #item.totalProduct="{ item }">
<div class="text-end pe-4">
{{ (item.totalProduct).toLocaleString() }}
</div>
</template>
<template #bottom>
<TablePagination
v-model:page="page"
:items-per-page="itemsPerPage"
:total-items="categoryData.length"
/>
</template>
</VDataTable>
</div>
</VCard>
<ECommerceAddCategoryDrawer v-model:is-drawer-open="isAddProductDrawerOpen" />
</div>
</template>
<style lang="scss">
.category-table {
.v-table {
th:nth-child(3),
th:nth-child(4) {
.v-data-table-header__content {
justify-content: end;
}
}
}
}
</style>

View File

@@ -0,0 +1,495 @@
<script setup>
const widgetData = ref([
{
title: 'In-Store Sales',
value: '$5,345',
icon: 'tabler-smart-home',
desc: '5k orders',
change: 5.7,
},
{
title: 'Website Sales',
value: '$674,347',
icon: 'tabler-device-laptop',
desc: '21k orders',
change: 12.4,
},
{
title: 'Discount',
value: '$14,235',
icon: 'tabler-gift',
desc: '6k orders',
},
{
title: 'Affiliate',
value: '$8,345',
icon: 'tabler-wallet',
desc: '150 orders',
change: -3.5,
},
])
const headers = [
{
title: 'Product',
key: 'product',
},
{
title: 'Category',
key: 'category',
},
{
title: 'Stock',
key: 'stock',
sortable: false,
},
{
title: 'SKU',
key: 'sku',
},
{
title: 'Price',
key: 'price',
},
{
title: 'QTY',
key: 'qty',
},
{
title: 'Status',
key: 'status',
},
{
title: 'Actions',
key: 'actions',
sortable: false,
},
]
const selectedStatus = ref()
const selectedCategory = ref()
const selectedStock = ref()
const searchQuery = ref('')
const selectedRows = ref([])
const status = ref([
{
title: 'Scheduled',
value: 'Scheduled',
},
{
title: 'Publish',
value: 'Published',
},
{
title: 'Inactive',
value: 'Inactive',
},
])
const categories = ref([
{
title: 'Accessories',
value: 'Accessories',
},
{
title: 'Home Decor',
value: 'Home Decor',
},
{
title: 'Electronics',
value: 'Electronics',
},
{
title: 'Shoes',
value: 'Shoes',
},
{
title: 'Office',
value: 'Office',
},
{
title: 'Games',
value: 'Games',
},
])
const stockStatus = ref([
{
title: 'In Stock',
value: true,
},
{
title: 'Out of Stock',
value: false,
},
])
// Data table options
const itemsPerPage = ref(10)
const page = ref(1)
const sortBy = ref()
const orderBy = ref()
const updateOptions = options => {
sortBy.value = options.sortBy[0]?.key
orderBy.value = options.sortBy[0]?.order
}
const resolveCategory = category => {
if (category === 'Accessories')
return {
color: 'error',
icon: 'tabler-device-watch',
}
if (category === 'Home Decor')
return {
color: 'info',
icon: 'tabler-home',
}
if (category === 'Electronics')
return {
color: 'primary',
icon: 'tabler-device-imac',
}
if (category === 'Shoes')
return {
color: 'success',
icon: 'tabler-shoe',
}
if (category === 'Office')
return {
color: 'warning',
icon: 'tabler-briefcase',
}
if (category === 'Games')
return {
color: 'primary',
icon: 'tabler-device-gamepad-2',
}
}
const resolveStatus = statusMsg => {
if (statusMsg === 'Scheduled')
return {
text: 'Scheduled',
color: 'warning',
}
if (statusMsg === 'Published')
return {
text: 'Publish',
color: 'success',
}
if (statusMsg === 'Inactive')
return {
text: 'Inactive',
color: 'error',
}
}
const {
data: productsData,
execute: fetchProducts,
} = await useApi(createUrl('/apps/ecommerce/products', {
query: {
q: searchQuery,
stock: selectedStock,
category: selectedCategory,
status: selectedStatus,
page,
itemsPerPage,
sortBy,
orderBy,
},
}))
const products = computed(() => productsData.value.products)
const totalProduct = computed(() => productsData.value.total)
const deleteProduct = async id => {
await $api(`apps/ecommerce/products/${ id }`, { method: 'DELETE' })
// Delete from selectedRows
const index = selectedRows.value.findIndex(row => row === id)
if (index !== -1)
selectedRows.value.splice(index, 1)
// Refetch products
fetchProducts()
}
</script>
<template>
<div>
<!-- 👉 widgets -->
<VCard class="mb-6">
<VCardText class="px-3">
<VRow>
<template
v-for="(data, id) in widgetData"
:key="id"
>
<VCol
cols="12"
sm="6"
md="3"
class="px-6"
>
<div
class="d-flex justify-space-between"
:class="$vuetify.display.xs
? id !== widgetData.length - 1 ? 'border-b pb-4' : ''
: $vuetify.display.sm
? id < (widgetData.length / 2) ? 'border-b pb-4' : ''
: ''"
>
<div class="d-flex flex-column gap-y-1">
<div class="text-body-1 text-capitalize">
{{ data.title }}
</div>
<h4 class="text-h4">
{{ data.value }}
</h4>
<div class="d-flex align-center gap-x-2">
<div class="text-no-wrap">
{{ data.desc }}
</div>
<VChip
v-if="data.change"
label
:color="data.change > 0 ? 'success' : 'error'"
size="small"
>
{{ prefixWithPlus(data.change) }}%
</VChip>
</div>
</div>
<VAvatar
variant="tonal"
rounded
size="44"
>
<VIcon
:icon="data.icon"
size="28"
class="text-high-emphasis"
/>
</VAvatar>
</div>
</VCol>
<VDivider
v-if="$vuetify.display.mdAndUp ? id !== widgetData.length - 1
: $vuetify.display.smAndUp ? id % 2 === 0
: false"
vertical
inset
length="92"
/>
</template>
</VRow>
</VCardText>
</VCard>
<!-- 👉 products -->
<VCard
title="Filters"
class="mb-6"
>
<VCardText>
<VRow>
<!-- 👉 Select Status -->
<VCol
cols="12"
sm="4"
>
<AppSelect
v-model="selectedStatus"
placeholder="Status"
:items="status"
clearable
clear-icon="tabler-x"
/>
</VCol>
<!-- 👉 Select Category -->
<VCol
cols="12"
sm="4"
>
<AppSelect
v-model="selectedCategory"
placeholder="Category"
:items="categories"
clearable
clear-icon="tabler-x"
/>
</VCol>
<!-- 👉 Select Stock Status -->
<VCol
cols="12"
sm="4"
>
<AppSelect
v-model="selectedStock"
placeholder="Stock"
:items="stockStatus"
clearable
clear-icon="tabler-x"
/>
</VCol>
</VRow>
</VCardText>
<VDivider />
<div class="d-flex flex-wrap gap-4 ma-6">
<div class="d-flex align-center">
<!-- 👉 Search -->
<AppTextField
v-model="searchQuery"
placeholder="Search Product"
style="inline-size: 200px;"
class="me-3"
/>
</div>
<VSpacer />
<div class="d-flex gap-4 flex-wrap align-center">
<AppSelect
v-model="itemsPerPage"
:items="[5, 10, 20, 25, 50]"
/>
<!-- 👉 Export button -->
<VBtn
variant="tonal"
color="secondary"
prepend-icon="tabler-upload"
>
Export
</VBtn>
<VBtn
color="primary"
prepend-icon="tabler-plus"
@click="$router.push('/apps/ecommerce/product/add')"
>
Add Product
</VBtn>
</div>
</div>
<VDivider class="mt-4" />
<!-- 👉 Datatable -->
<VDataTableServer
v-model:items-per-page="itemsPerPage"
v-model:model-value="selectedRows"
v-model:page="page"
:headers="headers"
show-select
:items="products"
:items-length="totalProduct"
class="text-no-wrap"
@update:options="updateOptions"
>
<!-- product -->
<template #item.product="{ item }">
<div class="d-flex align-center gap-x-4">
<VAvatar
v-if="item.image"
size="38"
variant="tonal"
rounded
:image="item.image"
/>
<div class="d-flex flex-column">
<span class="text-body-1 font-weight-medium text-high-emphasis">{{ item.productName }}</span>
<span class="text-body-2">{{ item.productBrand }}</span>
</div>
</div>
</template>
<!-- category -->
<template #item.category="{ item }">
<VAvatar
size="30"
variant="tonal"
:color="resolveCategory(item.category)?.color"
class="me-4"
>
<VIcon
:icon="resolveCategory(item.category)?.icon"
size="18"
/>
</VAvatar>
<span class="text-body-1 text-high-emphasis">{{ item.category }}</span>
</template>
<!-- stock -->
<template #item.stock="{ item }">
<VSwitch :model-value="item.stock" />
</template>
<!-- status -->
<template #item.status="{ item }">
<VChip
v-bind="resolveStatus(item.status)"
density="default"
label
size="small"
/>
</template>
<!-- Actions -->
<template #item.actions="{ item }">
<IconBtn>
<VIcon icon="tabler-edit" />
</IconBtn>
<IconBtn>
<VIcon icon="tabler-dots-vertical" />
<VMenu activator="parent">
<VList>
<VListItem
value="download"
prepend-icon="tabler-download"
>
Download
</VListItem>
<VListItem
value="delete"
prepend-icon="tabler-trash"
@click="deleteProduct(item.id)"
>
Delete
</VListItem>
<VListItem
value="duplicate"
prepend-icon="tabler-copy"
>
Duplicate
</VListItem>
</VList>
</VMenu>
</IconBtn>
</template>
<!-- pagination -->
<template #bottom>
<TablePagination
v-model:page="page"
:items-per-page="itemsPerPage"
:total-items="totalProduct"
/>
</template>
</VDataTableServer>
</VCard>
</div>
</template>

View File

@@ -0,0 +1,393 @@
<script setup>
import paperImg from '@images/svg/paper-send.svg?raw'
import rocketImg from '@images/svg/rocket.svg?raw'
import userInfoImg from '@images/svg/user-info.svg?raw'
const rocketIcon = h('div', {
innerHTML: rocketImg,
style: 'font-size: 2.625rem;color: rgb(var(--v-theme-primary))',
})
const userInfoIcon = h('div', {
innerHTML: paperImg,
style: 'font-size: 2.625rem;color: rgb(var(--v-theme-primary))',
})
const paperIcon = h('div', {
innerHTML: userInfoImg,
style: 'font-size: 2.625rem;color: rgb(var(--v-theme-primary))',
})
const widgetData = [
{
title: 'Total Earning',
value: '$24,983',
icon: 'tabler-currency-dollar',
color: 'primary',
},
{
title: 'Unpaid Earning',
value: '$8,647',
icon: 'tabler-gift',
color: 'success',
},
{
title: 'Signup',
value: '2,367',
icon: 'tabler-users',
color: 'error',
},
{
title: 'Conversion Rate',
value: '4.5%',
icon: 'tabler-infinity',
color: 'info',
},
]
const stepsData = [
{
icon: rocketIcon,
desc: 'Create & validate your referral link and get',
value: '$50',
},
{
icon: paperIcon,
desc: 'For every new signup you get',
value: '10%',
},
{
icon: userInfoIcon,
desc: 'Get other friends to generate link and get',
value: '$100',
},
]
// Data table options
const itemsPerPage = ref(10)
const page = ref(1)
const sortBy = ref()
const orderBy = ref()
// Data Table Headers
const headers = [
{
title: 'Users',
key: 'users',
},
{
title: 'Referred ID',
key: 'referred-id',
},
{
title: 'Status',
key: 'status',
},
{
title: 'Value',
key: 'value',
},
{
title: 'Earnings',
key: 'earning',
},
]
const updateOptions = options => {
sortBy.value = options.sortBy[0]?.key
orderBy.value = options.sortBy[0]?.order
}
const { data: referralData } = await useApi(createUrl('/apps/ecommerce/referrals', {
query: {
page,
itemsPerPage,
sortBy,
orderBy,
},
}))
const resolveAvatarbg = status => {
if (status === 'Rejected')
return { color: 'error' }
if (status === 'Unpaid')
return { color: 'warning' }
if (status === 'Paid')
return { color: 'success' }
}
const referrals = computed(() => referralData.value.referrals)
const totalReferrals = computed(() => referralData.value.total)
const resolveStatus = status => {
if (status === 'Rejected')
return {
text: 'Rejected',
color: 'error',
}
if (status === 'Unpaid')
return {
text: 'Unpaid',
color: 'warning',
}
if (status === 'Paid')
return {
text: 'Paid',
color: 'success',
}
}
</script>
<template>
<div>
<!-- 👉 Header -->
<VRow class="match-height">
<!-- 👉 Widgets -->
<VCol
v-for="(data, index) in widgetData"
:key="index"
cols="12"
md="3"
sm="6"
>
<VCard>
<VCardText>
<div class="d-flex justify-space-between align-center">
<div class="d-flex flex-column">
<h5 class="text-h5 mb-1">
{{ data.value }}
</h5>
<div class="text-body-2">
{{ data.title }}
</div>
</div>
<VAvatar
size="40"
variant="tonal"
:color="data.color"
>
<VIcon :icon="data.icon" />
</VAvatar>
</div>
</VCardText>
</VCard>
</VCol>
<!-- 👉 Icon Steps -->
<VCol
cols="12"
md="6"
>
<VCard>
<VCardItem>
<VCardTitle class="mb-1">
How to use
</VCardTitle>
<VCardSubtitle>
Integrate your referral code in 3 easy steps.
</VCardSubtitle>
</VCardItem>
<VCardText>
<div class="d-flex flex-column flex-sm-row gap-6 justify-sm-space-between align-center">
<div
v-for="(step, index) in stepsData"
:key="index"
class="d-flex flex-column align-center gap-y-2"
style="max-inline-size: 185px;"
>
<div class="icon-container">
<VIcon
:icon="step.icon"
color="primary"
size="36"
/>
</div>
<div class="text-body-1 text-wrap text-center">
{{ step.desc }}
</div>
<h6 class="text-primary text-h6">
{{ step.value }}
</h6>
</div>
</div>
</VCardText>
</VCard>
</VCol>
<!-- 👉 Invite -->
<VCol
cols="12"
md="6"
>
<VCard>
<VCardText>
<div class="mb-5">
<h5 class="text-h5 mb-5">
Invite your friends
</h5>
<div class="d-flex align-center flex-wrap gap-4 flex-wrap">
<AppTextField
label="Enter friends email address and invite them"
placeholder="Email Address"
/>
<VBtn class="align-self-end">
Submit
</VBtn>
</div>
</div>
<div>
<h5 class="text-h5 mb-5">
Share the referral link
</h5>
<div class="d-flex gap-4 align-center flex-wrap">
<AppTextField
label="Share referral link in social media"
placeholder="pixinvent.com/?ref=6478"
/>
<div class="d-flex align-self-end gap-x-2">
<VBtn
icon
class="rounded"
color="#3B5998"
>
<VIcon
color="white"
icon="tabler-brand-facebook"
/>
</VBtn>
<VBtn
icon
class="rounded"
color="#55ACEE"
>
<VIcon
color="white"
icon="tabler-brand-twitter"
/>
</VBtn>
</div>
</div>
</div>
</VCardText>
</VCard>
</VCol>
<!-- 👉 Referral Table -->
<VCol cols="12">
<VCard>
<VCardText>
<div class="d-flex justify-space-between align-center flex-wrap gap-4">
<h5 class="text-h5">
Referred Users
</h5>
<div class="d-flex flex-wrap gap-4">
<div class="d-flex gap-4 align-center flex-wrap">
<AppSelect
v-model="itemsPerPage"
:items="[10, 25, 50, 100]"
style="inline-size: 100px;"
/>
<VBtn
prepend-icon="tabler-upload"
color="default"
variant="tonal"
>
Export
</VBtn>
</div>
</div>
</div>
</VCardText>
<VDivider />
<VDataTableServer
v-model:items-per-page="itemsPerPage"
v-model:page="page"
:items="referrals"
:headers="headers"
:items-length="totalReferrals"
show-select
@update:options="updateOptions"
>
<template #item.users="{ item }">
<div class="d-flex align-center gap-x-4">
<VAvatar
size="34"
:variant="!item.avatar ? 'tonal' : undefined"
:color="!item.avatar ? resolveAvatarbg(item.status)?.color : undefined"
>
<VImg
v-if="item.avatar"
:src="item.avatar"
/>
<span v-else>{{ avatarText(item.user) }}</span>
</VAvatar>
<div>
<div class="font-weight-medium text-high-emphasis">
<RouterLink
:to="{ name: 'apps-ecommerce-customer-details-id', params: { id: 478426 } }"
class="text-link"
>
{{ item.user }}
</RouterLink>
</div>
<div class="text-body-2">
{{ item.email }}
</div>
</div>
</div>
</template>
<template #item.referred-id="{ item }">
{{ item.referredId }}
</template>
<template #item.status="{ item }">
<VChip
v-bind="resolveStatus(item.status)"
label
size="small"
/>
</template>
<template #item.earning="{ item }">
<div class="text-body-1 text-high-emphasis">
{{ item.earning }}
</div>
</template>
<!-- pagination -->
<template #bottom>
<TablePagination
v-model:page="page"
:items-per-page="itemsPerPage"
:total-items="totalReferrals"
/>
</template>
</VDataTableServer>
</VCard>
</VCol>
</VRow>
</div>
</template>
<style lang="scss" scoped>
.icon-container {
display: flex;
align-items: center;
justify-content: center;
border: 2px dashed rgb(var(--v-theme-primary));
border-radius: 50%;
block-size: 70px;
inline-size: 70px;
}
.icon {
color: #000;
font-size: 42px;
}
</style>

View File

@@ -0,0 +1,107 @@
<script setup>
import SettingsCheckout from '@/views/apps/ecommerce/settings/SettingsCheckout.vue'
import SettingsLocations from '@/views/apps/ecommerce/settings/SettingsLocations.vue'
import SettingsNotifications from '@/views/apps/ecommerce/settings/SettingsNotifications.vue'
import SettingsPayment from '@/views/apps/ecommerce/settings/SettingsPayment.vue'
import SettingsShippingAndDelivery from '@/views/apps/ecommerce/settings/SettingsShippingAndDelivery.vue'
import SettingsStoreDetails from '@/views/apps/ecommerce/settings/SettingsStoreDetails.vue'
const tabsData = [
{
icon: 'tabler-building-store',
title: 'Store Details',
},
{
icon: 'tabler-credit-card',
title: 'Payments',
},
{
icon: 'tabler-shopping-cart',
title: 'Checkout',
},
{
icon: 'tabler-discount',
title: 'Shipping & Delivery',
},
{
icon: 'tabler-map-pin',
title: 'Location',
},
{
icon: 'tabler-bell-ringing',
title: 'Notifications',
},
]
const activeTab = ref(null)
</script>
<template>
<VRow>
<VCol
cols="12"
md="4"
>
<h5 class="text-h5 mb-4">
Getting Started
</h5>
<VTabs
v-model="activeTab"
direction="vertical"
class="v-tabs-pill disable-tab-transition"
>
<VTab
v-for="(tabItem, index) in tabsData"
:key="index"
:prepend-icon="tabItem.icon"
>
{{ tabItem.title }}
</VTab>
</VTabs>
</VCol>
<VCol
cols="12"
md="8"
>
<VWindow
v-model="activeTab"
class="disable-tab-transition"
:touch="false"
>
<VWindowItem>
<SettingsStoreDetails />
</VWindowItem>
<VWindowItem>
<SettingsPayment />
</VWindowItem>
<VWindowItem>
<SettingsCheckout />
</VWindowItem>
<VWindowItem>
<SettingsShippingAndDelivery />
</VWindowItem>
<VWindowItem>
<SettingsLocations />
</VWindowItem>
<VWindowItem>
<SettingsNotifications />
</VWindowItem>
</VWindow>
</VCol>
</VRow>
</template>
<style lang="scss">
.my-class {
padding: 1.25rem;
border-radius: 0.375rem;
background-color: rgba(var(--v-theme-on-surface), var(--v-hover-opacity));
}
</style>