first commit
This commit is contained in:
107
resources/js/pages/apps/ecommerce/customer/details/[id].vue
Normal file
107
resources/js/pages/apps/ecommerce/customer/details/[id].vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<script setup>
|
||||
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 = [
|
||||
{
|
||||
icon: 'ri-group-line',
|
||||
title: 'Overview',
|
||||
},
|
||||
{
|
||||
icon: 'ri-lock-line',
|
||||
title: 'Security',
|
||||
},
|
||||
{
|
||||
icon: 'ri-map-pin-line',
|
||||
title: 'Address & Billing',
|
||||
},
|
||||
{
|
||||
icon: 'ri-notification-3-line',
|
||||
title: 'Notifications',
|
||||
},
|
||||
]
|
||||
|
||||
const { data, error } = await useApi(`/apps/ecommerce/customers/${ route.params.id }`)
|
||||
if (error.value)
|
||||
console.log(error.value)
|
||||
else if (data.value)
|
||||
customerData.value = data.value
|
||||
</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">
|
||||
Customer ID #{{ route.params.id }}
|
||||
</h4>
|
||||
<p class="text-body-1 mb-0">
|
||||
Aug 17, 2020, 5:48 (ET)
|
||||
</p>
|
||||
</div>
|
||||
<VBtn
|
||||
variant="outlined"
|
||||
color="error"
|
||||
>
|
||||
Delete Customer
|
||||
</VBtn>
|
||||
</div>
|
||||
<!-- 👉 Customer Profile -->
|
||||
<VRow v-if="customerData">
|
||||
<VCol
|
||||
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-6 6 disable-tab-transition"
|
||||
>
|
||||
<VTab
|
||||
v-for="tab in tabs"
|
||||
:key="tab.icon"
|
||||
>
|
||||
<VIcon
|
||||
start
|
||||
:icon="tab.icon"
|
||||
/>
|
||||
<span>{{ tab.title }}</span>
|
||||
</VTab>
|
||||
</VTabs>
|
||||
<VWindow
|
||||
v-model="userTab"
|
||||
class="mb-6 disable-tab-transition"
|
||||
:touch="false"
|
||||
>
|
||||
<VWindowItem>
|
||||
<CustomerTabOverview />
|
||||
</VWindowItem>
|
||||
<VWindowItem>
|
||||
<CustomerTabSecurity />
|
||||
</VWindowItem>
|
||||
<VWindowItem>
|
||||
<CustomerTabAddressAndBilling />
|
||||
</VWindowItem>
|
||||
<VWindowItem>
|
||||
<CustomerTabNotification />
|
||||
</VWindowItem>
|
||||
</VWindow>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
</template>
|
187
resources/js/pages/apps/ecommerce/customer/list/index.vue
Normal file
187
resources/js/pages/apps/ecommerce/customer/list/index.vue
Normal file
@@ -0,0 +1,187 @@
|
||||
<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 => {
|
||||
page.value = options.page
|
||||
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">
|
||||
<VTextField
|
||||
v-model="searchQuery"
|
||||
style="max-inline-size: 200px; min-inline-size: 200px;"
|
||||
density="compact"
|
||||
placeholder="Search .."
|
||||
/>
|
||||
<div class="d-flex flex-row gap-4 align-center flex-wrap">
|
||||
<VBtn
|
||||
prepend-icon="ri-upload-2-line"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
>
|
||||
Export
|
||||
</VBtn>
|
||||
<VBtn
|
||||
prepend-icon="ri-add-line"
|
||||
@click="isAddCustomerDrawerOpen = !isAddCustomerDrawerOpen"
|
||||
>
|
||||
Add Customer
|
||||
</VBtn>
|
||||
</div>
|
||||
</div>
|
||||
</VCardText>
|
||||
|
||||
<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"
|
||||
:image="item.avatar"
|
||||
/>
|
||||
<div class="d-flex flex-column">
|
||||
<RouterLink
|
||||
:to="{ name: 'apps-ecommerce-customer-details-id', params: { id: item.customerId } }"
|
||||
class="text-h6 font-weight-medium"
|
||||
>
|
||||
{{ item.customer }}
|
||||
</RouterLink>
|
||||
<span class="text-sm">{{ item.email }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #item.customerId="{ item }">
|
||||
<h6 class="text-h6 font-weight-regular">
|
||||
#{{ item.customerId }}
|
||||
</h6>
|
||||
</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>
|
||||
|
||||
<!-- Pagination -->
|
||||
<template #bottom>
|
||||
<VDivider />
|
||||
|
||||
<div class="d-flex justify-end flex-wrap gap-x-6 px-2 py-1">
|
||||
<div class="d-flex align-center gap-x-2 text-medium-emphasis text-base">
|
||||
Rows Per Page:
|
||||
<VSelect
|
||||
v-model="itemsPerPage"
|
||||
class="per-page-select"
|
||||
variant="plain"
|
||||
:items="[10, 20, 25, 50, 100]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="d-flex align-center text-base text-high-emphasis me-2 mb-0">
|
||||
{{ paginationMeta({ page, itemsPerPage }, totalCustomers) }}
|
||||
</p>
|
||||
|
||||
<div class="d-flex gap-x-2 align-center me-2">
|
||||
<VBtn
|
||||
class="flip-in-rtl"
|
||||
icon="ri-arrow-left-s-line"
|
||||
variant="text"
|
||||
density="comfortable"
|
||||
color="high-emphasis"
|
||||
:disabled="page <= 1"
|
||||
@click="page <= 1 ? page = 1 : page--"
|
||||
/>
|
||||
|
||||
<VBtn
|
||||
class="flip-in-rtl"
|
||||
icon="ri-arrow-right-s-line"
|
||||
density="comfortable"
|
||||
variant="text"
|
||||
color="high-emphasis"
|
||||
:disabled="page >= Math.ceil(totalCustomers / itemsPerPage)"
|
||||
@click="page >= Math.ceil(totalCustomers / itemsPerPage) ? page = Math.ceil(totalCustomers / itemsPerPage) : page++ "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</VDataTableServer>
|
||||
</VCard>
|
||||
<ECommerceAddCustomerDrawer v-model:is-drawer-open="isAddCustomerDrawerOpen" />
|
||||
</div>
|
||||
</template>
|
587
resources/js/pages/apps/ecommerce/manage-review.vue
Normal file
587
resources/js/pages/apps/ecommerce/manage-review.vue
Normal file
@@ -0,0 +1,587 @@
|
||||
<script setup>
|
||||
const selectedStatus = ref('All')
|
||||
const searchQuery = ref('')
|
||||
const itemsPerPage = ref(10)
|
||||
const page = ref(1)
|
||||
const sortBy = ref()
|
||||
const orderBy = 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 => {
|
||||
page.value = options.page
|
||||
sortBy.value = options.sortBy[0]?.key
|
||||
orderBy.value = options.sortBy[0]?.order
|
||||
}
|
||||
|
||||
const deleteReview = async id => {
|
||||
await $api(`/apps/ecommerce/reviews/${ id }`, { method: 'DELETE' })
|
||||
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 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: [
|
||||
'rgba(var(--v-theme-success), var(--v-activated-opacity))',
|
||||
'rgba(var(--v-theme-success), var(--v-activated-opacity))',
|
||||
'rgba(var(--v-theme-success), var(--v-activated-opacity))',
|
||||
'rgba(var(--v-theme-success), var(--v-activated-opacity))',
|
||||
'rgba(var(--v-theme-success), 1)',
|
||||
'rgba(var(--v-theme-success), var(--v-activated-opacity))',
|
||||
'rgba(var(--v-theme-success), var(--v-activated-opacity))',
|
||||
],
|
||||
plotOptions: {
|
||||
bar: {
|
||||
barHeight: '75%',
|
||||
columnWidth: '35%',
|
||||
borderRadius: 5,
|
||||
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: 6,
|
||||
columnWidth: '40%',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
breakpoint: 1400,
|
||||
options: {
|
||||
plotOptions: {
|
||||
bar: {
|
||||
borderRadius: 6,
|
||||
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: 576,
|
||||
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: 3,
|
||||
columnWidth: '30%',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Total Review Card -->
|
||||
<VCard>
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div :class="$vuetify.display.smAndUp ? 'border-e' : 'border-b'">
|
||||
<div class="d-flex align-center gap-x-2">
|
||||
<h4 class="text-h3 text-primary">
|
||||
4.89
|
||||
</h4>
|
||||
<VIcon
|
||||
icon="ri-star-smile-line"
|
||||
color="primary"
|
||||
size="32"
|
||||
/>
|
||||
</div>
|
||||
<h6 class="my-2 text-h6">
|
||||
Total 187 reviews
|
||||
</h6>
|
||||
<div class="mb-2">
|
||||
All reviews are from genuine customers
|
||||
</div>
|
||||
<VChip
|
||||
color="primary"
|
||||
size="small"
|
||||
:class="$vuetify.display.smAndUp ? '' : 'mb-4'"
|
||||
>
|
||||
+5 This week
|
||||
</VChip>
|
||||
</div>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in reviewCardData"
|
||||
:key="index"
|
||||
class="d-flex align-center gap-4 mb-3"
|
||||
>
|
||||
<div class="text-sm text-no-wrap">
|
||||
{{ item.rating }} Star
|
||||
</div>
|
||||
<div class="w-100">
|
||||
<VProgressLinear
|
||||
color="primary"
|
||||
height="8"
|
||||
:model-value="(item.value / 185) * 100"
|
||||
rounded
|
||||
/>
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
{{ item.value }}
|
||||
</div>
|
||||
</div>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VCard>
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="5"
|
||||
>
|
||||
<div>
|
||||
<h5 class="text-h5 mb-2">
|
||||
Reviews statistics
|
||||
</h5>
|
||||
<div class="mb-9">
|
||||
<span class="me-2">12 New Reviews</span>
|
||||
<VChip
|
||||
color="success"
|
||||
size="small"
|
||||
>
|
||||
+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>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="7"
|
||||
>
|
||||
<div class="d-flex justify-start justify-sm-end">
|
||||
<VueApexCharts
|
||||
id="shipment-statistics"
|
||||
type="bar"
|
||||
height="150"
|
||||
:options="reviewStatChartConfig"
|
||||
:series="reviewStatChartSeries"
|
||||
/>
|
||||
</div>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardText>
|
||||
<div class="d-flex justify-space-between flex-wrap gap-y-4">
|
||||
<VTextField
|
||||
v-model="searchQuery"
|
||||
style="max-inline-size: 250px; min-inline-size: 200px;"
|
||||
placeholder="Search"
|
||||
density="compact"
|
||||
/>
|
||||
<div class="d-flex flex-row gap-4 align-center flex-wrap">
|
||||
<VSelect
|
||||
v-model="selectedStatus"
|
||||
style="min-inline-size: 6.25rem;"
|
||||
density="compact"
|
||||
:items="[
|
||||
{ title: 'All', value: 'All' },
|
||||
{ title: 'Published', value: 'Published' },
|
||||
{ title: 'Pending', value: 'Pending' },
|
||||
]"
|
||||
/>
|
||||
<VBtn prepend-icon="ri-upload-2-line">
|
||||
Export
|
||||
</VBtn>
|
||||
</div>
|
||||
</div>
|
||||
</VCardText>
|
||||
|
||||
<VDataTableServer
|
||||
v-model:items-per-page="itemsPerPage"
|
||||
v-model:page="page"
|
||||
:headers="headers"
|
||||
:items="reviews"
|
||||
show-select
|
||||
:items-length="totalReviews"
|
||||
item-value="id"
|
||||
class="text-no-wrap rounded-0"
|
||||
@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>
|
||||
<span class="text-sm text-wrap clamp-text">{{ item.companyName }}</span>
|
||||
</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"
|
||||
>
|
||||
{{ item.reviewer }}
|
||||
</RouterLink>
|
||||
<span class="text-body-2">{{ item.email }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item.review="{ item }">
|
||||
<div class="py-4">
|
||||
<VRating
|
||||
:size="24"
|
||||
readonly
|
||||
:model-value="item.review"
|
||||
/>
|
||||
<h6 class="text-h6 mb-1">
|
||||
{{ item.head }}
|
||||
</h6>
|
||||
<p class="text-sm text-medium-emphasis text-wrap mb-0">
|
||||
{{ item.para }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item.date="{ item }">
|
||||
<span class="text-body-1">{{ new Date(item.date).toDateString() }}</span>
|
||||
</template>
|
||||
|
||||
<template #item.status="{ item }">
|
||||
<VChip
|
||||
:color="item.status === 'Published' ? 'success' : 'warning'"
|
||||
size="small"
|
||||
>
|
||||
{{ item.status }}
|
||||
</VChip>
|
||||
</template>
|
||||
|
||||
<template #item.actions="{ item }">
|
||||
<IconBtn size="small">
|
||||
<VIcon icon="ri-more-2-line" />
|
||||
<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>
|
||||
<VDivider />
|
||||
|
||||
<div class="d-flex justify-end flex-wrap gap-x-6 px-2 py-1">
|
||||
<div class="d-flex align-center gap-x-2 text-medium-emphasis text-base">
|
||||
Rows Per Page:
|
||||
<VSelect
|
||||
v-model="itemsPerPage"
|
||||
class="per-page-select"
|
||||
variant="plain"
|
||||
:items="[10, 20, 25, 50, 100]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="d-flex align-center text-base text-high-emphasis me-2 mb-0">
|
||||
{{ paginationMeta({ page, itemsPerPage }, totalReviews) }}
|
||||
</p>
|
||||
|
||||
<div class="d-flex gap-x-2 align-center me-2">
|
||||
<VBtn
|
||||
class="flip-in-rtl"
|
||||
icon="ri-arrow-left-s-line"
|
||||
variant="text"
|
||||
density="comfortable"
|
||||
color="high-emphasis"
|
||||
:disabled="page <= 1"
|
||||
@click="page <= 1 ? page = 1 : page--"
|
||||
/>
|
||||
|
||||
<VBtn
|
||||
class="flip-in-rtl"
|
||||
icon="ri-arrow-right-s-line"
|
||||
density="comfortable"
|
||||
variant="text"
|
||||
color="high-emphasis"
|
||||
:disabled="page >= Math.ceil(totalReviews / itemsPerPage)"
|
||||
@click="page >= Math.ceil(totalReviews / itemsPerPage) ? page = Math.ceil(totalReviews / itemsPerPage) : page++ "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</VDataTableServer>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/libs/apex-chart.scss";
|
||||
</style>
|
413
resources/js/pages/apps/ecommerce/order/details/[id].vue
Normal file
413
resources/js/pages/apps/ecommerce/order/details/[id].vue
Normal file
@@ -0,0 +1,413 @@
|
||||
<script setup>
|
||||
import avatar1 from '@images/avatars/avatar-1.png'
|
||||
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 route = useRoute('apps-ecommerce-order-details-id')
|
||||
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',
|
||||
sortable: false,
|
||||
},
|
||||
]
|
||||
|
||||
const orderData = [
|
||||
{
|
||||
productName: 'OnePlus 7 Pro',
|
||||
productImage: product21,
|
||||
brand: 'OnePlus',
|
||||
price: 799,
|
||||
quantity: 1,
|
||||
},
|
||||
{
|
||||
productName: 'Magic Mouse',
|
||||
productImage: product22,
|
||||
brand: 'Apple',
|
||||
price: 89,
|
||||
quantity: 1,
|
||||
},
|
||||
{
|
||||
productName: 'Wooden Chair',
|
||||
productImage: product23,
|
||||
brand: 'insofer',
|
||||
price: 289,
|
||||
quantity: 2,
|
||||
},
|
||||
{
|
||||
productName: 'Air Jorden',
|
||||
productImage: product24,
|
||||
brand: 'Nike',
|
||||
price: 299,
|
||||
quantity: 2,
|
||||
},
|
||||
]
|
||||
</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
|
||||
variant="tonal"
|
||||
color="success"
|
||||
size="small"
|
||||
>
|
||||
Paid
|
||||
</VChip>
|
||||
<VChip
|
||||
variant="tonal"
|
||||
color="info"
|
||||
size="small"
|
||||
>
|
||||
Ready to Pickup
|
||||
</VChip>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-body-1">
|
||||
Aug 17, 2020, 5:48 (ET)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<VBtn
|
||||
variant="outlined"
|
||||
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>
|
||||
<span class="text-primary cursor-pointer">Edit</span>
|
||||
</template>
|
||||
</VCardItem>
|
||||
|
||||
<VDataTable
|
||||
:headers="headers"
|
||||
:items="orderData"
|
||||
item-value="productName"
|
||||
show-select
|
||||
class="text-no-wrap"
|
||||
>
|
||||
<template #item.productName="{ item }">
|
||||
<div class="d-flex gap-x-3">
|
||||
<VAvatar
|
||||
size="34"
|
||||
variant="tonal"
|
||||
:image="item.productImage"
|
||||
rounded
|
||||
/>
|
||||
|
||||
<div class="d-flex flex-column align-center">
|
||||
<h6 class="text-h6">
|
||||
{{ item.productName }}
|
||||
</h6>
|
||||
|
||||
<span class="text-sm text-start align-self-start">
|
||||
{{ item.brand }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item.price="{ item }">
|
||||
<span>${{ item.price }}</span>
|
||||
</template>
|
||||
|
||||
<template #item.total="{ item }">
|
||||
<span>
|
||||
${{ item.price * item.quantity }}
|
||||
</span>
|
||||
</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 fee: </td>
|
||||
<td class="font-weight-medium">
|
||||
$2
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tax: </td>
|
||||
<td class="font-weight-medium">
|
||||
$28
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="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"
|
||||
align="start"
|
||||
side="end"
|
||||
line-inset="10"
|
||||
line-color="primary"
|
||||
density="compact"
|
||||
class="v-timeline-density-compact"
|
||||
>
|
||||
<VTimelineItem
|
||||
dot-color="primary"
|
||||
size="x-small"
|
||||
>
|
||||
<div class="d-flex justify-space-between align-center mb-3">
|
||||
<span class="app-timeline-title">Order was placed (Order ID: #32543)</span>
|
||||
<span class="app-timeline-meta">Tuesday 11:29 AM</span>
|
||||
</div>
|
||||
<p class="app-timeline-text mb-0">
|
||||
Your order has been placed successfully
|
||||
</p>
|
||||
</VTimelineItem>
|
||||
|
||||
<VTimelineItem
|
||||
dot-color="primary"
|
||||
size="x-small"
|
||||
>
|
||||
<div class="d-flex justify-space-between align-center mb-3">
|
||||
<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">
|
||||
Pick-up scheduled with courier
|
||||
</p>
|
||||
</VTimelineItem>
|
||||
|
||||
<VTimelineItem
|
||||
dot-color="primary"
|
||||
size="x-small"
|
||||
>
|
||||
<div class="d-flex justify-space-between align-center mb-3">
|
||||
<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">
|
||||
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 mb-3">
|
||||
<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">
|
||||
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 mb-3">
|
||||
<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">
|
||||
Package has left an Amazon facility , NY
|
||||
</p>
|
||||
</VTimelineItem>
|
||||
|
||||
<VTimelineItem
|
||||
dot-color="primary"
|
||||
size="x-small"
|
||||
>
|
||||
<div class="d-flex justify-space-between align-center mb-3">
|
||||
<span class="app-timeline-title">Delivery</span>
|
||||
</div>
|
||||
<p class="app-timeline-text mb-0">
|
||||
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
|
||||
:image="avatar1"
|
||||
class="me-3"
|
||||
/>
|
||||
<div>
|
||||
<div class="text-body-1 text-high-emphasis font-weight-medium">
|
||||
Shamus Tuttle
|
||||
</div>
|
||||
<span>Customer ID: #47389</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-center">
|
||||
<VAvatar
|
||||
variant="tonal"
|
||||
color="success"
|
||||
class="me-3"
|
||||
>
|
||||
<VIcon icon="ri-shopping-cart-line" />
|
||||
</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 gap-1 text-body-2">
|
||||
<h6 class="text-h6">
|
||||
Contact Info
|
||||
</h6>
|
||||
<span
|
||||
class="text-base text-primary font-weight-medium cursor-pointer"
|
||||
@click="isUserInfoEditDialogVisible = !isUserInfoEditDialogVisible"
|
||||
>
|
||||
Edit
|
||||
</span>
|
||||
</div>
|
||||
<span>Email: Sheldon88@yahoo.com</span>
|
||||
<span>Mobile: +1 (609) 972-22-22</span>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- 👉 Shipping Address -->
|
||||
<VCard class="mb-6">
|
||||
<VCardText>
|
||||
<div class="d-flex align-center justify-space-between gap-1 mb-6">
|
||||
<div class="text-body-1 text-high-emphasis font-weight-medium">
|
||||
Shipping Address
|
||||
</div>
|
||||
<span
|
||||
class="text-base text-primary font-weight-medium cursor-pointer"
|
||||
@click="isEditAddressDialogVisible = !isEditAddressDialogVisible"
|
||||
>Edit</span>
|
||||
</div>
|
||||
<div>
|
||||
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 gap-1 mb-3">
|
||||
<div class="text-body-1 text-high-emphasis font-weight-medium">
|
||||
Billing Address
|
||||
</div>
|
||||
<span
|
||||
class="text-base text-primary font-weight-medium cursor-pointer"
|
||||
@click="isEditAddressDialogVisible = !isEditAddressDialogVisible"
|
||||
>Edit</span>
|
||||
</div>
|
||||
<div>
|
||||
45 Rocker Terrace <br> Latheronwheel <br> KW5 8NW, London <br> UK
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<h6 class="text-h6 mb-1">
|
||||
Mastercard
|
||||
</h6>
|
||||
<div class="text-base">
|
||||
Card Number: ******4291
|
||||
</div>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<ConfirmDialog
|
||||
v-model:isDialogVisible="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:isDialogVisible="isUserInfoEditDialogVisible" />
|
||||
|
||||
<AddEditAddressDialog v-model:isDialogVisible="isEditAddressDialogVisible" />
|
||||
</div>
|
||||
</template>
|
395
resources/js/pages/apps/ecommerce/order/list/index.vue
Normal file
395
resources/js/pages/apps/ecommerce/order/list/index.vue
Normal file
@@ -0,0 +1,395 @@
|
||||
<script setup>
|
||||
import mastercard from '@images/logos/mastercard.png'
|
||||
import paypal from '@images/logos/paypal.png'
|
||||
|
||||
const widgetData = ref([
|
||||
{
|
||||
title: 'Pending Payment',
|
||||
value: 56,
|
||||
icon: 'ri-calendar-2-line',
|
||||
},
|
||||
{
|
||||
title: 'Completed',
|
||||
value: 12689,
|
||||
icon: 'ri-check-double-line',
|
||||
},
|
||||
{
|
||||
title: 'Refunded',
|
||||
value: 124,
|
||||
icon: 'ri-wallet-3-line',
|
||||
},
|
||||
{
|
||||
title: 'Failed',
|
||||
value: 32,
|
||||
icon: 'ri-error-warning-line',
|
||||
},
|
||||
])
|
||||
|
||||
const searchQuery = ref('')
|
||||
|
||||
// Data table options
|
||||
const itemsPerPage = ref(10)
|
||||
const page = ref(1)
|
||||
const sortBy = ref()
|
||||
const orderBy = 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: 'Actions',
|
||||
key: 'actions',
|
||||
sortable: false,
|
||||
},
|
||||
]
|
||||
|
||||
const updateOptions = options => {
|
||||
page.value = options.page
|
||||
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' })
|
||||
fetchOrders()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<VCard class="mb-6">
|
||||
<VCardText class="px-2">
|
||||
<VRow>
|
||||
<template
|
||||
v-for="(data, index) in widgetData"
|
||||
:key="index"
|
||||
>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="3"
|
||||
class="px-6"
|
||||
>
|
||||
<div
|
||||
class="d-flex justify-space-between"
|
||||
:class="$vuetify.display.xs ? 'product-widget' : $vuetify.display.sm ? index < 2 ? 'product-widget' : '' : ''"
|
||||
>
|
||||
<div class="d-flex flex-column gap-y-1">
|
||||
<h4 class="text-h4">
|
||||
{{ data.value }}
|
||||
</h4>
|
||||
<span class="text-base text-capitalize">
|
||||
{{ data.title }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<VAvatar
|
||||
variant="tonal"
|
||||
rounded
|
||||
size="42"
|
||||
>
|
||||
<VIcon
|
||||
:icon="data.icon"
|
||||
size="26"
|
||||
/>
|
||||
</VAvatar>
|
||||
</div>
|
||||
</VCol>
|
||||
<VDivider
|
||||
v-if="$vuetify.display.mdAndUp ? index !== widgetData.length - 1 : $vuetify.display.smAndUp ? index % 2 === 0 : false"
|
||||
vertical
|
||||
inset
|
||||
length="100"
|
||||
/>
|
||||
</template>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<VCard>
|
||||
<VCardText>
|
||||
<div class="d-flex justify-sm-space-between align-center justify-start flex-wrap gap-4">
|
||||
<VTextField
|
||||
v-model="searchQuery"
|
||||
placeholder="Search Order"
|
||||
density="compact"
|
||||
style=" max-inline-size: 200px; min-inline-size: 200px;"
|
||||
/>
|
||||
|
||||
<VBtn
|
||||
prepend-icon="ri-upload-2-line"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
>
|
||||
Export
|
||||
</VBtn>
|
||||
</div>
|
||||
</VCardText>
|
||||
|
||||
<VDataTableServer
|
||||
v-model:items-per-page="itemsPerPage"
|
||||
v-model:page="page"
|
||||
:headers="headers"
|
||||
:items="orders"
|
||||
item-value="order"
|
||||
: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">
|
||||
<VAvatar
|
||||
size="34"
|
||||
:variant="!item.avatar.length ? 'tonal' : undefined"
|
||||
:rounded="1"
|
||||
class="me-4"
|
||||
>
|
||||
<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">
|
||||
<RouterLink :to="{ name: 'pages-user-profile-tab', params: { tab: 'profile' } }">
|
||||
<div class="text-high-emphasis font-weight-medium">
|
||||
{{ item.customer }}
|
||||
</div>
|
||||
</RouterLink>
|
||||
<span class="text-sm">{{ item.email }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Payments -->
|
||||
<template #item.payment="{ item }">
|
||||
<div
|
||||
:class="`text-${resolvePaymentStatus(item.payment)?.color}`"
|
||||
class="d-flex align-center font-weight-medium"
|
||||
>
|
||||
<VIcon
|
||||
size="10"
|
||||
icon="ri-circle-fill"
|
||||
class="me-2"
|
||||
/>
|
||||
<span>{{ resolvePaymentStatus(item.payment)?.text }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Status -->
|
||||
<template #item.status="{ item }">
|
||||
<VChip
|
||||
v-bind="resolveStatus(item.status)"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Method -->
|
||||
<template #item.method="{ item }">
|
||||
<div class="d-flex align-start gap-x-2">
|
||||
<img :src="item.method === 'mastercard' ? mastercard : paypal">
|
||||
<div>
|
||||
<VIcon
|
||||
icon="ri-more-line"
|
||||
class="me-2"
|
||||
/>
|
||||
<span v-if="item.method === 'mastercard'">
|
||||
{{ item.methodNumber }}
|
||||
</span>
|
||||
<span v-else>
|
||||
@gmail.com
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Actions -->
|
||||
<template #item.actions="{ item }">
|
||||
<IconBtn size="small">
|
||||
<VIcon icon="ri-more-2-line" />
|
||||
<VMenu activator="parent">
|
||||
<VList>
|
||||
<VListItem value="view">
|
||||
<RouterLink
|
||||
:to="{ name: 'apps-ecommerce-order-details-id', params: { id: item.order } }"
|
||||
class="text-high-emphasis"
|
||||
>
|
||||
View
|
||||
</RouterLink>
|
||||
</VListItem>
|
||||
<VListItem
|
||||
value="delete"
|
||||
@click="deleteOrder(item.id)"
|
||||
>
|
||||
Delete
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</IconBtn>
|
||||
</template>
|
||||
|
||||
<!-- Pagination -->
|
||||
<template #bottom>
|
||||
<VDivider />
|
||||
|
||||
<div class="d-flex justify-end flex-wrap gap-x-6 px-2 py-1">
|
||||
<div class="d-flex align-center gap-x-2 text-medium-emphasis text-base">
|
||||
Rows Per Page:
|
||||
<VSelect
|
||||
v-model="itemsPerPage"
|
||||
class="per-page-select"
|
||||
variant="plain"
|
||||
:items="[10, 20, 25, 50, 100]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="d-flex align-center text-base text-high-emphasis me-2 mb-0">
|
||||
{{ paginationMeta({ page, itemsPerPage }, totalOrder) }}
|
||||
</p>
|
||||
|
||||
<div class="d-flex gap-x-2 align-center me-2">
|
||||
<VBtn
|
||||
class="flip-in-rtl"
|
||||
icon="ri-arrow-left-s-line"
|
||||
variant="text"
|
||||
density="comfortable"
|
||||
color="high-emphasis"
|
||||
:disabled="page <= 1"
|
||||
@click="page <= 1 ? page = 1 : page--"
|
||||
/>
|
||||
|
||||
<VBtn
|
||||
class="flip-in-rtl"
|
||||
icon="ri-arrow-right-s-line"
|
||||
density="comfortable"
|
||||
variant="text"
|
||||
color="high-emphasis"
|
||||
:disabled="page >= Math.ceil(totalOrder / itemsPerPage)"
|
||||
@click="page >= Math.ceil(totalOrder / itemsPerPage) ? page = Math.ceil(totalOrder / itemsPerPage) : page++ "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</VDataTableServer>
|
||||
</VCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#customer-link{
|
||||
&:hover{
|
||||
color: '#000' !important
|
||||
}
|
||||
}
|
||||
|
||||
.product-widget{
|
||||
border-block-end: 1px solid rgba(var(--v-theme-on-surface), var(--v-border-opacity));
|
||||
padding-block-end: 1rem;
|
||||
}
|
||||
</style>
|
688
resources/js/pages/apps/ecommerce/product/add/index.vue
Normal file
688
resources/js/pages/apps/ecommerce/product/add/index.vue
Normal file
@@ -0,0 +1,688 @@
|
||||
<script setup>
|
||||
import {
|
||||
useDropZone,
|
||||
useFileDialog,
|
||||
useObjectUrl,
|
||||
} from '@vueuse/core'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const optionCounter = ref(1)
|
||||
const dropZoneRef = ref()
|
||||
const fileData = ref([])
|
||||
const { open, onChange } = useFileDialog({ accept: 'image/*' })
|
||||
function onDrop(DroppedFiles) {
|
||||
DroppedFiles?.forEach(file => {
|
||||
if (file.type.slice(0, 6) !== 'image/') {
|
||||
alert('Only image files are allowed')
|
||||
|
||||
return
|
||||
}
|
||||
fileData.value.push({
|
||||
file,
|
||||
url: useObjectUrl(file).value ?? '',
|
||||
})
|
||||
})
|
||||
}
|
||||
onChange(selectedFiles => {
|
||||
if (!selectedFiles)
|
||||
return
|
||||
for (const file of selectedFiles) {
|
||||
fileData.value.push({
|
||||
file,
|
||||
url: useObjectUrl(file).value ?? '',
|
||||
})
|
||||
}
|
||||
})
|
||||
useDropZone(dropZoneRef, onDrop)
|
||||
|
||||
const content = ref(`<p>
|
||||
This is a radically reduced version of tiptap. It has support for a document, with paragraphs and text. That's it. It's probably too much for real minimalists though.
|
||||
</p>
|
||||
<p>
|
||||
The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.
|
||||
</p>`)
|
||||
|
||||
const activeTab = ref('Restock')
|
||||
|
||||
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: 'ri-add-line',
|
||||
title: 'Restock',
|
||||
value: 'Restock',
|
||||
},
|
||||
{
|
||||
icon: 'ri-flight-takeoff-line',
|
||||
title: 'Shipping',
|
||||
value: 'Shipping',
|
||||
},
|
||||
{
|
||||
icon: 'ri-map-pin-line',
|
||||
title: 'Global Delivery',
|
||||
value: 'Global Delivery',
|
||||
},
|
||||
{
|
||||
icon: 'ri-attachment-2',
|
||||
title: 'Attributes',
|
||||
value: 'Attributes',
|
||||
},
|
||||
{
|
||||
icon: 'ri-lock-unlock-line',
|
||||
title: 'Advanced',
|
||||
value: 'Advanced',
|
||||
},
|
||||
]
|
||||
|
||||
const inStock = ref(true)
|
||||
const isTaxable = ref(true)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="d-flex flex-wrap justify-space-between gap-4 mb-6">
|
||||
<div class="d-flex flex-column justify-center">
|
||||
<h4 class="text-h4">
|
||||
Add a new product
|
||||
</h4>
|
||||
<span class="text-medium-emphasis">Orders placed across your store</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-4 align-center flex-wrap">
|
||||
<VBtn
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
>
|
||||
Discard
|
||||
</VBtn>
|
||||
<VBtn
|
||||
variant="outlined"
|
||||
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">
|
||||
<VTextField
|
||||
label="Product Name"
|
||||
placeholder="iPhone 14"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
label="SKU"
|
||||
placeholder="FXSK123U"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
label="Barcode"
|
||||
placeholder="0123-4567"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol>
|
||||
<VLabel class="mb-1">
|
||||
Description (Optional)
|
||||
</VLabel>
|
||||
<TiptapEditor
|
||||
v-model="content"
|
||||
class="border rounded"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- 👉 Product Image -->
|
||||
<VCard class="mb-6">
|
||||
<VCardItem>
|
||||
<template #title>
|
||||
Product Image
|
||||
</template>
|
||||
<template #append>
|
||||
<div class="text-primary font-weight-medium cursor-pointer">
|
||||
Add Media from URL
|
||||
</div>
|
||||
</template>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<div class="flex">
|
||||
<div class="w-full h-auto relative">
|
||||
<div
|
||||
ref="dropZoneRef"
|
||||
class="cursor-pointer"
|
||||
@click="() => open()"
|
||||
>
|
||||
<div
|
||||
v-if="fileData.length === 0"
|
||||
class="d-flex flex-column justify-center align-center gap-y-2 pa-12 border-dashed drop-zone"
|
||||
>
|
||||
<VAvatar
|
||||
variant="tonal"
|
||||
color="secondary"
|
||||
rounded
|
||||
>
|
||||
<VIcon icon="ri-upload-2-line" />
|
||||
</VAvatar>
|
||||
<h4 class="text-h4">
|
||||
Drag and Drop Your Image Here.
|
||||
</h4>
|
||||
<span class="text-disabled">or</span>
|
||||
|
||||
<VBtn variant="outlined">
|
||||
Browse Images
|
||||
</VBtn>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="d-flex justify-center align-center gap-3 pa-8 border-dashed drop-zone flex-wrap"
|
||||
>
|
||||
<VRow class="match-height w-100">
|
||||
<template
|
||||
v-for="(item, index) in fileData"
|
||||
:key="index"
|
||||
>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="4"
|
||||
>
|
||||
<VCard
|
||||
:ripple="false"
|
||||
border
|
||||
>
|
||||
<VCardText
|
||||
class="d-flex flex-column"
|
||||
@click.stop
|
||||
>
|
||||
<VImg
|
||||
:src="item.url"
|
||||
width="200px"
|
||||
height="150px"
|
||||
class="w-100 mx-auto"
|
||||
/>
|
||||
<div class="mt-2">
|
||||
<span class="clamp-text text-wrap">
|
||||
{{ item.file.name }}
|
||||
</span>
|
||||
<span>
|
||||
{{ item.file.size / 1000 }} KB
|
||||
</span>
|
||||
</div>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VBtn
|
||||
variant="text"
|
||||
block
|
||||
@click.stop="fileData.splice(index, 1)"
|
||||
>
|
||||
Remove File
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</template>
|
||||
</VRow>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- 👉 Variants -->
|
||||
<VCard
|
||||
title="Variants"
|
||||
class="mb-6"
|
||||
>
|
||||
<VCardText>
|
||||
<template
|
||||
v-for="i in optionCounter"
|
||||
:key="i"
|
||||
>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<VSelect
|
||||
:items="['Size', 'Color', 'Weight', 'Smell']"
|
||||
placeholder="Select Variant"
|
||||
label="Select Variant"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="8"
|
||||
>
|
||||
<VTextField
|
||||
label="Variant Value"
|
||||
type="number"
|
||||
placeholder="Enter Variant Value"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<VBtn
|
||||
class="mt-6"
|
||||
@click="optionCounter++"
|
||||
>
|
||||
Add another option
|
||||
</VBtn>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- 👉 Inventory -->
|
||||
<VCard
|
||||
title="Inventory"
|
||||
class="inventory-card"
|
||||
>
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<VTabs
|
||||
v-model="activeTab"
|
||||
direction="vertical"
|
||||
color="primary"
|
||||
class="v-tabs-pill"
|
||||
>
|
||||
<VTab
|
||||
v-for="(tab, index) in inventoryTabsData"
|
||||
:key="index"
|
||||
:value="tab.value"
|
||||
>
|
||||
<VIcon
|
||||
:icon="tab.icon"
|
||||
class="me-2"
|
||||
/>
|
||||
<span>{{ tab.title }}</span>
|
||||
</VTab>
|
||||
</VTabs>
|
||||
</VCol>
|
||||
|
||||
<VDivider
|
||||
:vertical="$vuetify.display.mdAndUp ? true : false"
|
||||
inset
|
||||
/>
|
||||
|
||||
<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">
|
||||
<div class="text-body-1 font-weight-medium">
|
||||
Options
|
||||
</div>
|
||||
<div class="d-flex gap-x-4 align-center">
|
||||
<VTextField
|
||||
label="Add to stock"
|
||||
placeholder="100"
|
||||
density="compact"
|
||||
/>
|
||||
<VBtn prepend-icon="ri-check-line">
|
||||
Confirm
|
||||
</VBtn>
|
||||
</div>
|
||||
<div class="d-flex flex-column gap-2 text-high-emphasis">
|
||||
<div>
|
||||
Product in stock now: 54
|
||||
</div>
|
||||
<div>
|
||||
Product in transit: 390
|
||||
</div>
|
||||
<div>
|
||||
Last time restocked: 24th June, 2022
|
||||
</div>
|
||||
<div>
|
||||
Total stock over lifetime: 2,430
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</VWindowItem>
|
||||
|
||||
<VWindowItem value="Shipping">
|
||||
<VRadioGroup v-model="shippingType">
|
||||
<template #label>
|
||||
<span class="font-weight-medium mb-1">Shipping Type</span>
|
||||
</template>
|
||||
<VRadio
|
||||
v-for="item in shippingList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
class="mb-4 ps-1"
|
||||
inline
|
||||
>
|
||||
<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>
|
||||
<VRadioGroup v-model="deliveryType">
|
||||
<template #label>
|
||||
<span class="font-weight-medium mb-1">Global Delivery</span>
|
||||
</template>
|
||||
|
||||
<VRadio
|
||||
value="Worldwide delivery"
|
||||
class="mb-4 ps-1"
|
||||
>
|
||||
<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 ps-1"
|
||||
>
|
||||
<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="mb-2 text-base font-weight-medium">
|
||||
Attributes
|
||||
</div>
|
||||
<div>
|
||||
<VCheckbox
|
||||
v-model="selectedAttrs"
|
||||
label="Fragile Product"
|
||||
value="Fragile Product"
|
||||
class="ps-3"
|
||||
/>
|
||||
<VCheckbox
|
||||
v-model="selectedAttrs"
|
||||
value="Biodegradable"
|
||||
label="Biodegradable"
|
||||
class="ps-3"
|
||||
/>
|
||||
<VCheckbox
|
||||
v-model="selectedAttrs"
|
||||
value="Frozen Product"
|
||||
class="ps-3"
|
||||
>
|
||||
<template #label>
|
||||
<div class="d-flex flex-column mb-1">
|
||||
<div>Frozen Product</div>
|
||||
<VTextField
|
||||
placeholder="40 C"
|
||||
type="number"
|
||||
style="min-inline-size: 250px;"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</VCheckbox>
|
||||
|
||||
<VCheckbox
|
||||
v-model="selectedAttrs"
|
||||
value="Expiry Date"
|
||||
class="ps-3"
|
||||
>
|
||||
<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>
|
||||
</VWindowItem>
|
||||
|
||||
<VWindowItem value="Advanced">
|
||||
<div class="mb-3 text-base font-weight-medium">
|
||||
Advanced
|
||||
</div>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="7"
|
||||
>
|
||||
<VSelect
|
||||
style="min-inline-size: 200px;"
|
||||
label="Product ID Type"
|
||||
placeholder="Select Product Type"
|
||||
:items="['ISBN', 'UPC', 'EAN', 'JAN']"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="5"
|
||||
>
|
||||
<VTextField
|
||||
label="Product Id"
|
||||
placeholder="100023"
|
||||
type="number"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VWindowItem>
|
||||
</VWindow>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
md="4"
|
||||
cols="12"
|
||||
>
|
||||
<!-- 👉 Pricing -->
|
||||
<VCard
|
||||
title="Pricing"
|
||||
class="mb-6"
|
||||
>
|
||||
<VCardText>
|
||||
<VTextField
|
||||
label="Base Price"
|
||||
placeholder="iPhone 14"
|
||||
class="mb-6"
|
||||
/>
|
||||
<VTextField
|
||||
label="Discounted Price"
|
||||
placeholder="$499"
|
||||
class="mb-4"
|
||||
/>
|
||||
<VCheckbox
|
||||
v-model="isTaxable"
|
||||
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
|
||||
v-model="inStock"
|
||||
density="compact"
|
||||
/>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- 👉 Organize -->
|
||||
<VCard title="Organize">
|
||||
<VCardText>
|
||||
<div class="d-flex flex-column gap-y-4">
|
||||
<VSelect
|
||||
placeholder="Select Vendor"
|
||||
label="Vendor"
|
||||
:items="['Men\'s Clothing', 'Women\'s Clothing', 'Kid\'s Clothing']"
|
||||
/>
|
||||
<VSelect
|
||||
placeholder="Select Category"
|
||||
label="Category"
|
||||
:items="['Household', 'Office', 'Electronics', 'Management', 'Automotive']"
|
||||
>
|
||||
<template #append>
|
||||
<IconBtn
|
||||
icon="ri-add-line"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
rounded
|
||||
/>
|
||||
</template>
|
||||
</VSelect>
|
||||
<VSelect
|
||||
placeholder="Select Collection"
|
||||
label="Collection"
|
||||
:items="['Men\'s Clothing', 'Women\'s Clothing', 'Kid\'s Clothing']"
|
||||
/>
|
||||
<VSelect
|
||||
placeholder="Select Status"
|
||||
label="Status"
|
||||
:items="['Published', 'Inactive', 'Scheduled']"
|
||||
/>
|
||||
<VTextField
|
||||
label="Tags"
|
||||
placeholder="Fashion, Trending, Summer"
|
||||
/>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.drop-zone {
|
||||
border: 1px dashed rgba(var(--v-theme-on-surface), 0.12);
|
||||
border-radius: 6px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.inventory-card{
|
||||
.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>
|
310
resources/js/pages/apps/ecommerce/product/category-list.vue
Normal file
310
resources/js/pages/apps/ecommerce/product/category-list.vue
Normal file
@@ -0,0 +1,310 @@
|
||||
<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 = [
|
||||
{
|
||||
categoryTitle: 'Smart Phone',
|
||||
description: 'Choose from wide range of smartphones online at best prices.',
|
||||
totalProduct: 12548,
|
||||
totalEarning: 98784,
|
||||
image: product1,
|
||||
},
|
||||
{
|
||||
categoryTitle: 'Clothing, Shoes, and jewellery',
|
||||
description: 'Fashion for a wide selection of clothing, shoes, jewellery and watches.',
|
||||
totalProduct: 4689,
|
||||
totalEarning: 45627,
|
||||
image: product9,
|
||||
},
|
||||
{
|
||||
categoryTitle: 'Home and Kitchen',
|
||||
description: 'Browse through the wide range of Home and kitchen products.',
|
||||
totalProduct: 12548,
|
||||
totalEarning: 98784,
|
||||
image: product10,
|
||||
},
|
||||
{
|
||||
categoryTitle: 'Beauty and Personal Care',
|
||||
description: 'Explore beauty and personal care products, shop makeup and etc.',
|
||||
totalProduct: 12548,
|
||||
totalEarning: 98784,
|
||||
image: product19,
|
||||
},
|
||||
{
|
||||
categoryTitle: 'Books',
|
||||
description: 'Over 25 million titles across categories such as business and etc.',
|
||||
totalProduct: 12548,
|
||||
totalEarning: 98784,
|
||||
image: product25,
|
||||
},
|
||||
{
|
||||
categoryTitle: 'Games',
|
||||
description: 'Every month, get exclusive in-game loot, free games, a free subscription.',
|
||||
totalProduct: 12548,
|
||||
totalEarning: 98784,
|
||||
image: product12,
|
||||
},
|
||||
{
|
||||
categoryTitle: 'Baby Products',
|
||||
description: 'Buy baby products across different categories from top brands.',
|
||||
totalProduct: 12548,
|
||||
totalEarning: 98784,
|
||||
image: product14,
|
||||
},
|
||||
{
|
||||
categoryTitle: 'Growsari',
|
||||
description: 'Shop grocery Items through at best prices in India.',
|
||||
totalProduct: 12548,
|
||||
totalEarning: 98784,
|
||||
image: product28,
|
||||
},
|
||||
{
|
||||
categoryTitle: 'Computer Accessories',
|
||||
description: 'Enhance your computing experience with our range of computer accessories.',
|
||||
totalProduct: 9876,
|
||||
totalEarning: 65421,
|
||||
image: product17,
|
||||
},
|
||||
{
|
||||
categoryTitle: 'Fitness Tracker',
|
||||
description: 'Monitor your health and fitness goals with our range of advanced fitness trackers.',
|
||||
totalProduct: 1987,
|
||||
totalEarning: 32067,
|
||||
image: product10,
|
||||
},
|
||||
{
|
||||
categoryTitle: 'Smart Home Devices',
|
||||
description: 'Transform your home into a smart home with our innovative smart home devices.',
|
||||
totalProduct: 2345,
|
||||
totalEarning: 87654,
|
||||
image: product11,
|
||||
},
|
||||
{
|
||||
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',
|
||||
align: 'end',
|
||||
},
|
||||
{
|
||||
title: 'Total Earning',
|
||||
key: 'totalEarning',
|
||||
align: 'end',
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
key: 'actions',
|
||||
sortable: false,
|
||||
},
|
||||
]
|
||||
|
||||
const itemsPerPage = ref(10)
|
||||
const searchQuery = ref('')
|
||||
const isAddProductDrawerOpen = ref(false)
|
||||
const page = ref(1)
|
||||
|
||||
const updateOptions = options => {
|
||||
page.value = options.page
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<VCard>
|
||||
<VCardText>
|
||||
<div class="d-flex justify-md-space-between flex-wrap gap-4 justify-center">
|
||||
<VTextField
|
||||
v-model="searchQuery"
|
||||
placeholder="Search"
|
||||
density="compact"
|
||||
style="max-inline-size: 280px; min-inline-size: 200px;"
|
||||
/>
|
||||
|
||||
<div class="d-flex align-center flex-wrap gap-4">
|
||||
<VBtn
|
||||
prepend-icon="ri-upload-2-line"
|
||||
color="secondary"
|
||||
variant="outlined"
|
||||
>
|
||||
Export
|
||||
</VBtn>
|
||||
<VBtn
|
||||
prepend-icon="ri-add-line"
|
||||
@click="isAddProductDrawerOpen = !isAddProductDrawerOpen"
|
||||
>
|
||||
Add Category
|
||||
</VBtn>
|
||||
</div>
|
||||
</div>
|
||||
</VCardText>
|
||||
|
||||
<VDataTable
|
||||
v-model:items-per-page="itemsPerPage"
|
||||
:headers="headers"
|
||||
:page="page"
|
||||
:items="categoryData"
|
||||
item-value="categoryTitle"
|
||||
:search="searchQuery"
|
||||
show-select
|
||||
class="text-no-wrap category-table"
|
||||
@update:options="updateOptions"
|
||||
>
|
||||
<template #item.actions>
|
||||
<IconBtn size="small">
|
||||
<VIcon icon="ri-edit-box-line" />
|
||||
</IconBtn>
|
||||
|
||||
<MoreBtn
|
||||
size="small"
|
||||
:menu-list="[
|
||||
{
|
||||
title: 'Download',
|
||||
value: 'download',
|
||||
prependIcon: 'ri-download-line',
|
||||
},
|
||||
{
|
||||
title: 'Edit',
|
||||
value: 'edit',
|
||||
prependIcon: 'ri-pencil-line',
|
||||
},
|
||||
{
|
||||
title: 'Duplicate',
|
||||
value: 'duplicate',
|
||||
prependIcon: 'ri-stack-line',
|
||||
},
|
||||
]"
|
||||
item-props
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #item.categoryTitle="{ item }">
|
||||
<div class="d-flex gap-x-3">
|
||||
<VAvatar
|
||||
variant="tonal"
|
||||
rounded
|
||||
size="38"
|
||||
>
|
||||
<img
|
||||
:src="item.image"
|
||||
:alt="item.categoryTitle"
|
||||
width="38"
|
||||
height="38"
|
||||
>
|
||||
</VAvatar>
|
||||
<div>
|
||||
<p class="text-high-emphasis font-weight-medium mb-0">
|
||||
{{ item.categoryTitle }}
|
||||
</p>
|
||||
<div class="text-body-2">
|
||||
{{ item.description }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item.totalEarning="{ item }">
|
||||
<div class="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>
|
||||
|
||||
<!-- Pagination -->
|
||||
<template #bottom>
|
||||
<VDivider />
|
||||
|
||||
<div class="d-flex justify-end flex-wrap gap-x-6 px-2 py-1">
|
||||
<div class="d-flex align-center gap-x-2 text-medium-emphasis text-base">
|
||||
Rows Per Page:
|
||||
<VSelect
|
||||
v-model="itemsPerPage"
|
||||
class="per-page-select"
|
||||
variant="plain"
|
||||
:items="[10, 20, 25, 50, 100]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="d-flex align-center text-base text-high-emphasis me-2 mb-0">
|
||||
{{ paginationMeta({ page, itemsPerPage }, categoryData.length) }}
|
||||
</p>
|
||||
|
||||
<div class="d-flex gap-x-2 align-center me-2">
|
||||
<VBtn
|
||||
class="flip-in-rtl"
|
||||
icon="ri-arrow-left-s-line"
|
||||
variant="text"
|
||||
density="comfortable"
|
||||
color="high-emphasis"
|
||||
:disabled="page <= 1"
|
||||
@click="page <= 1 ? page = 1 : page--"
|
||||
/>
|
||||
|
||||
<VBtn
|
||||
class="flip-in-rtl"
|
||||
icon="ri-arrow-right-s-line"
|
||||
density="comfortable"
|
||||
variant="text"
|
||||
color="high-emphasis"
|
||||
:disabled="page >= Math.ceil(categoryData.length / itemsPerPage)"
|
||||
@click="page >= Math.ceil(categoryData.length / itemsPerPage) ? page = Math.ceil(categoryData.length / itemsPerPage) : page++ "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</VDataTable>
|
||||
</VCard>
|
||||
|
||||
<ECommerceAddCategoryDrawer v-model:isDrawerOpen="isAddProductDrawerOpen" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.ProseMirror-focused{
|
||||
border: none;
|
||||
}
|
||||
|
||||
.category-table.v-table.v-data-table{
|
||||
.v-table__wrapper{
|
||||
table{
|
||||
thead{
|
||||
tr{
|
||||
th.v-data-table-column--align-end{
|
||||
.v-data-table-header__content{
|
||||
flex-direction: row;
|
||||
justify-content: end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
518
resources/js/pages/apps/ecommerce/product/list/index.vue
Normal file
518
resources/js/pages/apps/ecommerce/product/list/index.vue
Normal file
@@ -0,0 +1,518 @@
|
||||
<script setup>
|
||||
const widgetData = ref([
|
||||
{
|
||||
title: 'In-Store Sales',
|
||||
value: '$5,345',
|
||||
icon: 'ri-home-6-line',
|
||||
desc: '5k orders',
|
||||
change: 5.7,
|
||||
},
|
||||
{
|
||||
title: 'Website Sales',
|
||||
value: '$74,347',
|
||||
icon: 'ri-computer-line',
|
||||
desc: '21k orders',
|
||||
change: 12.4,
|
||||
},
|
||||
{
|
||||
title: 'Discount',
|
||||
value: '$14,235',
|
||||
icon: 'ri-gift-line',
|
||||
desc: '6k orders',
|
||||
},
|
||||
{
|
||||
title: 'Affiliate',
|
||||
value: '$8,345',
|
||||
icon: 'ri-money-dollar-circle-line',
|
||||
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 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 => {
|
||||
page.value = options.page
|
||||
sortBy.value = options.sortBy[0]?.key
|
||||
orderBy.value = options.sortBy[0]?.order
|
||||
}
|
||||
|
||||
const resolveCategory = category => {
|
||||
if (category === 'Accessories')
|
||||
return {
|
||||
color: 'error',
|
||||
icon: 'ri-headphone-line',
|
||||
}
|
||||
if (category === 'Home Decor')
|
||||
return {
|
||||
color: 'info',
|
||||
icon: 'ri-home-6-line',
|
||||
}
|
||||
if (category === 'Electronics')
|
||||
return {
|
||||
color: 'primary',
|
||||
icon: 'ri-computer-line',
|
||||
}
|
||||
if (category === 'Shoes')
|
||||
return {
|
||||
color: 'success',
|
||||
icon: 'ri-footprint-line',
|
||||
}
|
||||
if (category === 'Office')
|
||||
return {
|
||||
color: 'warning',
|
||||
icon: 'ri-briefcase-line',
|
||||
}
|
||||
if (category === 'Games')
|
||||
return {
|
||||
color: 'primary',
|
||||
icon: 'ri-gamepad-line',
|
||||
}
|
||||
}
|
||||
|
||||
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' })
|
||||
fetchProducts()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- 👉 widgets -->
|
||||
<VCard class="mb-6">
|
||||
<VCardText class="px-2">
|
||||
<VRow>
|
||||
<template
|
||||
v-for="(data, index) in widgetData"
|
||||
:key="index"
|
||||
>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="3"
|
||||
class="px-6"
|
||||
>
|
||||
<div
|
||||
class="d-flex justify-space-between"
|
||||
:class="$vuetify.display.xs ? 'product-widget' : $vuetify.display.sm ? index < 2 ? 'product-widget' : '' : ''"
|
||||
>
|
||||
<div class="d-flex flex-column gap-y-1">
|
||||
<p class="text-capitalize mb-0">
|
||||
{{ data.title }}
|
||||
</p>
|
||||
|
||||
<h6 class="text-h4">
|
||||
{{ data.value }}
|
||||
</h6>
|
||||
|
||||
<div class="d-flex align-center">
|
||||
<div class="text-no-wrap me-2">
|
||||
{{ data.desc }}
|
||||
</div>
|
||||
|
||||
<VChip
|
||||
v-if="data.change"
|
||||
size="small"
|
||||
:color="data.change > 0 ? 'success' : 'error'"
|
||||
>
|
||||
{{ prefixWithPlus(data.change) }}%
|
||||
</VChip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<VAvatar
|
||||
variant="tonal"
|
||||
rounded
|
||||
size="44"
|
||||
>
|
||||
<VIcon
|
||||
:icon="data.icon"
|
||||
size="28"
|
||||
color="high-emphasis"
|
||||
/>
|
||||
</VAvatar>
|
||||
</div>
|
||||
</VCol>
|
||||
<VDivider
|
||||
v-if="$vuetify.display.mdAndUp ? index !== widgetData.length - 1 : $vuetify.display.smAndUp ? index % 2 === 0 : false"
|
||||
vertical
|
||||
inset
|
||||
length="100"
|
||||
/>
|
||||
</template>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- 👉 products -->
|
||||
<VCard title="Filters">
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<!-- 👉 Select Status -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="4"
|
||||
>
|
||||
<VSelect
|
||||
v-model="selectedStatus"
|
||||
label="Select Status"
|
||||
placeholder="Select Status"
|
||||
:items="status"
|
||||
clearable
|
||||
clear-icon="ri-close-line"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Select Category -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="4"
|
||||
>
|
||||
<VSelect
|
||||
v-model="selectedCategory"
|
||||
label="Category"
|
||||
placeholder="Select Category"
|
||||
:items="categories"
|
||||
clearable
|
||||
clear-icon="ri-close-line"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Select Stock Status -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="4"
|
||||
>
|
||||
<VSelect
|
||||
v-model="selectedStock"
|
||||
label="Stock"
|
||||
placeholder="Stock"
|
||||
:items="stockStatus"
|
||||
clearable
|
||||
clear-icon="ri-close-line"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
|
||||
<VDivider />
|
||||
|
||||
<VCardText class="d-flex flex-wrap gap-4">
|
||||
<div class="d-flex align-center">
|
||||
<!-- 👉 Search -->
|
||||
<VTextField
|
||||
v-model="searchQuery"
|
||||
placeholder="Search Product"
|
||||
style="inline-size: 200px;"
|
||||
density="compact"
|
||||
class="me-3"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<VSpacer />
|
||||
|
||||
<div class="d-flex gap-x-4">
|
||||
<!-- 👉 Export button -->
|
||||
<div>
|
||||
<VBtn
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
prepend-icon="ri-external-link-line"
|
||||
>
|
||||
Export
|
||||
</VBtn>
|
||||
</div>
|
||||
|
||||
<VBtn
|
||||
color="primary"
|
||||
prepend-icon="ri-add-line"
|
||||
@click="$router.push('/apps/ecommerce/product/add')"
|
||||
>
|
||||
Add Product
|
||||
</VBtn>
|
||||
</div>
|
||||
</VCardText>
|
||||
|
||||
<!-- 👉 Datatable -->
|
||||
<VDataTableServer
|
||||
v-model:items-per-page="itemsPerPage"
|
||||
v-model:page="page"
|
||||
:headers="headers"
|
||||
show-select
|
||||
:items="products"
|
||||
:items-length="totalProduct"
|
||||
class="text-no-wrap rounded-0"
|
||||
@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-base text-high-emphasis font-weight-medium">{{ item.productName }}</span>
|
||||
<span class="text-sm">{{ 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-base 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)"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Actions -->
|
||||
<template #item.actions="{ item }">
|
||||
<IconBtn size="small">
|
||||
<VIcon icon="ri-edit-box-line" />
|
||||
</IconBtn>
|
||||
|
||||
<IconBtn size="small">
|
||||
<VIcon icon="ri-more-2-fill" />
|
||||
|
||||
<VMenu activator="parent">
|
||||
<VList>
|
||||
<VListItem
|
||||
value="download"
|
||||
prepend-icon="ri-download-line"
|
||||
>
|
||||
Download
|
||||
</VListItem>
|
||||
|
||||
<VListItem
|
||||
value="delete"
|
||||
prepend-icon="ri-delete-bin-line"
|
||||
@click="deleteProduct(item.id)"
|
||||
>
|
||||
Delete
|
||||
</VListItem>
|
||||
|
||||
<VListItem
|
||||
value="duplicate"
|
||||
prepend-icon="ri-stack-line"
|
||||
>
|
||||
Duplicate
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</IconBtn>
|
||||
</template>
|
||||
|
||||
<!-- Pagination -->
|
||||
<template #bottom>
|
||||
<VDivider />
|
||||
|
||||
<div class="d-flex justify-end flex-wrap gap-x-6 px-2 py-1">
|
||||
<div class="d-flex align-center gap-x-2 text-medium-emphasis text-base">
|
||||
Rows Per Page:
|
||||
<VSelect
|
||||
v-model="itemsPerPage"
|
||||
class="per-page-select"
|
||||
variant="plain"
|
||||
:items="[10, 20, 25, 50, 100]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="d-flex align-center text-base text-high-emphasis me-2 mb-0">
|
||||
{{ paginationMeta({ page, itemsPerPage }, totalProduct) }}
|
||||
</p>
|
||||
|
||||
<div class="d-flex gap-x-2 align-center me-2">
|
||||
<VBtn
|
||||
class="flip-in-rtl"
|
||||
icon="ri-arrow-left-s-line"
|
||||
variant="text"
|
||||
density="comfortable"
|
||||
color="high-emphasis"
|
||||
:disabled="page <= 1"
|
||||
@click="page <= 1 ? page = 1 : page--"
|
||||
/>
|
||||
|
||||
<VBtn
|
||||
class="flip-in-rtl"
|
||||
icon="ri-arrow-right-s-line"
|
||||
density="comfortable"
|
||||
variant="text"
|
||||
color="high-emphasis"
|
||||
:disabled="page >= Math.ceil(totalProduct / itemsPerPage)"
|
||||
@click="page >= Math.ceil(totalProduct / itemsPerPage) ? page = Math.ceil(totalProduct / itemsPerPage) : page++ "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</VDataTableServer>
|
||||
</VCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.product-widget{
|
||||
border-block-end: 1px solid rgba(var(--v-theme-on-surface), var(--v-border-opacity));
|
||||
padding-block-end: 1rem;
|
||||
}
|
||||
</style>
|
384
resources/js/pages/apps/ecommerce/referrals.vue
Normal file
384
resources/js/pages/apps/ecommerce/referrals.vue
Normal file
@@ -0,0 +1,384 @@
|
||||
<script setup>
|
||||
import paperImg from '@images/svg/paper.svg?raw'
|
||||
import rocketImg from '@images/svg/rocket.svg?raw'
|
||||
import userInfoImg from '@images/svg/user-info.svg?raw'
|
||||
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
|
||||
|
||||
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: 'ri-money-dollar-circle-line',
|
||||
color: 'primary',
|
||||
},
|
||||
{
|
||||
title: 'Unpaid Earning',
|
||||
value: '$8,647',
|
||||
icon: 'ri-gift-line',
|
||||
color: 'success',
|
||||
},
|
||||
{
|
||||
title: 'Signups',
|
||||
value: '2,367',
|
||||
icon: 'ri-group-line',
|
||||
color: 'error',
|
||||
},
|
||||
{
|
||||
title: 'Conversion Rate',
|
||||
value: '4.5%',
|
||||
icon: 'ri-refresh-line',
|
||||
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 => {
|
||||
page.value = options.page
|
||||
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 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 align-center justify-space-between">
|
||||
<div class="d-flex flex-column">
|
||||
<span class="text-h5 mb-1">{{ data.value }}</span>
|
||||
<span class="text-sm">{{ data.title }}</span>
|
||||
</div>
|
||||
<VAvatar
|
||||
:icon="data.icon"
|
||||
variant="tonal"
|
||||
:color="data.color"
|
||||
/>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Icon Steps -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="8"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle> How to use</VCardTitle>
|
||||
<VCardSubtitle>
|
||||
Integrate your referral code in 3 easy steps.
|
||||
</VCardSubtitle>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<div class="d-flex gap-6 justify-space-between align-center flex-sm-row flex-column">
|
||||
<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">
|
||||
<VNodeRenderer :nodes="step.icon" />
|
||||
</div>
|
||||
<span class="text-body-1 text-wrap text-center">
|
||||
{{ step.desc }}
|
||||
</span>
|
||||
<span class="text-primary text-h6">{{ step.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Invite -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<VCard>
|
||||
<VCardText>
|
||||
<div class="mb-11">
|
||||
<h5 class="text-h5 mb-5">
|
||||
Invite your friends
|
||||
</h5>
|
||||
<div class="d-flex align-center flex-wrap gap-4">
|
||||
<VTextField
|
||||
placeholder="Email Addresss"
|
||||
density="compact"
|
||||
/>
|
||||
<VBtn>
|
||||
<VIcon
|
||||
start
|
||||
icon="ri-check-line"
|
||||
/>
|
||||
Submit
|
||||
</VBtn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5 class="text-h5 mb-5">
|
||||
Share the referral link
|
||||
</h5>
|
||||
<div class="d-flex align-center flex-wrap gap-4">
|
||||
<VTextField
|
||||
placeholder="themeselection.com/?ref=6478"
|
||||
density="compact"
|
||||
/>
|
||||
<div class="d-flex gap-x-2">
|
||||
<VBtn
|
||||
icon
|
||||
class="rounded"
|
||||
color="#3B5998"
|
||||
>
|
||||
<VIcon
|
||||
color="white"
|
||||
icon="ri-facebook-circle-line"
|
||||
/>
|
||||
</VBtn>
|
||||
|
||||
<VBtn
|
||||
icon
|
||||
class="rounded"
|
||||
color="#55ACEE"
|
||||
>
|
||||
<VIcon
|
||||
color="white"
|
||||
icon="ri-twitter-line"
|
||||
/>
|
||||
</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>
|
||||
<VBtn prepend-icon="ri-upload-2-line">
|
||||
Export
|
||||
</VBtn>
|
||||
</div>
|
||||
</VCardText>
|
||||
|
||||
<VDataTableServer
|
||||
v-model:items-per-page="itemsPerPage"
|
||||
v-model:page="page"
|
||||
:items="referrals"
|
||||
:headers="headers"
|
||||
:items-length="totalReferrals"
|
||||
show-select
|
||||
class="text-high-emphasis"
|
||||
@update:options="updateOptions"
|
||||
>
|
||||
<template #item.users="{ item }">
|
||||
<div class="d-flex align-center gap-x-4">
|
||||
<VAvatar
|
||||
:image="item.avatar"
|
||||
:size="34"
|
||||
/>
|
||||
<div>
|
||||
<h6 class="text-h6">
|
||||
<RouterLink
|
||||
class="text-high-emphasis"
|
||||
:to="{ name: 'apps-ecommerce-customer-details-id', params: { id: 478426 } }"
|
||||
>
|
||||
{{ item.user }}
|
||||
</RouterLink>
|
||||
</h6>
|
||||
|
||||
<div class="text-sm text-medium-emphasis">
|
||||
{{ item.email }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item.referred-id="{ item }">
|
||||
{{ item.referredId }}
|
||||
</template>
|
||||
|
||||
<template #item.status="{ item }">
|
||||
<VChip
|
||||
v-bind="resolveStatus(item.status)"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Pagination -->
|
||||
<template #bottom>
|
||||
<VDivider />
|
||||
|
||||
<div class="d-flex justify-end flex-wrap gap-x-6 px-2 py-1">
|
||||
<div class="d-flex align-center gap-x-2 text-medium-emphasis text-base">
|
||||
Rows Per Page:
|
||||
<VSelect
|
||||
v-model="itemsPerPage"
|
||||
class="per-page-select"
|
||||
variant="plain"
|
||||
:items="[10, 20, 25, 50, 100]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="d-flex align-center text-base text-high-emphasis me-2 mb-0">
|
||||
{{ paginationMeta({ page, itemsPerPage }, totalReferrals) }}
|
||||
</p>
|
||||
|
||||
<div class="d-flex gap-x-2 align-center me-2">
|
||||
<VBtn
|
||||
class="flip-in-rtl"
|
||||
icon="ri-arrow-left-s-line"
|
||||
variant="text"
|
||||
density="comfortable"
|
||||
color="high-emphasis"
|
||||
:disabled="page <= 1"
|
||||
@click="page <= 1 ? page = 1 : page--"
|
||||
/>
|
||||
|
||||
<VBtn
|
||||
class="flip-in-rtl"
|
||||
icon="ri-arrow-right-s-line"
|
||||
density="comfortable"
|
||||
variant="text"
|
||||
color="high-emphasis"
|
||||
:disabled="page >= Math.ceil(totalReferrals / itemsPerPage)"
|
||||
@click="page >= Math.ceil(totalReferrals / itemsPerPage) ? page = Math.ceil(totalReferrals / itemsPerPage) : page++ "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</VDataTableServer>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px dashed rgb(var(--v-theme-primary));
|
||||
border-radius: 50%;
|
||||
block-size: 70px;
|
||||
inline-size: 70px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: #000;
|
||||
font-size: 42px;
|
||||
}
|
||||
</style>
|
99
resources/js/pages/apps/ecommerce/settings.vue
Normal file
99
resources/js/pages/apps/ecommerce/settings.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<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: 'ri-store-line',
|
||||
title: 'Store Details',
|
||||
},
|
||||
{
|
||||
icon: 'ri-bank-card-line',
|
||||
title: 'Payments',
|
||||
},
|
||||
{
|
||||
icon: 'ri-shopping-cart-line',
|
||||
title: 'Checkout',
|
||||
},
|
||||
{
|
||||
icon: 'ri-car-line',
|
||||
title: 'Shipping & Delivery',
|
||||
},
|
||||
{
|
||||
icon: 'ri-map-pin-line',
|
||||
title: 'Location',
|
||||
},
|
||||
{
|
||||
icon: 'ri-notification-3-line',
|
||||
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>
|
Reference in New Issue
Block a user