Initial commit
This commit is contained in:
54
resources/js/pages/[...error].vue
Normal file
54
resources/js/pages/[...error].vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<script setup>
|
||||
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
|
||||
import misc404 from '@images/pages/404.png'
|
||||
import miscMaskDark from '@images/pages/misc-mask-dark.png'
|
||||
import miscMaskLight from '@images/pages/misc-mask-light.png'
|
||||
|
||||
const authThemeMask = useGenerateImageVariant(miscMaskLight, miscMaskDark)
|
||||
|
||||
definePage({
|
||||
alias: '/pages/misc/not-found/:error(.*)',
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
public: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="misc-wrapper">
|
||||
<ErrorHeader
|
||||
status-code="404"
|
||||
title="Page Not Found ⚠️"
|
||||
description="We couldn't find the page you are looking for."
|
||||
/>
|
||||
|
||||
<VBtn
|
||||
to="/"
|
||||
class="mb-11"
|
||||
>
|
||||
Back to Home
|
||||
</VBtn>
|
||||
|
||||
<!-- 👉 Image -->
|
||||
<div class="misc-avatar w-100 text-center">
|
||||
<VImg
|
||||
:src="misc404"
|
||||
alt="error 404"
|
||||
:max-height="$vuetify.display.smAndDown ? 350 : 500"
|
||||
class="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<img
|
||||
class="misc-footer-img d-none d-md-block"
|
||||
:src="authThemeMask"
|
||||
alt="misc-footer-img"
|
||||
height="320"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/pages/misc.scss";
|
||||
</style>
|
||||
36
resources/js/pages/access-control.vue
Normal file
36
resources/js/pages/access-control.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<script setup>
|
||||
definePage({
|
||||
meta: {
|
||||
action: 'read',
|
||||
subject: 'AclDemo',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VCard title="Common">
|
||||
<VCardText>No ability is required to view this card</VCardText>
|
||||
<VCardText>
|
||||
This card is visible to both 'user' and 'admin'
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
<VCol
|
||||
v-if="$can('read', 'all')"
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VCard title="Analytics">
|
||||
<VCardText>User with 'Analytics' subject's 'Read' ability can view this card</VCardText>
|
||||
<VCardText class="text-danger">
|
||||
This card is visible to 'admin' only
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
315
resources/js/pages/apps/academy/course-details.vue
Normal file
315
resources/js/pages/apps/academy/course-details.vue
Normal file
@@ -0,0 +1,315 @@
|
||||
<script setup>
|
||||
import { VideoPlayer } from '@videojs-player/vue'
|
||||
import InstructorPoster from '@images/pages/instructor-poster.png'
|
||||
import 'video.js/dist/video-js.css'
|
||||
|
||||
const courseDetails = ref()
|
||||
const { data, error } = await useApi('/apps/academy/course-details')
|
||||
if (error.value)
|
||||
console.log(error.value)
|
||||
else if (data.value)
|
||||
courseDetails.value = data.value
|
||||
const panelStatus = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="8"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem
|
||||
title="UI/UX Basic Fundamentals"
|
||||
class="pb-6"
|
||||
>
|
||||
<template #subtitle>
|
||||
<div class="text-body-1">
|
||||
Prof. <span class="text-h6 d-inline-block">{{ courseDetails?.instructor }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #append>
|
||||
<div class="d-flex gap-4 align-center">
|
||||
<VChip
|
||||
variant="tonal"
|
||||
color="error"
|
||||
size="small"
|
||||
>
|
||||
UI/UX
|
||||
</VChip>
|
||||
<VIcon
|
||||
size="24"
|
||||
class="cursor-pointer"
|
||||
icon="tabler-share"
|
||||
/>
|
||||
<VIcon
|
||||
size="24"
|
||||
class="cursor-pointer"
|
||||
icon="tabler-bookmarks"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VCard
|
||||
flat
|
||||
border
|
||||
>
|
||||
<div class="px-2 pt-2">
|
||||
<VideoPlayer
|
||||
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4"
|
||||
:poster="InstructorPoster"
|
||||
controls
|
||||
plays-inline
|
||||
:height="$vuetify.display.mdAndUp ? 440 : 250"
|
||||
class="w-100 rounded"
|
||||
/>
|
||||
</div>
|
||||
<VCardText>
|
||||
<h5 class="text-h5 mb-4">
|
||||
About this course
|
||||
</h5>
|
||||
<p class="text-body-1">
|
||||
{{ courseDetails?.about }}
|
||||
</p>
|
||||
<VDivider class="my-6" />
|
||||
|
||||
<h5 class="text-h5 mb-4">
|
||||
By the numbers
|
||||
</h5>
|
||||
<div class="d-flex gap-x-12 gap-y-5 flex-wrap">
|
||||
<div>
|
||||
<VList class="card-list text-medium-emphasis">
|
||||
<VListItem>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
icon="tabler-check"
|
||||
size="20"
|
||||
/>
|
||||
</template>
|
||||
<VListItemTitle>Skill Level: {{ courseDetails?.skillLevel }}</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
icon="tabler-users"
|
||||
size="20"
|
||||
/>
|
||||
</template>
|
||||
<VListItemTitle>Students: {{ courseDetails?.totalStudents }}</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
icon="tabler-world"
|
||||
size="20"
|
||||
/>
|
||||
</template>
|
||||
<VListItemTitle>Languages: {{ courseDetails?.language }}</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
icon="tabler-file"
|
||||
size="20"
|
||||
/>
|
||||
</template>
|
||||
<VListItemTitle>Captions: {{ courseDetails?.isCaptions }}</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<VList class="card-list text-medium-emphasis">
|
||||
<VListItem>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
icon="tabler-video"
|
||||
size="20"
|
||||
/>
|
||||
</template>
|
||||
<VListItemTitle>Lectures: {{ courseDetails?.totalLectures }}</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
icon="tabler-clock"
|
||||
size="20"
|
||||
/>
|
||||
</template>
|
||||
<VListItemTitle>Video: {{ courseDetails?.length }}</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</div>
|
||||
</div>
|
||||
<VDivider class="my-6" />
|
||||
|
||||
<h5 class="text-h5 mb-4">
|
||||
Description
|
||||
</h5>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div v-html="courseDetails?.description" />
|
||||
|
||||
<VDivider class="my-6" />
|
||||
|
||||
<h5 class="text-h5 mb-4">
|
||||
Instructor
|
||||
</h5>
|
||||
<div class="d-flex align-center gap-x-4">
|
||||
<VAvatar
|
||||
:image="courseDetails?.instructorAvatar"
|
||||
size="38"
|
||||
/>
|
||||
<div>
|
||||
<h6 class="text-h6 mb-1">
|
||||
{{ courseDetails?.instructor }}
|
||||
</h6>
|
||||
<div class="text-body-2">
|
||||
{{ courseDetails?.instructorPosition }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<div class="course-content">
|
||||
<VExpansionPanels
|
||||
v-model="panelStatus"
|
||||
variant="accordion"
|
||||
class="expansion-panels-width-border"
|
||||
>
|
||||
<template
|
||||
v-for="(section, index) in courseDetails?.content"
|
||||
:key="index"
|
||||
>
|
||||
<VExpansionPanel
|
||||
elevation="0"
|
||||
:value="index"
|
||||
expand-icon="tabler-chevron-right"
|
||||
collapse-icon="tabler-chevron-down"
|
||||
>
|
||||
<template #title>
|
||||
<div>
|
||||
<h5 class="text-h5 mb-1">
|
||||
{{ section.title }}
|
||||
</h5>
|
||||
<div class="text-medium-emphasis font-weight-normal">
|
||||
{{ section.status }} | {{ section.time }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #text>
|
||||
<VList class="card-list">
|
||||
<VListItem
|
||||
v-for="(topic, id) in section.topics"
|
||||
:key="id"
|
||||
class="py-4"
|
||||
>
|
||||
<template #prepend>
|
||||
<VCheckbox
|
||||
:model-value="topic.isCompleted"
|
||||
class="me-1"
|
||||
/>
|
||||
</template>
|
||||
<VListItemTitle class="text-high-emphasis font-weight-medium">
|
||||
{{ id + 1 }} . {{ topic.title }}
|
||||
</VListItemTitle>
|
||||
<VListItemSubtitle>
|
||||
<div class="text-body-2">
|
||||
{{ topic.time }}
|
||||
</div>
|
||||
</VListItemSubtitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</template>
|
||||
</VExpansionPanel>
|
||||
</template>
|
||||
</VExpansionPanels>
|
||||
</div>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.course-content {
|
||||
position: sticky;
|
||||
inset-block: 4rem 0;
|
||||
}
|
||||
|
||||
.card-list {
|
||||
--v-card-list-gap: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@layouts/styles/mixins" as layoutsMixins;
|
||||
|
||||
body .v-layout .v-application__wrap {
|
||||
.course-content {
|
||||
.v-expansion-panels {
|
||||
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||||
border-radius: 6px;
|
||||
|
||||
.v-expansion-panel {
|
||||
&--active {
|
||||
.v-expansion-panel-title--active {
|
||||
border-block-end: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||||
|
||||
.v-expansion-panel-title__overlay {
|
||||
opacity: var(--v-hover-opacity) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.v-expansion-panel-title {
|
||||
.v-expansion-panel-title__overlay {
|
||||
background-color: rgba(var(--v-theme-on-surface));
|
||||
opacity: var(--v-hover-opacity) !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.v-expansion-panel-title__overlay {
|
||||
opacity: var(--v-hover-opacity) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
.v-icon {
|
||||
block-size: 1.5rem !important;
|
||||
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
|
||||
font-size: 1.5rem !important;
|
||||
inline-size: 1.5rem !important;
|
||||
|
||||
@include layoutsMixins.rtl {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.v-expansion-panel-text {
|
||||
&__wrapper {
|
||||
padding-block: 1rem;
|
||||
padding-inline: 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-list {
|
||||
.v-list-item__prepend {
|
||||
.v-list-item__spacer {
|
||||
inline-size: 8px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
253
resources/js/pages/apps/academy/dashboard.vue
Normal file
253
resources/js/pages/apps/academy/dashboard.vue
Normal file
@@ -0,0 +1,253 @@
|
||||
<script setup>
|
||||
import AcademyAssignmentProgress from '@/views/apps/academy/AcademyAssignmentProgress.vue'
|
||||
import AcademyCardPopularInstructors from '@/views/apps/academy/AcademyCardPopularInstructors.vue'
|
||||
import AcademyCardTopCourses from '@/views/apps/academy/AcademyCardTopCourses.vue'
|
||||
import AcademyCourseTable from '@/views/apps/academy/AcademyCourseTable.vue'
|
||||
import AcademyTopicYouAreInterested from '@/views/apps/academy/AcademyTopicYouAreInterested.vue'
|
||||
import AcademyUpcomingWebinar from '@/views/apps/academy/AcademyUpcomingWebinar.vue'
|
||||
import customCheck from '@images/svg/Check.svg'
|
||||
import customLaptop from '@images/svg/laptop.svg'
|
||||
import customLightbulb from '@images/svg/lightbulb.svg'
|
||||
|
||||
const donutChartColors = {
|
||||
donut: {
|
||||
series1: '#22A95E',
|
||||
series2: '#24B364',
|
||||
series3: '#56CA00',
|
||||
series4: '#53D28C',
|
||||
series5: '#7EDDA9',
|
||||
series6: '#A9E9C5',
|
||||
},
|
||||
}
|
||||
|
||||
const timeSpendingChartConfig = {
|
||||
chart: {
|
||||
height: 157,
|
||||
width: 130,
|
||||
parentHeightOffset: 0,
|
||||
type: 'donut',
|
||||
},
|
||||
labels: [
|
||||
'36h',
|
||||
'56h',
|
||||
'16h',
|
||||
'32h',
|
||||
'56h',
|
||||
'16h',
|
||||
],
|
||||
colors: [
|
||||
donutChartColors.donut.series1,
|
||||
donutChartColors.donut.series2,
|
||||
donutChartColors.donut.series3,
|
||||
donutChartColors.donut.series4,
|
||||
donutChartColors.donut.series5,
|
||||
donutChartColors.donut.series6,
|
||||
],
|
||||
stroke: { width: 0 },
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
formatter(val) {
|
||||
return `${ Number.parseInt(val) }%`
|
||||
},
|
||||
},
|
||||
legend: { show: false },
|
||||
tooltip: { theme: false },
|
||||
grid: { padding: { top: 0 } },
|
||||
plotOptions: {
|
||||
pie: {
|
||||
donut: {
|
||||
size: '75%',
|
||||
labels: {
|
||||
show: true,
|
||||
value: {
|
||||
fontSize: '1.125rem',
|
||||
color: 'rgba(var(--v-theme-on-background), var(--v-high-emphasis-opacity))',
|
||||
fontWeight: 500,
|
||||
offsetY: -15,
|
||||
formatter(val) {
|
||||
return `${ Number.parseInt(val) }%`
|
||||
},
|
||||
},
|
||||
name: { offsetY: 20 },
|
||||
total: {
|
||||
show: true,
|
||||
fontSize: '15px',
|
||||
label: 'Total',
|
||||
color: 'rgba(var(--v-theme-on-background), var(--v-disabled-opacity))',
|
||||
formatter() {
|
||||
return '231h'
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const timeSpendingChartSeries = [
|
||||
23,
|
||||
35,
|
||||
10,
|
||||
20,
|
||||
35,
|
||||
23,
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<VRow class="py-6">
|
||||
<!-- 👉 Welcome -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="8"
|
||||
:class="$vuetify.display.mdAndUp ? 'border-e' : 'border-b'"
|
||||
>
|
||||
<div class="pe-3">
|
||||
<h5 class="text-h5 mb-2">
|
||||
Welcome back,<span class="text-h4"> Felecia 👋🏻 </span>
|
||||
</h5>
|
||||
|
||||
<div
|
||||
class="text-wrap text-body-1"
|
||||
style="max-inline-size: 360px;"
|
||||
>
|
||||
Your progress this week is Awesome. let's keep it up
|
||||
and get a lot of points reward!
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-space-between flex-wrap gap-4 flex-column flex-md-row mt-4">
|
||||
<div
|
||||
v-for="{ title, value, icon, color } in [
|
||||
{ title: 'Hours Spent', value: '34h', icon: customLaptop, color: 'primary' },
|
||||
{ title: 'Test Results', value: '82%', icon: customLightbulb, color: 'info' },
|
||||
{ title: 'Course Completed', value: '14', icon: customCheck, color: 'warning' },
|
||||
]"
|
||||
:key="title"
|
||||
>
|
||||
<div class="d-flex align-center">
|
||||
<VAvatar
|
||||
variant="tonal"
|
||||
:color="color"
|
||||
rounded
|
||||
size="54"
|
||||
class="text-primary me-4"
|
||||
>
|
||||
<VIcon
|
||||
:icon="icon"
|
||||
size="38"
|
||||
/>
|
||||
</VAvatar>
|
||||
<div>
|
||||
<h6 class="text-h6 text-medium-emphasis">
|
||||
{{ title }}
|
||||
</h6>
|
||||
<h4
|
||||
class="text-h4"
|
||||
:class="`text-${color}`"
|
||||
>
|
||||
{{ value }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Time Spending -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<div class="d-flex flex-column ps-3">
|
||||
<h5 class="text-h5 mb-1 text-no-wrap">
|
||||
Time Spending
|
||||
</h5>
|
||||
<div class="text-body-1 mb-7">
|
||||
Weekly Report
|
||||
</div>
|
||||
<h4 class="text-h4 mb-2">
|
||||
231<span class="text-medium-emphasis">h</span> 14<span class="text-medium-emphasis">m</span>
|
||||
</h4>
|
||||
<div>
|
||||
<VChip
|
||||
color="success"
|
||||
label
|
||||
size="small"
|
||||
>
|
||||
+18.4%
|
||||
</VChip>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<VueApexCharts
|
||||
type="donut"
|
||||
height="150"
|
||||
width="150"
|
||||
:options="timeSpendingChartConfig"
|
||||
:series="timeSpendingChartSeries"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<VRow class="match-height">
|
||||
<!-- 👉 Topics you are interested in -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="8"
|
||||
>
|
||||
<!-- 👉 Topic You are Interested in -->
|
||||
<AcademyTopicYouAreInterested />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Popular Instructors -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
sm="6"
|
||||
>
|
||||
<AcademyCardPopularInstructors />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Academy Top Courses -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
sm="6"
|
||||
>
|
||||
<AcademyCardTopCourses />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Academy Upcoming Webinar -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
sm="6"
|
||||
>
|
||||
<AcademyUpcomingWebinar />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Academy Assignment Progress -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
sm="6"
|
||||
>
|
||||
<AcademyAssignmentProgress />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Academy Course Table -->
|
||||
<VCol>
|
||||
<AcademyCourseTable />
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/libs/apex-chart.scss";
|
||||
</style>
|
||||
234
resources/js/pages/apps/academy/my-course.vue
Normal file
234
resources/js/pages/apps/academy/my-course.vue
Normal file
@@ -0,0 +1,234 @@
|
||||
<script setup>
|
||||
import { VideoPlayer } from '@videojs-player/vue'
|
||||
import AcademyMyCourses from '@/views/apps/academy/AcademyMyCourses.vue'
|
||||
import boyAppAcademy from '@images/illustrations/boy-app-academy.png'
|
||||
import girlAppAcademy from '@images/illustrations/girl-app-academy.png'
|
||||
import academyCourseIllustration1 from '@images/pages/academy-course-illustration1.png'
|
||||
import academyCourseIllustration2Dark from '@images/pages/academy-course-illustration2-dark.png'
|
||||
import academyCourseIllustration2Light from '@images/pages/academy-course-illustration2-light.png'
|
||||
import guitarCoursePoster from '@images/pages/guitar-course-poster.png'
|
||||
import singingCoursePoster from '@images/pages/singing-course-poster.png'
|
||||
|
||||
const academyCourseIllustration2 = useGenerateImageVariant(academyCourseIllustration2Light, academyCourseIllustration2Dark)
|
||||
const searchQuery = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<VCard class="mb-6">
|
||||
<VCardText class="py-12 position-relative">
|
||||
<div
|
||||
class="d-flex flex-column gap-y-4 mx-auto"
|
||||
:class="$vuetify.display.mdAndUp ? 'w-50' : $vuetify.display.xs ? 'w-100' : 'w-75'"
|
||||
>
|
||||
<h4
|
||||
class="text-h4 text-center text-wrap mx-auto"
|
||||
:class="$vuetify.display.mdAndUp ? 'w-75' : 'w-100'"
|
||||
>
|
||||
Education, talents, and career
|
||||
opportunities. <span class="text-primary text-no-wrap"> All in one place.</span>
|
||||
</h4>
|
||||
<p class="text-center text-wrap text-body-1 mx-auto mb-0">
|
||||
Grow your skill with the most reliable online courses and certifications in marketing, information technology, programming, and data science.
|
||||
</p>
|
||||
<div class="d-flex justify-center align-center gap-4 flex-wrap">
|
||||
<div
|
||||
class="flex-grow-1"
|
||||
style="max-inline-size: 350px;"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="searchQuery"
|
||||
placeholder="Find your course"
|
||||
/>
|
||||
</div>
|
||||
<VBtn
|
||||
color="primary"
|
||||
density="comfortable"
|
||||
icon="tabler-search"
|
||||
class="rounded"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
:src="academyCourseIllustration1"
|
||||
class="illustration1 d-none d-md-block flip-in-rtl"
|
||||
height="180"
|
||||
>
|
||||
<img
|
||||
:src="academyCourseIllustration2"
|
||||
class="illustration2 d-none d-md-block"
|
||||
height="100"
|
||||
>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<AcademyMyCourses :search-query="searchQuery" />
|
||||
|
||||
<div class="mb-6">
|
||||
<VRow>
|
||||
<VCol
|
||||
v-for="{ title, btnText, color, description, image } in [
|
||||
{ title: 'Earn a Certificate', description: 'Get the right professional certificate program for you.', btnText: 'View Programs', color: 'primary', image: boyAppAcademy },
|
||||
{ title: 'Best Rated Courses', description: 'Enroll now in the most popular and best rated courses.', btnText: 'View Courses', color: 'error', image: girlAppAcademy },
|
||||
]"
|
||||
:key="title"
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VCard
|
||||
|
||||
flat
|
||||
:color="`rgba(var(--v-theme-${color}), var(--v-selected-opacity))`"
|
||||
>
|
||||
<VCardText>
|
||||
<div class="d-flex justify-space-between gap-4 flex-column-reverse flex-sm-row">
|
||||
<div class="text-center text-sm-start">
|
||||
<h5
|
||||
class="text-h5 mb-1"
|
||||
:class="`text-${color}`"
|
||||
>
|
||||
<div class="d-flex justify-space-between gap-4 flex-column-reverse flex-sm-row">
|
||||
<div class="text-center text-sm-start">
|
||||
<h5
|
||||
class="text-h5 mb-1"
|
||||
:class="`text-${color}`"
|
||||
>
|
||||
{{ title }}
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</h5>
|
||||
<p
|
||||
class="text-body-1 mx-auto"
|
||||
style="max-inline-size: 300px;"
|
||||
>
|
||||
{{ description }}
|
||||
</p>
|
||||
<VBtn :color="color">
|
||||
{{ btnText }}
|
||||
</VBtn>
|
||||
</div>
|
||||
|
||||
<div class="align-self-center">
|
||||
<div class="align-self-center">
|
||||
<img
|
||||
:src="image"
|
||||
height="127"
|
||||
class="flip-in-rtl"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
|
||||
<VCard>
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<div class="d-flex flex-column align-center gap-y-4 h-100 justify-center">
|
||||
<VAvatar
|
||||
variant="tonal"
|
||||
size="52"
|
||||
rounded
|
||||
color="primary"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-gift"
|
||||
size="36"
|
||||
/>
|
||||
</VAvatar>
|
||||
<h4 class="text-h4 font-weight-medium">
|
||||
Today's Free Courses
|
||||
</h4>
|
||||
<p class="text-body-1 text-center mb-0">
|
||||
We offers 284 Free Online courses from top tutors and companies to help you start or advance your career skills. Learn online for free and fast today!
|
||||
</p>
|
||||
<VBtn>Get Premium Courses</VBtn>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
sm="6"
|
||||
>
|
||||
<VCard
|
||||
flat
|
||||
border
|
||||
>
|
||||
<div class="px-2 pt-2">
|
||||
<VideoPlayer
|
||||
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4"
|
||||
:poster="guitarCoursePoster"
|
||||
controls
|
||||
plays-inline
|
||||
:height="$vuetify.display.mdAndUp ? 200 : 150"
|
||||
class="w-100 rounded"
|
||||
/>
|
||||
</div>
|
||||
<VCardText>
|
||||
<h5 class="text-h5 mb-2">
|
||||
Your First Singing Lesson
|
||||
</h5>
|
||||
<p class="text-body-1 mb-0">
|
||||
In the same way as any other artistic domain, singing lends itself perfectly to self-teaching.
|
||||
</p>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
sm="6"
|
||||
>
|
||||
<VCard
|
||||
flat
|
||||
border
|
||||
>
|
||||
<div class="px-2 pt-2">
|
||||
<VideoPlayer
|
||||
src="https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-576p.mp4"
|
||||
:poster="singingCoursePoster"
|
||||
controls
|
||||
plays-inline
|
||||
:height="$vuetify.display.mdAndUp ? 200 : 150"
|
||||
class="w-100 rounded"
|
||||
/>
|
||||
</div>
|
||||
<VCardText>
|
||||
<h5 class="text-h5 mb-2">
|
||||
Guitar for Beginners
|
||||
</h5>
|
||||
<p class="text-body-1 mb-0">
|
||||
The Fender Acoustic Guitar is best choice for beginners and professionals.
|
||||
</p>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "video.js/dist/video-js.css";
|
||||
|
||||
.illustration1 {
|
||||
position: absolute;
|
||||
inset-block-end: 0;
|
||||
inset-inline-end: 0;
|
||||
}
|
||||
|
||||
.illustration2 {
|
||||
position: absolute;
|
||||
inset-block-start: 2rem;
|
||||
inset-inline-start: 2.5rem;
|
||||
}
|
||||
</style>
|
||||
190
resources/js/pages/apps/calendar.vue
Normal file
190
resources/js/pages/apps/calendar.vue
Normal file
@@ -0,0 +1,190 @@
|
||||
<script setup>
|
||||
import FullCalendar from '@fullcalendar/vue3'
|
||||
import {
|
||||
blankEvent,
|
||||
useCalendar,
|
||||
} from '@/views/apps/calendar/useCalendar'
|
||||
import { useCalendarStore } from '@/views/apps/calendar/useCalendarStore'
|
||||
|
||||
// Components
|
||||
import CalendarEventHandler from '@/views/apps/calendar/CalendarEventHandler.vue'
|
||||
|
||||
// 👉 Store
|
||||
const store = useCalendarStore()
|
||||
|
||||
// 👉 Event
|
||||
const event = ref(structuredClone(blankEvent))
|
||||
const isEventHandlerSidebarActive = ref(false)
|
||||
|
||||
watch(isEventHandlerSidebarActive, val => {
|
||||
if (!val)
|
||||
event.value = structuredClone(blankEvent)
|
||||
})
|
||||
|
||||
const { isLeftSidebarOpen } = useResponsiveLeftSidebar()
|
||||
|
||||
// 👉 useCalendar
|
||||
const { refCalendar, calendarOptions, addEvent, updateEvent, removeEvent, jumpToDate } = useCalendar(event, isEventHandlerSidebarActive, isLeftSidebarOpen)
|
||||
|
||||
// SECTION Sidebar
|
||||
|
||||
// 👉 Check all
|
||||
const checkAll = computed({
|
||||
|
||||
/*GET: Return boolean `true` => if length of options matches length of selected filters => Length matches when all events are selected
|
||||
SET: If value is `true` => then add all available options in selected filters => Select All
|
||||
Else if => all filters are selected (by checking length of both array) => Empty Selected array => Deselect All
|
||||
*/
|
||||
get: () => store.selectedCalendars.length === store.availableCalendars.length,
|
||||
set: val => {
|
||||
if (val)
|
||||
store.selectedCalendars = store.availableCalendars.map(i => i.label)
|
||||
else if (store.selectedCalendars.length === store.availableCalendars.length)
|
||||
store.selectedCalendars = []
|
||||
},
|
||||
})
|
||||
|
||||
const jumpToDateFn = date => {
|
||||
jumpToDate(date)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<VCard>
|
||||
<!-- `z-index: 0` Allows overlapping vertical nav on calendar -->
|
||||
<VLayout style="z-index: 0;">
|
||||
<!-- 👉 Navigation drawer -->
|
||||
<VNavigationDrawer
|
||||
v-model="isLeftSidebarOpen"
|
||||
data-allow-mismatch
|
||||
width="292"
|
||||
absolute
|
||||
touchless
|
||||
location="start"
|
||||
class="calendar-add-event-drawer"
|
||||
:temporary="$vuetify.display.mdAndDown"
|
||||
>
|
||||
<div style="margin: 1.5rem;">
|
||||
<VBtn
|
||||
block
|
||||
prepend-icon="tabler-plus"
|
||||
@click="isEventHandlerSidebarActive = true"
|
||||
>
|
||||
Add event
|
||||
</VBtn>
|
||||
</div>
|
||||
|
||||
<VDivider />
|
||||
|
||||
<div class="d-flex align-center justify-center pa-2">
|
||||
<AppDateTimePicker
|
||||
id="calendar-date-picker"
|
||||
:model-value="new Date().toJSON().slice(0, 10)"
|
||||
:config="{ inline: true }"
|
||||
class="calendar-date-picker"
|
||||
@update:model-value="jumpToDateFn"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<VDivider />
|
||||
<div class="pa-6">
|
||||
<h6 class="text-lg font-weight-medium mb-4">
|
||||
Event Filters
|
||||
</h6>
|
||||
|
||||
<div class="d-flex flex-column calendars-checkbox">
|
||||
<VCheckbox
|
||||
id="check-all-events"
|
||||
v-model="checkAll"
|
||||
label="View all"
|
||||
/>
|
||||
<VCheckbox
|
||||
v-for="(calendar, index) in store.availableCalendars"
|
||||
:id="`${index}`"
|
||||
:key="calendar.label"
|
||||
v-model="store.selectedCalendars"
|
||||
:value="calendar.label"
|
||||
:color="calendar.color"
|
||||
:label="calendar.label"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</VNavigationDrawer>
|
||||
|
||||
<VMain>
|
||||
<VCard flat>
|
||||
<FullCalendar
|
||||
ref="refCalendar"
|
||||
:options="calendarOptions"
|
||||
/>
|
||||
</VCard>
|
||||
</VMain>
|
||||
</VLayout>
|
||||
</VCard>
|
||||
<CalendarEventHandler
|
||||
v-model:is-drawer-open="isEventHandlerSidebarActive"
|
||||
:event="event"
|
||||
@add-event="addEvent"
|
||||
@update-event="updateEvent"
|
||||
@remove-event="removeEvent"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/libs/full-calendar";
|
||||
|
||||
.calendars-checkbox {
|
||||
.v-label {
|
||||
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
|
||||
opacity: var(--v-high-emphasis-opacity);
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-add-event-drawer {
|
||||
&.v-navigation-drawer:not(.v-navigation-drawer--temporary) {
|
||||
border-end-start-radius: 0.375rem;
|
||||
border-start-start-radius: 0.375rem;
|
||||
}
|
||||
|
||||
&.v-navigation-drawer--temporary:not(.v-navigation-drawer--active) {
|
||||
transform: translateX(-110%) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-date-picker {
|
||||
display: none;
|
||||
|
||||
+.flatpickr-input {
|
||||
+.flatpickr-calendar.inline {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
|
||||
.flatpickr-months {
|
||||
border-block-end: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& ~ .flatpickr-calendar .flatpickr-weekdays {
|
||||
margin-block: 0 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1279px) {
|
||||
.calendar-add-event-drawer {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-layout {
|
||||
overflow: visible !important;
|
||||
|
||||
.v-card {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1576
resources/js/pages/apps/chat.vue
Normal file
1576
resources/js/pages/apps/chat.vue
Normal file
File diff suppressed because it is too large
Load Diff
192
resources/js/pages/apps/components/ChatInput.vue
Normal file
192
resources/js/pages/apps/components/ChatInput.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<script setup>
|
||||
import { ref, nextTick } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
msg: String,
|
||||
isInputDisabled: Boolean,
|
||||
activeModelColor: String,
|
||||
selectedAttachments: Array,
|
||||
showSuggestions: Boolean,
|
||||
suggestions: Array,
|
||||
selectedSuggestionIndex: Number,
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:msg',
|
||||
'send',
|
||||
'compositionstart',
|
||||
'compositionend',
|
||||
'keydown',
|
||||
'remove-attachment',
|
||||
'select-suggestion',
|
||||
]);
|
||||
|
||||
const inputRef = ref();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="input-wrapper">
|
||||
<div
|
||||
class="chat-input-container rounded-3xl pa-3 mb-2"
|
||||
:class="{ 'pt-0': selectedAttachments.length === 0 }"
|
||||
>
|
||||
<div
|
||||
v-if="selectedAttachments.length > 0"
|
||||
class="d-flex flex-wrap gap-2 mt-3 mb-2 attachment-chip-container"
|
||||
>
|
||||
<VChip
|
||||
v-for="(file, index) in selectedAttachments"
|
||||
:key="index"
|
||||
closable
|
||||
@click:close="emit('remove-attachment', index)"
|
||||
color="grey-lighten-3"
|
||||
class="align-center attachment-chip"
|
||||
>
|
||||
<VIcon start :icon="'tabler-file'" size="small"></VIcon>
|
||||
<span class="attachment-name">{{ file.name }}</span>
|
||||
<span class="attachment-size">({{ (file.size / (1024 * 1024)).toFixed(1) }} MB)</span>
|
||||
</VChip>
|
||||
</div>
|
||||
|
||||
<div class="position-relative">
|
||||
<VTextarea
|
||||
ref="inputRef"
|
||||
:model-value="msg"
|
||||
auto-grow
|
||||
rows="1"
|
||||
row-height="20"
|
||||
:disabled="isInputDisabled"
|
||||
hide-details
|
||||
variant="plain"
|
||||
persistent-placeholder
|
||||
density="comfortable"
|
||||
class="chat-textarea"
|
||||
placeholder="Type your message..."
|
||||
no-resize
|
||||
@update:model-value="emit('update:msg', $event)"
|
||||
@keydown="emit('keydown', $event)"
|
||||
@compositionstart="emit('compositionstart')"
|
||||
@compositionend="emit('compositionend')"
|
||||
></VTextarea>
|
||||
|
||||
<div v-if="showSuggestions" class="suggestions-dropdown rounded pa-2">
|
||||
<div class="suggestions-header text-caption text-grey-darken-1 mb-1 px-2">Suggestions</div>
|
||||
<div
|
||||
v-for="(suggestion, index) in suggestions"
|
||||
:key="index"
|
||||
class="suggestion-item pa-2 rounded d-flex align-center"
|
||||
:class="{ 'suggestion-selected': selectedSuggestionIndex === index }"
|
||||
@click="emit('select-suggestion', suggestion)"
|
||||
@mouseenter="emit('update:selectedSuggestionIndex', index)"
|
||||
>
|
||||
<VIcon icon="tabler-search" size="16" class="me-2 text-grey-darken-1" />
|
||||
<span class="suggestion-text">{{ suggestion }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-center justify-end mt-1">
|
||||
<VBtn
|
||||
icon
|
||||
size="large"
|
||||
:color="activeModelColor"
|
||||
@click="emit('send')"
|
||||
:disabled="isInputDisabled || !msg.trim()"
|
||||
>
|
||||
<VIcon icon="tabler-send" />
|
||||
</VBtn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.attachment-chip-container {
|
||||
overflow-x: auto;
|
||||
padding-bottom: 4px;
|
||||
margin-right: -8px;
|
||||
margin-left: -8px;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.attachment-chip-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.attachment-chip {
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.attachment-name {
|
||||
max-width: 120px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.attachment-size {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.suggestions-dropdown {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgb(var(--v-theme-surface));
|
||||
box-shadow: 0 4px 16px rgba(var(--v-theme-on-surface), 0.15);
|
||||
border: 1px solid rgba(var(--v-theme-on-surface), 0.1);
|
||||
z-index: 15;
|
||||
margin-bottom: 8px;
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
animation: suggestions-fade-in 0.2s ease-out;
|
||||
}
|
||||
|
||||
.suggestions-header {
|
||||
font-weight: 500;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.suggestion-item {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
margin-bottom: 2px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.suggestion-item:hover,
|
||||
.suggestion-item.suggestion-selected {
|
||||
background: rgba(var(--v-theme-primary), 0.08);
|
||||
border-color: rgba(var(--v-theme-primary), 0.2);
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.suggestion-text {
|
||||
font-size: 0.875rem;
|
||||
color: rgb(var(--v-theme-on-surface));
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@keyframes suggestions-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(4px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
54
resources/js/pages/apps/components/FormQuestion.vue
Normal file
54
resources/js/pages/apps/components/FormQuestion.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
form: Object,
|
||||
activeModelColor: String,
|
||||
});
|
||||
const emit = defineEmits(["select"]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="form-container">
|
||||
<div class="form-title font-weight-bold mb-2">
|
||||
{{ form.title }}
|
||||
</div>
|
||||
<div class="form-question mb-3">
|
||||
{{ form.question }}
|
||||
</div>
|
||||
<div class="form-options d-flex gap-2">
|
||||
<VBtn
|
||||
v-for="(option, optIndex) in form.options"
|
||||
:key="optIndex"
|
||||
:color="activeModelColor"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
class="form-option-btn"
|
||||
@click="emit('select', option)"
|
||||
>
|
||||
{{ option }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.form-title {
|
||||
font-size: 1.1rem;
|
||||
color: rgb(var(--v-theme-on-surface));
|
||||
}
|
||||
|
||||
.form-question {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.form-options .form-option-btn {
|
||||
margin-bottom: 8px;
|
||||
border-radius: 8px;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.form-options .form-option-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
107
resources/js/pages/apps/components/MessageItem.vue
Normal file
107
resources/js/pages/apps/components/MessageItem.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useTheme } from 'vuetify';
|
||||
import { themes } from '@/plugins/vuetify/theme';
|
||||
|
||||
const props = defineProps({
|
||||
message: Object,
|
||||
activeModelColor: String,
|
||||
activeModelBgColor: String,
|
||||
models: Array,
|
||||
selectedModelIdentifier: String,
|
||||
});
|
||||
|
||||
const getFileIcon = (fileType) => {
|
||||
if (fileType.startsWith('image/')) return 'tabler-photo';
|
||||
if (fileType.includes('pdf')) return 'tabler-file-type-pdf';
|
||||
if (fileType.includes('word')) return 'tabler-file-type-doc';
|
||||
if (fileType.includes('text')) return 'tabler-file-text';
|
||||
return 'tabler-file';
|
||||
};
|
||||
|
||||
const getFileSize = (size) => {
|
||||
if (size < 1024) return `${size} B`;
|
||||
if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
|
||||
return `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li
|
||||
class="my-3 d-flex"
|
||||
:class="[
|
||||
message.sender === 'user' ? 'justify-end' : 'justify-start',
|
||||
message.isAnimating ? 'is-animating' : '',
|
||||
message.sender === 'user' ? 'user-message' : 'bot-message',
|
||||
message.isSystemMessage ? 'system-message' : '',
|
||||
]"
|
||||
>
|
||||
<template v-if="message.sender !== 'user'">
|
||||
<VAvatar
|
||||
size="32"
|
||||
:color="message.isSystemMessage ? 'grey' : activeModelColor"
|
||||
variant="tonal"
|
||||
class="me-2 message-avatar"
|
||||
>
|
||||
<VIcon
|
||||
:icon="message.isSystemMessage ? 'tabler-info-circle' : 'tabler-robot'"
|
||||
/>
|
||||
</VAvatar>
|
||||
</template>
|
||||
|
||||
<div
|
||||
class="pa-3 rounded-lg message-bubble"
|
||||
:class="[
|
||||
message.sender === 'user'
|
||||
? 'bg-primary text-white'
|
||||
: message.isSystemMessage
|
||||
? 'bg-grey-lighten-3 text-grey-darken-3'
|
||||
: `bg-${activeModelBgColor} text-white`,
|
||||
message.isAnimating ? 'animate-message' : '',
|
||||
]"
|
||||
>
|
||||
<div v-if="!message.form && !message.multiForm && !message.type">
|
||||
{{ message.text }}
|
||||
</div>
|
||||
|
||||
<div v-if="message.isAttachment && message.files" class="attachment-preview mt-2">
|
||||
<div
|
||||
v-for="(file, fileIndex) in message.files"
|
||||
:key="fileIndex"
|
||||
class="d-flex align-center mb-1"
|
||||
>
|
||||
<VIcon :icon="getFileIcon(file.type)" size="small" class="me-1"></VIcon>
|
||||
<span class="text-caption">{{ file.name }} ({{ getFileSize(file.size) }})</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="message.sender === 'user'">
|
||||
<VAvatar size="32" color="grey" variant="tonal" class="ms-2 message-avatar">
|
||||
<VIcon icon="tabler-user" />
|
||||
</VAvatar>
|
||||
</template>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.message-avatar {
|
||||
opacity: 0;
|
||||
animation: fade-in 0.3s ease forwards;
|
||||
animation-delay: 0.15s;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-preview {
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
</style>
|
||||
63
resources/js/pages/apps/components/ModelSelector.vue
Normal file
63
resources/js/pages/apps/components/ModelSelector.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
models: Array,
|
||||
selectedModelIdentifier: String,
|
||||
activeModelColor: String,
|
||||
showModelMenu: Boolean,
|
||||
});
|
||||
const emit = defineEmits(['update:model', 'update:menu']);
|
||||
|
||||
const selectedModelName = computed(() => {
|
||||
const model = props.models.find((m) => m.identifier === props.selectedModelIdentifier);
|
||||
return model ? model.name : 'Select Model';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VMenu :model-value="showModelMenu" location="top" @update:model-value="emit('update:menu', $event)">
|
||||
<template v-slot:activator="{ props: menuProps }">
|
||||
<VBtn
|
||||
v-bind="menuProps"
|
||||
:color="activeModelColor"
|
||||
variant="text"
|
||||
class="text-none model-select-btn"
|
||||
density="comfortable"
|
||||
>
|
||||
<span class="d-none d-sm-block">{{ selectedModelName }}</span>
|
||||
<span class="d-block d-sm-none">Model</span>
|
||||
<VIcon icon="tabler-chevron-down" class="ms-1" />
|
||||
</VBtn>
|
||||
</template>
|
||||
|
||||
<VList density="compact" max-height="300">
|
||||
<VListItem
|
||||
v-for="model in models"
|
||||
:key="model.identifier"
|
||||
:value="model.identifier"
|
||||
@click="emit('update:model', model.identifier); emit('update:menu', false)"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<div
|
||||
class="model-color-indicator"
|
||||
:style="`background-color: var(--v-theme-${model.color})`"
|
||||
></div>
|
||||
</template>
|
||||
<VListItemTitle>{{ model.name }}</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.model-select-btn {
|
||||
min-width: auto;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.model-color-indicator {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
</style>
|
||||
99
resources/js/pages/apps/components/MultiStepForm.vue
Normal file
99
resources/js/pages/apps/components/MultiStepForm.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
step: Object,
|
||||
stepNumber: Number,
|
||||
totalSteps: Number,
|
||||
activeModelColor: String,
|
||||
activeModelBgColor: String,
|
||||
});
|
||||
const emit = defineEmits(["cancel", "select"]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="multi-form-container">
|
||||
<div class="multi-form-header mb-3">
|
||||
<div class="d-flex justify-space-between align-center mb-2">
|
||||
<div class="form-title font-weight-bold">
|
||||
{{ step.formTitle }}
|
||||
</div>
|
||||
<div class="d-flex align-center">
|
||||
<VChip :color="activeModelColor" size="small" variant="tonal">
|
||||
{{ stepNumber }}/{{ totalSteps }}
|
||||
</VChip>
|
||||
<VBtn icon size="x-small" @click="emit('cancel')" class="ms-2">
|
||||
<VIcon icon="tabler-x" />
|
||||
</VBtn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<VProgressLinear
|
||||
:model-value="(stepNumber / totalSteps) * 100"
|
||||
:color="activeModelColor"
|
||||
height="4"
|
||||
rounded
|
||||
class="mb-3"
|
||||
></VProgressLinear>
|
||||
</div>
|
||||
|
||||
<div class="step-title font-weight-medium mb-2">
|
||||
{{ step.title }}
|
||||
</div>
|
||||
|
||||
<div class="form-question mb-3">
|
||||
{{ step.question }}
|
||||
</div>
|
||||
|
||||
<div v-if="step.type === 'options'" class="form-options">
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<VBtn
|
||||
v-for="(option, optIndex) in step.options"
|
||||
:key="optIndex"
|
||||
:color="activeModelColor"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
class="form-option-btn"
|
||||
@click="emit('select', { id: step.id, value: option })"
|
||||
>
|
||||
{{ option }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.multi-form-container {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.multi-form-header {
|
||||
border-bottom: 1px solid rgba(var(--v-theme-on-surface), 0.1);
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
font-size: 1.1rem;
|
||||
color: rgb(var(--v-theme-on-surface));
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-size: 0.95rem;
|
||||
color: rgba(var(--v-theme-on-surface), 0.8);
|
||||
}
|
||||
|
||||
.form-question {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.form-options .form-option-btn {
|
||||
margin-bottom: 8px;
|
||||
border-radius: 8px;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.form-options .form-option-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
48
resources/js/pages/apps/components/QuickReplies.vue
Normal file
48
resources/js/pages/apps/components/QuickReplies.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
replies: Array,
|
||||
activeModelColor: String,
|
||||
isInputCentered: Boolean,
|
||||
});
|
||||
const emit = defineEmits(['select']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="quick-replies-container">
|
||||
<div
|
||||
v-for="(reply, index) in replies"
|
||||
:key="index"
|
||||
class="quick-reply-item"
|
||||
>
|
||||
<VBtn
|
||||
:variant="isInputCentered ? 'tonal' : 'outlined'"
|
||||
:color="activeModelColor"
|
||||
size="small"
|
||||
@click="emit('select', reply)"
|
||||
class="w-100"
|
||||
>
|
||||
{{ reply }}
|
||||
</VBtn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.quick-replies-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
grid-gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.quick-replies-container {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.quick-reply-item {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
113
resources/js/pages/apps/components/SuggestionsDropdown.vue
Normal file
113
resources/js/pages/apps/components/SuggestionsDropdown.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
suggestions: Array,
|
||||
selectedIndex: Number,
|
||||
activeModelColor: String,
|
||||
});
|
||||
const emit = defineEmits(["select", "hover"]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="suggestions-dropdown rounded pa-2">
|
||||
<div class="suggestions-header text-caption text-grey-darken-1 mb-1 px-2">
|
||||
Suggestions
|
||||
</div>
|
||||
<div
|
||||
v-for="(suggestion, index) in suggestions"
|
||||
:key="index"
|
||||
class="suggestion-item pa-2 rounded d-flex align-center"
|
||||
:class="{ 'suggestion-selected': selectedIndex === index }"
|
||||
@click="emit('select', suggestion)"
|
||||
@mouseenter="emit('hover', index)"
|
||||
>
|
||||
<VIcon icon="tabler-search" size="16" class="me-2 text-grey-darken-1" />
|
||||
<span class="suggestion-text">{{ suggestion }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.suggestions-dropdown {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgb(var(--v-theme-surface));
|
||||
box-shadow: 0 4px 16px rgba(var(--v-theme-on-surface), 0.15);
|
||||
border: 1px solid rgba(var(--v-theme-on-surface), 0.1);
|
||||
z-index: 15;
|
||||
margin-bottom: 8px;
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
animation: suggestions-fade-in 0.2s ease-out;
|
||||
}
|
||||
|
||||
.suggestions-header {
|
||||
font-weight: 500;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.suggestion-item {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
margin-bottom: 2px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.suggestion-item:hover,
|
||||
.suggestion-item.suggestion-selected {
|
||||
background: rgba(var(--v-theme-primary), 0.08);
|
||||
border-color: rgba(var(--v-theme-primary), 0.2);
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.suggestion-text {
|
||||
font-size: 0.875rem;
|
||||
color: rgb(var(--v-theme-on-surface));
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@keyframes suggestions-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(4px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.suggestions-dropdown {
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.suggestion-text {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.suggestions-dropdown::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.suggestions-dropdown::-webkit-scrollbar-track {
|
||||
background: rgba(var(--v-theme-on-surface), 0.05);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.suggestions-dropdown::-webkit-scrollbar-thumb {
|
||||
background: rgba(var(--v-theme-on-surface), 0.2);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.suggestions-dropdown::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(var(--v-theme-on-surface), 0.3);
|
||||
}
|
||||
</style>
|
||||
72
resources/js/pages/apps/components/TypingIndicator.vue
Normal file
72
resources/js/pages/apps/components/TypingIndicator.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
activeModelColor: String,
|
||||
activeModelBgColor: String,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li class="my-3 d-flex justify-start typing-indicator-container">
|
||||
<VAvatar
|
||||
size="32"
|
||||
:color="activeModelColor"
|
||||
variant="tonal"
|
||||
class="me-2 message-avatar"
|
||||
>
|
||||
<VIcon icon="tabler-robot" />
|
||||
</VAvatar>
|
||||
|
||||
<div
|
||||
class="pa-3 rounded-lg message-bubble"
|
||||
:class="`bg-${activeModelBgColor} text-${activeModelColor}`"
|
||||
>
|
||||
<div class="bot-typing-indicator">
|
||||
<span class="dot"></span>
|
||||
<span class="dot"></span>
|
||||
<span class="dot"></span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.typing-indicator-container {
|
||||
animation: fade-in 0.3s ease forwards;
|
||||
}
|
||||
|
||||
.bot-typing-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.bot-typing-indicator .dot {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(var(--v-theme-on-surface), 0.6);
|
||||
margin: 0 2px;
|
||||
animation: pulse 1.5s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.bot-typing-indicator .dot:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.bot-typing-indicator .dot:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
transform: scale(0.7);
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
78
resources/js/pages/apps/components/UploadModal.vue
Normal file
78
resources/js/pages/apps/components/UploadModal.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
modelValue: Boolean,
|
||||
files: Array,
|
||||
fileError: String,
|
||||
activeModelColor: String,
|
||||
});
|
||||
const emit = defineEmits(["update:modelValue", "triggerInput", "remove", "done"]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDialog :model-value="modelValue" max-width="500" @update:modelValue="emit('update:modelValue', $event)">
|
||||
<VCard>
|
||||
<VCardTitle class="pb-2 pt-4">
|
||||
<span class="text-h5">Upload Files</span>
|
||||
</VCardTitle>
|
||||
|
||||
<VCardText>
|
||||
<div class="text-subtitle-2 mb-4">
|
||||
Select files to upload (Max 5 files, 5MB each)
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="upload-area pa-6 border-dashed rounded d-flex flex-column align-center justify-center"
|
||||
@click="emit('triggerInput')"
|
||||
@dragover.prevent
|
||||
@drop.prevent="$event => emit('triggerInput', $event)"
|
||||
>
|
||||
<VIcon :icon="'tabler-upload'" :size="36" :color="activeModelColor" class="mb-3" />
|
||||
<div class="text-body-1 mb-1">Drag files here or click to upload</div>
|
||||
<div class="text-caption text-grey">Supported formats: Images, PDF, DOC, DOCX, TXT</div>
|
||||
</div>
|
||||
|
||||
<div v-if="fileError" class="text-error mt-2">{{ fileError }}</div>
|
||||
|
||||
<div v-if="files.length > 0">
|
||||
<div class="text-subtitle-2 mt-4 mb-2">Selected files:</div>
|
||||
<VList density="compact" class="bg-grey-lighten-5 rounded">
|
||||
<VListItem v-for="(file, index) in files" :key="index">
|
||||
<template v-slot:prepend>
|
||||
<VIcon :icon="file.type.startsWith('image/') ? 'tabler-photo' : file.type.includes('pdf') ? 'tabler-file-type-pdf' : file.type.includes('word') ? 'tabler-file-type-doc' : file.type.includes('text') ? 'tabler-file-text' : 'tabler-file'" />
|
||||
</template>
|
||||
<VListItemTitle>{{ file.name }}</VListItemTitle>
|
||||
<VListItemSubtitle>{{ (file.size / (1024 * 1024)).toFixed(1) }} MB</VListItemSubtitle>
|
||||
<template v-slot:append>
|
||||
<VBtn icon size="small" variant="text" @click="emit('remove', index)">
|
||||
<VIcon icon="tabler-x" />
|
||||
</VBtn>
|
||||
</template>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</div>
|
||||
</VCardText>
|
||||
|
||||
<VCardActions class="pb-4 px-4">
|
||||
<VSpacer></VSpacer>
|
||||
<VBtn color="grey-darken-1" variant="text" @click="emit('update:modelValue', false)">Cancel</VBtn>
|
||||
<VBtn :color="activeModelColor" variant="tonal" @click="emit('done')" :disabled="files.length === 0">Done</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.border-dashed {
|
||||
border: 2px dashed rgba(var(--v-theme-on-surface), 0.2);
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.upload-area:hover {
|
||||
background-color: rgba(var(--v-theme-on-surface), 0.05);
|
||||
border-color: rgba(var(--v-theme-on-surface), 0.4);
|
||||
}
|
||||
</style>
|
||||
120
resources/js/pages/apps/ecommerce/customer/details/[id].vue
Normal file
120
resources/js/pages/apps/ecommerce/customer/details/[id].vue
Normal 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>
|
||||
173
resources/js/pages/apps/ecommerce/customer/list/index.vue
Normal file
173
resources/js/pages/apps/ecommerce/customer/list/index.vue
Normal 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>
|
||||
573
resources/js/pages/apps/ecommerce/manage-review.vue
Normal file
573
resources/js/pages/apps/ecommerce/manage-review.vue
Normal 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>
|
||||
535
resources/js/pages/apps/ecommerce/order/details/[id].vue
Normal file
535
resources/js/pages/apps/ecommerce/order/details/[id].vue
Normal 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>
|
||||
386
resources/js/pages/apps/ecommerce/order/list/index.vue
Normal file
386
resources/js/pages/apps/ecommerce/order/list/index.vue
Normal 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>
|
||||
570
resources/js/pages/apps/ecommerce/product/add/index.vue
Normal file
570
resources/js/pages/apps/ecommerce/product/add/index.vue
Normal 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>
|
||||
255
resources/js/pages/apps/ecommerce/product/category-list.vue
Normal file
255
resources/js/pages/apps/ecommerce/product/category-list.vue
Normal 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>
|
||||
495
resources/js/pages/apps/ecommerce/product/list/index.vue
Normal file
495
resources/js/pages/apps/ecommerce/product/list/index.vue
Normal 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>
|
||||
393
resources/js/pages/apps/ecommerce/referrals.vue
Normal file
393
resources/js/pages/apps/ecommerce/referrals.vue
Normal 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 friend’s 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>
|
||||
107
resources/js/pages/apps/ecommerce/settings.vue
Normal file
107
resources/js/pages/apps/ecommerce/settings.vue
Normal 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>
|
||||
557
resources/js/pages/apps/email/index.vue
Normal file
557
resources/js/pages/apps/email/index.vue
Normal file
@@ -0,0 +1,557 @@
|
||||
<script setup>
|
||||
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
|
||||
import ComposeDialog from '@/views/apps/email/ComposeDialog.vue'
|
||||
import EmailLeftSidebarContent from '@/views/apps/email/EmailLeftSidebarContent.vue'
|
||||
import EmailView from '@/views/apps/email/EmailView.vue'
|
||||
import { useEmail } from '@/views/apps/email/useEmail'
|
||||
|
||||
definePage({ meta: { layoutWrapperClasses: 'layout-content-height-fixed' } })
|
||||
|
||||
const { isLeftSidebarOpen } = useResponsiveLeftSidebar()
|
||||
|
||||
// Composables
|
||||
const route = useRoute()
|
||||
const { labels, resolveLabelColor, emailMoveToFolderActions, shallShowMoveToActionFor, moveSelectedEmailTo, updateEmails, updateEmailLabels } = useEmail()
|
||||
|
||||
// Compose dialog
|
||||
const isComposeDialogVisible = ref(false)
|
||||
|
||||
// Ref
|
||||
const q = ref('')
|
||||
|
||||
// Email Selection
|
||||
|
||||
// ------------------------------------------------
|
||||
const selectedEmails = ref([])
|
||||
|
||||
const {
|
||||
data: emailData,
|
||||
execute: fetchEmails,
|
||||
} = await useApi(createUrl('/apps/email', {
|
||||
query: {
|
||||
q,
|
||||
filter: () => 'filter' in route.params ? route.params.filter : undefined,
|
||||
label: () => 'label' in route.params ? route.params.label : undefined,
|
||||
},
|
||||
}))
|
||||
|
||||
const emails = computed(() => emailData.value.emails)
|
||||
const emailsMeta = computed(() => emailData.value.emailsMeta)
|
||||
|
||||
const toggleSelectedEmail = emailId => {
|
||||
const emailIndex = selectedEmails.value.indexOf(emailId)
|
||||
if (emailIndex === -1)
|
||||
selectedEmails.value.push(emailId)
|
||||
else
|
||||
selectedEmails.value.splice(emailIndex, 1)
|
||||
}
|
||||
|
||||
const selectAllEmailCheckbox = computed(() => emails.value.length && emails.value.length === selectedEmails.value.length)
|
||||
const isSelectAllEmailCheckboxIndeterminate = computed(() => Boolean(selectedEmails.value.length) && emails.value.length !== selectedEmails.value.length)
|
||||
|
||||
const isAllMarkRead = computed(() => {
|
||||
return selectedEmails.value.every(emailId => emails.value.find(email => email.id === emailId)?.isRead)
|
||||
})
|
||||
|
||||
const selectAllCheckboxUpdate = () => {
|
||||
selectedEmails.value = !selectAllEmailCheckbox.value ? emails.value.map(email => email.id) : []
|
||||
}
|
||||
|
||||
// Email View
|
||||
const openedEmail = ref(null)
|
||||
|
||||
const emailViewMeta = computed(() => {
|
||||
const returnValue = {
|
||||
hasNextEmail: false,
|
||||
hasPreviousEmail: false,
|
||||
}
|
||||
|
||||
if (openedEmail.value) {
|
||||
const openedEmailIndex = emails.value.findIndex(e => e.id === openedEmail.value?.id)
|
||||
|
||||
returnValue.hasNextEmail = !!emails.value[openedEmailIndex + 1]
|
||||
returnValue.hasPreviousEmail = !!emails.value[openedEmailIndex - 1]
|
||||
}
|
||||
|
||||
return returnValue
|
||||
})
|
||||
|
||||
const refreshOpenedEmail = async () => {
|
||||
await fetchEmails()
|
||||
if (openedEmail.value)
|
||||
openedEmail.value = emails.value.find(e => e.id === openedEmail.value?.id)
|
||||
}
|
||||
|
||||
/*ℹ️ You can optimize it so it doesn't fetch emails on each action.
|
||||
Currently, if you just star the email, two API calls will get fired.
|
||||
1. star the email
|
||||
2. Fetch all latest emails
|
||||
|
||||
You can limit this to single API call by:
|
||||
- making API to star the email
|
||||
- modify the state (set that email's isStarred property to true/false) in the store instead of making API for fetching emails
|
||||
|
||||
😊 For simplicity of the code and possible of modification, we kept it simple.
|
||||
*/
|
||||
const handleActionClick = async (action, emailIds = selectedEmails.value) => {
|
||||
selectedEmails.value = []
|
||||
selectedEmails.value = []
|
||||
if (!emailIds.length)
|
||||
return
|
||||
if (action === 'trash')
|
||||
await updateEmails(emailIds, { isDeleted: true })
|
||||
else if (action === 'spam')
|
||||
await updateEmails(emailIds, { folder: 'spam' })
|
||||
else if (action === 'unread')
|
||||
await updateEmails(emailIds, { isRead: false })
|
||||
else if (action === 'read')
|
||||
await updateEmails(emailIds, { isRead: true })
|
||||
else if (action === 'star')
|
||||
await updateEmails(emailIds, { isStarred: true })
|
||||
else if (action === 'unstar')
|
||||
await updateEmails(emailIds, { isStarred: false })
|
||||
if (openedEmail.value)
|
||||
refreshOpenedEmail()
|
||||
else
|
||||
await fetchEmails()
|
||||
}
|
||||
|
||||
const handleMoveMailsTo = async action => {
|
||||
await moveSelectedEmailTo(action, selectedEmails.value)
|
||||
await fetchEmails()
|
||||
}
|
||||
|
||||
const handleEmailLabels = async labelTitle => {
|
||||
await updateEmailLabels(selectedEmails.value, labelTitle)
|
||||
await fetchEmails()
|
||||
}
|
||||
|
||||
const changeOpenedEmail = dir => {
|
||||
if (!openedEmail.value)
|
||||
return
|
||||
const openedEmailIndex = emails.value.findIndex(e => e.id === openedEmail.value?.id)
|
||||
const newEmailIndex = dir === 'previous' ? openedEmailIndex - 1 : openedEmailIndex + 1
|
||||
|
||||
openedEmail.value = emails.value[newEmailIndex]
|
||||
}
|
||||
|
||||
const openEmail = async email => {
|
||||
openedEmail.value = email
|
||||
await handleActionClick('read', [email.id])
|
||||
}
|
||||
|
||||
watch(() => route.params, () => {
|
||||
selectedEmails.value = []
|
||||
}, { deep: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VLayout
|
||||
style=" z-index: 0;min-block-size: 100%;"
|
||||
class="email-app-layout"
|
||||
>
|
||||
<VNavigationDrawer
|
||||
v-model="isLeftSidebarOpen"
|
||||
data-allow-mismatch
|
||||
absolute
|
||||
touchless
|
||||
location="start"
|
||||
:temporary="$vuetify.display.mdAndDown"
|
||||
>
|
||||
<EmailLeftSidebarContent
|
||||
:emails-meta="emailsMeta"
|
||||
@toggle-compose-dialog-visibility="isComposeDialogVisible = !isComposeDialogVisible"
|
||||
/>
|
||||
</VNavigationDrawer>
|
||||
<EmailView
|
||||
:email="openedEmail"
|
||||
:email-meta="emailViewMeta"
|
||||
@refresh="refreshOpenedEmail"
|
||||
@navigated="changeOpenedEmail"
|
||||
@close="openedEmail = null"
|
||||
@trash="handleActionClick('trash', openedEmail ? [openedEmail.id] : [])"
|
||||
@unread="handleActionClick('unread', openedEmail ? [openedEmail.id] : [])"
|
||||
@star="handleActionClick('star', openedEmail ? [openedEmail.id] : [])"
|
||||
@unstar="handleActionClick('unstar', openedEmail ? [openedEmail.id] : [])"
|
||||
/>
|
||||
<VMain>
|
||||
<VCard
|
||||
flat
|
||||
class="email-content-list h-100 d-flex flex-column"
|
||||
>
|
||||
<div class="d-flex align-center">
|
||||
<IconBtn
|
||||
class="d-lg-none ms-3"
|
||||
@click="isLeftSidebarOpen = true"
|
||||
>
|
||||
<VIcon icon="tabler-menu-2" />
|
||||
</IconBtn>
|
||||
|
||||
<!-- 👉 Search -->
|
||||
<VTextField
|
||||
v-model="q"
|
||||
density="default"
|
||||
class="email-search px-sm-2 flex-grow-1 py-1"
|
||||
placeholder="Search mail"
|
||||
>
|
||||
<template #prepend-inner>
|
||||
<VIcon
|
||||
icon="tabler-search"
|
||||
size="24"
|
||||
class="me-1 text-medium-emphasis"
|
||||
/>
|
||||
</template>
|
||||
</VTextField>
|
||||
</div>
|
||||
<VDivider />
|
||||
<!-- 👉 Action bar -->
|
||||
<div class="py-2 px-4 d-flex align-center d-flex gap-x-1">
|
||||
<!-- TODO: Make checkbox primary on indeterminate state -->
|
||||
<VCheckbox
|
||||
:model-value="selectAllEmailCheckbox"
|
||||
:indeterminate="isSelectAllEmailCheckboxIndeterminate"
|
||||
class="d-flex"
|
||||
@update:model-value="selectAllCheckboxUpdate"
|
||||
/>
|
||||
<div
|
||||
class="w-100 d-flex align-center action-bar-actions gap-x-1"
|
||||
:style="{
|
||||
visibility:
|
||||
isSelectAllEmailCheckboxIndeterminate || selectAllEmailCheckbox
|
||||
? undefined
|
||||
: 'hidden',
|
||||
}"
|
||||
>
|
||||
<!-- Trash -->
|
||||
<IconBtn
|
||||
v-show="('filter' in route.params ? route.params.filter !== 'trashed' : true)"
|
||||
@click="handleActionClick('trash')"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-trash"
|
||||
size="22"
|
||||
/>
|
||||
<VTooltip
|
||||
activator="parent"
|
||||
location="top"
|
||||
>
|
||||
Delete Mail
|
||||
</VTooltip>
|
||||
</IconBtn>
|
||||
<!-- Mark unread/read -->
|
||||
<IconBtn @click="isAllMarkRead ? handleActionClick('unread') : handleActionClick('read') ">
|
||||
<VIcon
|
||||
:icon="isAllMarkRead ? 'tabler-mail' : 'tabler-mail-opened'"
|
||||
size="22"
|
||||
/>
|
||||
<VTooltip
|
||||
activator="parent"
|
||||
location="top"
|
||||
>
|
||||
{{ isAllMarkRead ? 'Mark as Unread' : 'Mark as Read' }}
|
||||
</VTooltip>
|
||||
</IconBtn>
|
||||
<!-- Move to folder -->
|
||||
<IconBtn>
|
||||
<VIcon
|
||||
icon="tabler-folder"
|
||||
size="22"
|
||||
/>
|
||||
<VTooltip
|
||||
activator="parent"
|
||||
location="top"
|
||||
>
|
||||
Folder
|
||||
</VTooltip>
|
||||
<VMenu activator="parent">
|
||||
<VList density="compact">
|
||||
<template
|
||||
v-for="moveTo in emailMoveToFolderActions"
|
||||
:key="moveTo.title"
|
||||
>
|
||||
<VListItem
|
||||
:class="shallShowMoveToActionFor(moveTo.action) ? 'd-flex' : 'd-none'"
|
||||
href="#"
|
||||
class="items-center"
|
||||
@click="handleMoveMailsTo(moveTo.action)"
|
||||
>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
:icon="moveTo.icon"
|
||||
class="me-2"
|
||||
size="20"
|
||||
/>
|
||||
</template>
|
||||
<VListItemTitle class="text-capitalize">
|
||||
{{ moveTo.action }}
|
||||
</VListItemTitle>
|
||||
</VListItem>
|
||||
</template>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</IconBtn>
|
||||
<!-- Update labels -->
|
||||
<IconBtn>
|
||||
<VIcon
|
||||
icon="tabler-tag"
|
||||
size="22"
|
||||
/>
|
||||
<VTooltip
|
||||
activator="parent"
|
||||
location="top"
|
||||
>
|
||||
Label
|
||||
</VTooltip>
|
||||
<VMenu activator="parent">
|
||||
<VList density="compact">
|
||||
<VListItem
|
||||
v-for="label in labels"
|
||||
:key="label.title"
|
||||
href="#"
|
||||
@click="handleEmailLabels(label.title)"
|
||||
>
|
||||
<template #prepend>
|
||||
<VBadge
|
||||
inline
|
||||
:color="resolveLabelColor(label.title)"
|
||||
dot
|
||||
/>
|
||||
</template>
|
||||
<VListItemTitle class="ms-2 text-capitalize">
|
||||
{{ label.title }}
|
||||
</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</IconBtn>
|
||||
</div>
|
||||
<VSpacer />
|
||||
<IconBtn @click="fetchEmails">
|
||||
<VIcon
|
||||
icon="tabler-refresh"
|
||||
size="22"
|
||||
/>
|
||||
</IconBtn>
|
||||
<IconBtn>
|
||||
<VIcon
|
||||
icon="tabler-dots-vertical"
|
||||
size="22"
|
||||
/>
|
||||
</IconBtn>
|
||||
</div>
|
||||
<VDivider />
|
||||
<!-- 👉 Emails list -->
|
||||
<PerfectScrollbar
|
||||
tag="ul"
|
||||
:options="{ wheelPropagation: false }"
|
||||
class="email-list"
|
||||
>
|
||||
<li
|
||||
v-for="email in emails"
|
||||
v-show="emails?.length"
|
||||
:key="email.id"
|
||||
class="email-item d-flex align-center pa-4 gap-2 cursor-pointer"
|
||||
:class="[{ 'email-read': email.isRead }]"
|
||||
@click="openEmail(email)"
|
||||
>
|
||||
<VCheckbox
|
||||
:model-value="selectedEmails.includes(email.id)"
|
||||
class="flex-shrink-0"
|
||||
@update:model-value="toggleSelectedEmail(email.id)"
|
||||
@click.stop
|
||||
/>
|
||||
<IconBtn
|
||||
:color="email.isStarred ? 'warning' : 'default'"
|
||||
@click.stop=" handleActionClick(email.isStarred ? 'unstar' : 'star', [email.id])"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-star"
|
||||
size="22"
|
||||
/>
|
||||
</IconBtn>
|
||||
<VAvatar size="32">
|
||||
<VImg
|
||||
:src="email.from.avatar"
|
||||
:alt="email.from.name"
|
||||
/>
|
||||
</VAvatar>
|
||||
<h6 class="text-h6">
|
||||
{{ email.from.name }}
|
||||
</h6>
|
||||
<span class="text-body-2 truncate">{{ email.subject }}</span>
|
||||
|
||||
<VSpacer />
|
||||
|
||||
<!-- 👉 Email meta -->
|
||||
<div
|
||||
class="email-meta align-center gap-2"
|
||||
:class="$vuetify.display.xs ? 'd-none' : ''"
|
||||
>
|
||||
<VIcon
|
||||
v-for="label in email.labels"
|
||||
:key="label"
|
||||
icon="tabler-circle-filled"
|
||||
size="10"
|
||||
:color="resolveLabelColor(label)"
|
||||
/>
|
||||
|
||||
<span class="text-sm text-disabled">
|
||||
{{ formatDateToMonthShort(email.time) }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- 👉 Email actions -->
|
||||
<div class="email-actions d-none">
|
||||
<IconBtn @click.stop="handleActionClick('trash', [email.id])">
|
||||
<VIcon
|
||||
icon="tabler-trash"
|
||||
size="22"
|
||||
/>
|
||||
<VTooltip
|
||||
activator="parent"
|
||||
location="top"
|
||||
>
|
||||
Delete Mail
|
||||
</VTooltip>
|
||||
</IconBtn>
|
||||
<IconBtn
|
||||
class="mx-2"
|
||||
@click.stop=" handleActionClick(email.isRead ? 'unread' : 'read', [email.id])"
|
||||
>
|
||||
<VIcon
|
||||
:icon="email.isRead ? 'tabler-mail' : 'tabler-mail-opened'"
|
||||
size="22"
|
||||
/>
|
||||
<VTooltip
|
||||
activator="parent"
|
||||
location="top"
|
||||
>
|
||||
{{ email.isRead ? 'Mark as Unread' : 'Mark as Read' }}
|
||||
</VTooltip>
|
||||
</IconBtn>
|
||||
<IconBtn @click.stop="handleActionClick('spam', [email.id])">
|
||||
<VIcon
|
||||
icon="tabler-info-circle"
|
||||
size="22"
|
||||
/>
|
||||
<VTooltip
|
||||
activator="parent"
|
||||
location="top"
|
||||
>
|
||||
Move to Spam
|
||||
</VTooltip>
|
||||
</IconBtn>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
v-show="!emails.length"
|
||||
class="py-4 px-5 text-center"
|
||||
>
|
||||
<span class="text-high-emphasis">No items found.</span>
|
||||
</li>
|
||||
</PerfectScrollbar>
|
||||
</VCard>
|
||||
<ComposeDialog
|
||||
v-show="isComposeDialogVisible"
|
||||
@close="isComposeDialogVisible = false"
|
||||
/>
|
||||
</VMain>
|
||||
</VLayout>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@styles/variables/vuetify";
|
||||
@use "@core-scss/base/mixins";
|
||||
|
||||
// ℹ️ Remove border. Using variant plain cause UI issue, caret isn't align in center
|
||||
.email-search {
|
||||
.v-field__outline {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.v-field__field {
|
||||
.v-field__input {
|
||||
font-size: 0.9375rem !important;
|
||||
line-height: 1.375rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.email-app-layout {
|
||||
border-radius: vuetify.$card-border-radius;
|
||||
|
||||
@include mixins.elevation(vuetify.$card-elevation);
|
||||
|
||||
$sel-email-app-layout: &;
|
||||
|
||||
@at-root {
|
||||
.skin--bordered {
|
||||
@include mixins.bordered-skin($sel-email-app-layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.email-content-list {
|
||||
border-end-start-radius: 0;
|
||||
border-start-start-radius: 0;
|
||||
}
|
||||
|
||||
.email-list {
|
||||
white-space: nowrap;
|
||||
|
||||
.email-item {
|
||||
block-size: 4.375rem;
|
||||
transition: all 0.2s ease-in-out;
|
||||
will-change: transform, box-shadow;
|
||||
|
||||
&.email-read {
|
||||
background-color: rgba(var(--v-theme-on-surface), var(--v-hover-opacity));
|
||||
}
|
||||
|
||||
& + .email-item {
|
||||
border-block-start: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||||
}
|
||||
}
|
||||
|
||||
.email-item .email-meta {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.email-item:hover {
|
||||
transform: translateY(-2px);
|
||||
|
||||
@include mixins.elevation(4);
|
||||
|
||||
// ℹ️ Don't show actions on hover on mobile & tablet devices
|
||||
@media screen and (min-width: 1280px) {
|
||||
.email-actions {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.email-meta {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
+ .email-item {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.email-actions {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.email-compose-dialog {
|
||||
position: absolute;
|
||||
inset-block-end: 0;
|
||||
inset-inline-end: 0;
|
||||
min-inline-size: 100%;
|
||||
|
||||
@media only screen and (min-width: 800px) {
|
||||
min-inline-size: 712px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
171
resources/js/pages/apps/invoice/add/index.vue
Normal file
171
resources/js/pages/apps/invoice/add/index.vue
Normal file
@@ -0,0 +1,171 @@
|
||||
<script setup>
|
||||
import InvoiceEditable from '@/views/apps/invoice/InvoiceEditable.vue'
|
||||
import InvoiceSendInvoiceDrawer from '@/views/apps/invoice/InvoiceSendInvoiceDrawer.vue'
|
||||
|
||||
// 👉 Default Blank Data
|
||||
const invoiceData = ref({
|
||||
invoice: {
|
||||
id: 5037,
|
||||
issuedDate: '',
|
||||
service: '',
|
||||
total: 0,
|
||||
avatar: '',
|
||||
invoiceStatus: '',
|
||||
dueDate: '',
|
||||
balance: 0,
|
||||
client: {
|
||||
address: '112, Lorem Ipsum, Florida',
|
||||
company: 'Greeva Inc',
|
||||
companyEmail: 'johndoe@greeva.com',
|
||||
contact: '+1 123 3452 12',
|
||||
country: 'USA',
|
||||
name: 'John Doe',
|
||||
},
|
||||
},
|
||||
paymentDetails: {
|
||||
totalDue: '$12,110.55',
|
||||
bankName: 'American Bank',
|
||||
country: 'United States',
|
||||
iban: 'ETD95476213',
|
||||
swiftCode: 'BR91905',
|
||||
},
|
||||
purchasedProducts: [{
|
||||
title: '',
|
||||
cost: 0,
|
||||
hours: 0,
|
||||
description: '',
|
||||
}],
|
||||
note: '',
|
||||
paymentMethod: '',
|
||||
salesperson: '',
|
||||
thanksNote: '',
|
||||
})
|
||||
|
||||
const paymentTerms = ref(true)
|
||||
const clientNotes = ref(false)
|
||||
const paymentStub = ref(false)
|
||||
const selectedPaymentMethod = ref('Bank Account')
|
||||
|
||||
const paymentMethods = [
|
||||
'Bank Account',
|
||||
'PayPal',
|
||||
'UPI Transfer',
|
||||
]
|
||||
|
||||
const isSendPaymentSidebarVisible = ref(false)
|
||||
|
||||
const addProduct = value => {
|
||||
invoiceData.value?.purchasedProducts.push(value)
|
||||
}
|
||||
|
||||
const removeProduct = id => {
|
||||
invoiceData.value?.purchasedProducts.splice(id, 1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<!-- 👉 InvoiceEditable -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="9"
|
||||
>
|
||||
<InvoiceEditable
|
||||
:data="invoiceData"
|
||||
@push="addProduct"
|
||||
@remove="removeProduct"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Right Column: Invoice Action -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="3"
|
||||
>
|
||||
<VCard class="mb-8">
|
||||
<VCardText>
|
||||
<!-- 👉 Send Invoice -->
|
||||
<VBtn
|
||||
block
|
||||
prepend-icon="tabler-send"
|
||||
class="mb-4"
|
||||
@click="isSendPaymentSidebarVisible = true"
|
||||
>
|
||||
Send Invoice
|
||||
</VBtn>
|
||||
|
||||
<!-- 👉 Preview -->
|
||||
<VBtn
|
||||
block
|
||||
color="secondary"
|
||||
variant="tonal"
|
||||
class="mb-4"
|
||||
:to="{ name: 'apps-invoice-preview-id', params: { id: '5036' } }"
|
||||
>
|
||||
Preview
|
||||
</VBtn>
|
||||
|
||||
<!-- 👉 Save -->
|
||||
<VBtn
|
||||
block
|
||||
color="secondary"
|
||||
variant="tonal"
|
||||
>
|
||||
Save
|
||||
</VBtn>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- 👉 Select payment method -->
|
||||
<AppSelect
|
||||
id="payment-method"
|
||||
v-model="selectedPaymentMethod"
|
||||
:items="paymentMethods"
|
||||
label="Accept Payment Via"
|
||||
class="mb-6"
|
||||
/>
|
||||
|
||||
<!-- 👉 Payment Terms -->
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<VLabel for="payment-terms">
|
||||
Payment Terms
|
||||
</VLabel>
|
||||
<div>
|
||||
<VSwitch
|
||||
id="payment-terms"
|
||||
v-model="paymentTerms"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Client Notes -->
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<VLabel for="client-notes">
|
||||
Client Notes
|
||||
</VLabel>
|
||||
<div>
|
||||
<VSwitch
|
||||
id="client-notes"
|
||||
v-model="clientNotes"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Payment Stub -->
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<VLabel for="payment-stub">
|
||||
Payment Stub
|
||||
</VLabel>
|
||||
<div>
|
||||
<VSwitch
|
||||
id="payment-stub"
|
||||
v-model="paymentStub"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<!-- 👉 Send Invoice Sidebar -->
|
||||
<InvoiceSendInvoiceDrawer v-model:is-drawer-open="isSendPaymentSidebarVisible" />
|
||||
</template>
|
||||
177
resources/js/pages/apps/invoice/edit/[id].vue
Normal file
177
resources/js/pages/apps/invoice/edit/[id].vue
Normal file
@@ -0,0 +1,177 @@
|
||||
<script setup>
|
||||
import InvoiceAddPaymentDrawer from '@/views/apps/invoice/InvoiceAddPaymentDrawer.vue'
|
||||
import InvoiceEditable from '@/views/apps/invoice/InvoiceEditable.vue'
|
||||
import InvoiceSendInvoiceDrawer from '@/views/apps/invoice/InvoiceSendInvoiceDrawer.vue'
|
||||
|
||||
const invoiceData = ref()
|
||||
const route = useRoute('apps-invoice-edit-id')
|
||||
const { data: invoiceDetails } = await useApi(`/apps/invoice/${ route.params.id }`)
|
||||
if (invoiceDetails.value) {
|
||||
invoiceData.value = {
|
||||
invoice: invoiceDetails.value.invoice,
|
||||
paymentDetails: invoiceDetails.value.paymentDetails,
|
||||
purchasedProducts: [{
|
||||
title: 'App Design',
|
||||
cost: 24,
|
||||
hours: 2,
|
||||
description: 'Designed UI kit & app pages.',
|
||||
}],
|
||||
note: 'It was a pleasure working with you and your team. We hope you will keep us in mind for future freelance projects. Thank You!',
|
||||
paymentMethod: 'Bank Account',
|
||||
salesperson: 'Tom Cook',
|
||||
thanksNote: 'Thanks for your business',
|
||||
}
|
||||
}
|
||||
|
||||
const addProduct = value => {
|
||||
invoiceData.value?.purchasedProducts.push(value)
|
||||
}
|
||||
|
||||
const removeProduct = id => {
|
||||
invoiceData.value?.purchasedProducts.splice(id, 1)
|
||||
}
|
||||
|
||||
const isSendSidebarActive = ref(false)
|
||||
const isAddPaymentSidebarActive = ref(false)
|
||||
const paymentTerms = ref(true)
|
||||
const clientNotes = ref(false)
|
||||
const paymentStub = ref(false)
|
||||
const selectedPaymentMethod = ref('Bank Account')
|
||||
|
||||
const paymentMethods = [
|
||||
'Bank Account',
|
||||
'PayPal',
|
||||
'UPI Transfer',
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow v-if="invoiceData && invoiceData?.invoice">
|
||||
<!-- 👉 InvoiceEditable -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="9"
|
||||
>
|
||||
<InvoiceEditable
|
||||
v-if="invoiceData?.invoice"
|
||||
:data="invoiceData"
|
||||
@push="addProduct"
|
||||
@remove="removeProduct"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Right Column: Invoice Action -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="3"
|
||||
>
|
||||
<VCard class="mb-8">
|
||||
<VCardText>
|
||||
<!-- 👉 Send Invoice Trigger button -->
|
||||
<VBtn
|
||||
block
|
||||
prepend-icon="tabler-send"
|
||||
class="mb-4"
|
||||
@click="isSendSidebarActive = true"
|
||||
>
|
||||
Send Invoice
|
||||
</VBtn>
|
||||
|
||||
<div class="d-flex flex-wrap gap-4">
|
||||
<!-- 👉 Preview button -->
|
||||
<VBtn
|
||||
color="secondary"
|
||||
variant="tonal"
|
||||
class="flex-grow-1"
|
||||
:to="{ name: 'apps-invoice-preview-id', params: { id: route.params.id } }"
|
||||
>
|
||||
Preview
|
||||
</VBtn>
|
||||
|
||||
<!-- 👉 Save button -->
|
||||
<VBtn
|
||||
color="secondary"
|
||||
variant="tonal"
|
||||
class="mb-4 flex-grow-1"
|
||||
>
|
||||
Save
|
||||
</VBtn>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Add Payment trigger button -->
|
||||
<VBtn
|
||||
block
|
||||
color="success"
|
||||
prepend-icon="tabler-currency-dollar"
|
||||
@click="isAddPaymentSidebarActive = true"
|
||||
>
|
||||
Add Payment
|
||||
</VBtn>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- 👉 Accept payment via -->
|
||||
<AppSelect
|
||||
id="payment-method"
|
||||
v-model="selectedPaymentMethod"
|
||||
:items="paymentMethods"
|
||||
label="Accept Payment Via"
|
||||
class="mb-4"
|
||||
/>
|
||||
|
||||
<!-- 👉 Payment Terms -->
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<VLabel for="payment-terms">
|
||||
Payment Terms
|
||||
</VLabel>
|
||||
<div>
|
||||
<VSwitch
|
||||
id="payment-terms"
|
||||
v-model="paymentTerms"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Client Notes -->
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<VLabel for="client-notes">
|
||||
Client Notes
|
||||
</VLabel>
|
||||
<div>
|
||||
<VSwitch
|
||||
id="client-notes"
|
||||
v-model="clientNotes"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Payment Stub -->
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<VLabel for="payment-stub">
|
||||
Payment Stub
|
||||
</VLabel>
|
||||
<div>
|
||||
<VSwitch
|
||||
id="payment-stub"
|
||||
v-model="paymentStub"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Invoice send drawer -->
|
||||
<InvoiceSendInvoiceDrawer v-model:is-drawer-open="isSendSidebarActive" />
|
||||
|
||||
<!-- 👉 Invoice add payment drawer -->
|
||||
<InvoiceAddPaymentDrawer v-model:is-drawer-open="isAddPaymentSidebarActive" />
|
||||
</VRow>
|
||||
|
||||
<section v-else>
|
||||
<VAlert
|
||||
type="error"
|
||||
variant="tonal"
|
||||
>
|
||||
Invoice with ID {{ route.params.id }} not found!
|
||||
</VAlert>
|
||||
</section>
|
||||
</template>
|
||||
442
resources/js/pages/apps/invoice/list/index.vue
Normal file
442
resources/js/pages/apps/invoice/list/index.vue
Normal file
@@ -0,0 +1,442 @@
|
||||
<script setup>
|
||||
const searchQuery = ref('')
|
||||
const selectedStatus = ref(null)
|
||||
const selectedRows = ref([])
|
||||
|
||||
// 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 widgetData = ref([
|
||||
{
|
||||
title: 'Clients',
|
||||
value: 24,
|
||||
icon: 'tabler-user',
|
||||
},
|
||||
{
|
||||
title: 'Invoices',
|
||||
value: 165,
|
||||
icon: 'tabler-file-invoice',
|
||||
},
|
||||
{
|
||||
title: 'Paid',
|
||||
value: '$2.46k',
|
||||
icon: 'tabler-checks',
|
||||
},
|
||||
{
|
||||
title: 'Unpaid',
|
||||
value: '$876',
|
||||
icon: 'tabler-circle-off',
|
||||
},
|
||||
])
|
||||
|
||||
// 👉 headers
|
||||
const headers = [
|
||||
{
|
||||
title: '#',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
key: 'status',
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
title: 'Client',
|
||||
key: 'client',
|
||||
},
|
||||
{
|
||||
title: 'Total',
|
||||
key: 'total',
|
||||
},
|
||||
{
|
||||
title: 'Issued Date',
|
||||
key: 'date',
|
||||
},
|
||||
{
|
||||
title: 'Balance',
|
||||
key: 'balance',
|
||||
},
|
||||
{
|
||||
title: 'Actions',
|
||||
key: 'actions',
|
||||
sortable: false,
|
||||
},
|
||||
]
|
||||
|
||||
const {
|
||||
data: invoiceData,
|
||||
execute: fetchInvoices,
|
||||
} = await useApi(createUrl('/apps/invoice', {
|
||||
query: {
|
||||
q: searchQuery,
|
||||
status: selectedStatus,
|
||||
itemsPerPage,
|
||||
page,
|
||||
sortBy,
|
||||
orderBy,
|
||||
},
|
||||
}))
|
||||
|
||||
const invoices = computed(() => invoiceData.value.invoices)
|
||||
const totalInvoices = computed(() => invoiceData.value.totalInvoices)
|
||||
|
||||
// 👉 Invoice balance variant resolver
|
||||
const resolveInvoiceBalanceVariant = (balance, total) => {
|
||||
if (balance === total)
|
||||
return {
|
||||
status: 'Unpaid',
|
||||
chip: { color: 'error' },
|
||||
}
|
||||
if (balance === 0)
|
||||
return {
|
||||
status: 'Paid',
|
||||
chip: { color: 'success' },
|
||||
}
|
||||
|
||||
return {
|
||||
status: balance,
|
||||
chip: { variant: 'text' },
|
||||
}
|
||||
}
|
||||
|
||||
const resolveInvoiceStatusVariantAndIcon = status => {
|
||||
if (status === 'Partial Payment')
|
||||
return {
|
||||
variant: 'warning',
|
||||
icon: 'tabler-chart-pie-2',
|
||||
}
|
||||
if (status === 'Paid')
|
||||
return {
|
||||
variant: 'success',
|
||||
icon: 'tabler-check',
|
||||
}
|
||||
if (status === 'Downloaded')
|
||||
return {
|
||||
variant: 'info',
|
||||
icon: 'tabler-arrow-down',
|
||||
}
|
||||
if (status === 'Draft')
|
||||
return {
|
||||
variant: 'primary',
|
||||
icon: 'tabler-folder',
|
||||
}
|
||||
if (status === 'Sent')
|
||||
return {
|
||||
variant: 'secondary',
|
||||
icon: 'tabler-mail',
|
||||
}
|
||||
if (status === 'Past Due')
|
||||
return {
|
||||
variant: 'error',
|
||||
icon: 'tabler-help',
|
||||
}
|
||||
|
||||
return {
|
||||
variant: 'secondary',
|
||||
icon: 'tabler-x',
|
||||
}
|
||||
}
|
||||
|
||||
const computedMoreList = computed(() => {
|
||||
return paramId => [
|
||||
{
|
||||
title: 'Download',
|
||||
value: 'download',
|
||||
prependIcon: 'tabler-download',
|
||||
},
|
||||
{
|
||||
title: 'Edit',
|
||||
value: 'edit',
|
||||
prependIcon: 'tabler-pencil',
|
||||
to: {
|
||||
name: 'apps-invoice-edit-id',
|
||||
params: { id: paramId },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Duplicate',
|
||||
value: 'duplicate',
|
||||
prependIcon: 'tabler-layers-intersect',
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
const deleteInvoice = async id => {
|
||||
await $api(`/apps/invoice/${ id }`, { method: 'DELETE' })
|
||||
|
||||
// Delete from selectedRows
|
||||
const index = selectedRows.value.findIndex(row => row === id)
|
||||
if (index !== -1)
|
||||
selectedRows.value.splice(index, 1)
|
||||
|
||||
// Refetch Invoices
|
||||
fetchInvoices()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section v-if="invoices">
|
||||
<!-- 👉 Invoice 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 align-center"
|
||||
: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>
|
||||
<span class="text-body-1 text-capitalize">{{ data.title }}</span>
|
||||
</div>
|
||||
|
||||
<VAvatar
|
||||
variant="tonal"
|
||||
rounded
|
||||
size="42"
|
||||
>
|
||||
<VIcon
|
||||
:icon="data.icon"
|
||||
size="26"
|
||||
color="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 id="invoice-list">
|
||||
<VCardText class="d-flex justify-space-between align-center flex-wrap gap-4">
|
||||
<div class="d-flex gap-4 align-center flex-wrap">
|
||||
<div class="d-flex align-center gap-2">
|
||||
<span>Show</span>
|
||||
<AppSelect
|
||||
:model-value="itemsPerPage"
|
||||
:items="[
|
||||
{ value: 10, title: '10' },
|
||||
{ value: 25, title: '25' },
|
||||
{ value: 50, title: '50' },
|
||||
{ value: 100, title: '100' },
|
||||
{ value: -1, title: 'All' },
|
||||
]"
|
||||
style="inline-size: 5.5rem;"
|
||||
@update:model-value="itemsPerPage = parseInt($event, 10)"
|
||||
/>
|
||||
</div>
|
||||
<!-- 👉 Create invoice -->
|
||||
<VBtn
|
||||
prepend-icon="tabler-plus"
|
||||
:to="{ name: 'apps-invoice-add' }"
|
||||
>
|
||||
Create invoice
|
||||
</VBtn>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-center flex-wrap gap-4">
|
||||
<!-- 👉 Search -->
|
||||
<div class="invoice-list-filter">
|
||||
<AppTextField
|
||||
v-model="searchQuery"
|
||||
placeholder="Search Invoice"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Select status -->
|
||||
<div class="invoice-list-filter">
|
||||
<AppSelect
|
||||
v-model="selectedStatus"
|
||||
placeholder="Invoice Status"
|
||||
clearable
|
||||
clear-icon="tabler-x"
|
||||
single-line
|
||||
:items="['Downloaded', 'Draft', 'Sent', 'Paid', 'Partial Payment', 'Past Due']"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</VCardText>
|
||||
<VDivider />
|
||||
|
||||
<!-- SECTION Datatable -->
|
||||
<VDataTableServer
|
||||
v-model:model-value="selectedRows"
|
||||
v-model:items-per-page="itemsPerPage"
|
||||
v-model:page="page"
|
||||
show-select
|
||||
:items-length="totalInvoices"
|
||||
:headers="headers"
|
||||
:items="invoices"
|
||||
item-value="id"
|
||||
class="text-no-wrap"
|
||||
@update:options="updateOptions"
|
||||
>
|
||||
<!-- id -->
|
||||
<template #item.id="{ item }">
|
||||
<RouterLink :to="{ name: 'apps-invoice-preview-id', params: { id: item.id } }">
|
||||
#{{ item.id }}
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
||||
<!-- status -->
|
||||
<template #item.status="{ item }">
|
||||
<VTooltip>
|
||||
<template #activator="{ props }">
|
||||
<VAvatar
|
||||
:size="28"
|
||||
v-bind="props"
|
||||
:color="resolveInvoiceStatusVariantAndIcon(item.invoiceStatus).variant"
|
||||
variant="tonal"
|
||||
>
|
||||
<VIcon
|
||||
:size="16"
|
||||
:icon="resolveInvoiceStatusVariantAndIcon(item.invoiceStatus).icon"
|
||||
/>
|
||||
</VAvatar>
|
||||
</template>
|
||||
<p class="mb-0">
|
||||
{{ item.invoiceStatus }}
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
Balance: {{ item.balance }}
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
Due date: {{ item.dueDate }}
|
||||
</p>
|
||||
</VTooltip>
|
||||
</template>
|
||||
|
||||
<!-- client -->
|
||||
<template #item.client="{ item }">
|
||||
<div class="d-flex align-center">
|
||||
<VAvatar
|
||||
size="34"
|
||||
:color="!item.avatar.length ? resolveInvoiceStatusVariantAndIcon(item.invoiceStatus).variant : undefined"
|
||||
:variant="!item.avatar.length ? 'tonal' : undefined"
|
||||
class="me-3"
|
||||
>
|
||||
<VImg
|
||||
v-if="item.avatar.length"
|
||||
:src="item.avatar"
|
||||
/>
|
||||
<span v-else>{{ avatarText(item.client.name) }}</span>
|
||||
</VAvatar>
|
||||
<div class="d-flex flex-column">
|
||||
<RouterLink
|
||||
:to="{ name: 'pages-user-profile-tab', params: { tab: 'profile' } }"
|
||||
class="text-link font-weight-medium"
|
||||
>
|
||||
{{ item.client.name }}
|
||||
</RouterLink>
|
||||
<span class="text-sm text-medium-emphasis">{{ item.client.companyEmail }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Total -->
|
||||
<template #item.total="{ item }">
|
||||
${{ item.total }}
|
||||
</template>
|
||||
|
||||
<!-- Date -->
|
||||
<template #item.date="{ item }">
|
||||
{{ item.issuedDate }}
|
||||
</template>
|
||||
|
||||
<!-- Balance -->
|
||||
<template #item.balance="{ item }">
|
||||
<VChip
|
||||
v-if="typeof ((resolveInvoiceBalanceVariant(item.balance, item.total)).status) === 'string'"
|
||||
:color="resolveInvoiceBalanceVariant(item.balance, item.total).chip.color"
|
||||
label
|
||||
size="x-small"
|
||||
>
|
||||
{{ (resolveInvoiceBalanceVariant(item.balance, item.total)).status }}
|
||||
</VChip>
|
||||
|
||||
<template v-else>
|
||||
<span class="text-base text-high-emphasis">
|
||||
{{ Number((resolveInvoiceBalanceVariant(item.balance, item.total)).status) > 0 ? `$${(resolveInvoiceBalanceVariant(item.balance, item.total)).status}` : `-$${Math.abs(Number((resolveInvoiceBalanceVariant(item.balance, item.total)).status))}` }}
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- Actions -->
|
||||
<template #item.actions="{ item }">
|
||||
<IconBtn @click="deleteInvoice(item.id)">
|
||||
<VIcon icon="tabler-trash" />
|
||||
</IconBtn>
|
||||
|
||||
<IconBtn :to="{ name: 'apps-invoice-preview-id', params: { id: item.id } }">
|
||||
<VIcon icon="tabler-eye" />
|
||||
</IconBtn>
|
||||
|
||||
<MoreBtn
|
||||
:menu-list="computedMoreList(item.id)"
|
||||
item-props
|
||||
color="undefined"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- pagination -->
|
||||
<template #bottom>
|
||||
<TablePagination
|
||||
v-model:page="page"
|
||||
:items-per-page="itemsPerPage"
|
||||
:total-items="totalInvoices"
|
||||
/>
|
||||
</template>
|
||||
</VDataTableServer>
|
||||
<!-- !SECTION -->
|
||||
</VCard>
|
||||
</section>
|
||||
<section v-else>
|
||||
<VCard>
|
||||
<VCardTitle>No Invoice Found</VCardTitle>
|
||||
</VCard>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
#invoice-list {
|
||||
.invoice-list-actions {
|
||||
inline-size: 8rem;
|
||||
}
|
||||
|
||||
.invoice-list-filter {
|
||||
inline-size: 12rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
452
resources/js/pages/apps/invoice/preview/[id].vue
Normal file
452
resources/js/pages/apps/invoice/preview/[id].vue
Normal file
@@ -0,0 +1,452 @@
|
||||
<script setup>
|
||||
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
|
||||
import { themeConfig } from '@themeConfig'
|
||||
import InvoiceAddPaymentDrawer from '@/views/apps/invoice/InvoiceAddPaymentDrawer.vue'
|
||||
import InvoiceSendInvoiceDrawer from '@/views/apps/invoice/InvoiceSendInvoiceDrawer.vue'
|
||||
|
||||
const route = useRoute('apps-invoice-preview-id')
|
||||
const isAddPaymentSidebarVisible = ref(false)
|
||||
const isSendPaymentSidebarVisible = ref(false)
|
||||
const { data: invoiceData } = await useApi(`/apps/invoice/${ Number(route.params.id) }`)
|
||||
const invoice = ref()
|
||||
const paymentDetails = ref()
|
||||
if (invoiceData.value) {
|
||||
invoice.value = invoiceData.value.invoice
|
||||
paymentDetails.value = invoiceData.value.paymentDetails
|
||||
}
|
||||
|
||||
const purchasedProducts = [
|
||||
{
|
||||
name: 'Premium Branding Package',
|
||||
description: 'Branding & Promotion',
|
||||
qty: 1,
|
||||
hours: 15,
|
||||
price: 32,
|
||||
},
|
||||
{
|
||||
name: 'SMM',
|
||||
description: 'Social media templates',
|
||||
qty: 1,
|
||||
hours: 14,
|
||||
price: 28,
|
||||
},
|
||||
{
|
||||
name: 'Web Design',
|
||||
description: 'Web designing package',
|
||||
qty: 1,
|
||||
hours: 12,
|
||||
price: 24,
|
||||
},
|
||||
{
|
||||
name: 'SEO',
|
||||
description: 'Search engine optimization',
|
||||
qty: 1,
|
||||
hours: 5,
|
||||
price: 22,
|
||||
},
|
||||
]
|
||||
|
||||
const printInvoice = () => {
|
||||
window.print()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section v-if="invoice && paymentDetails">
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="9"
|
||||
>
|
||||
<VCard class="invoice-preview-wrapper pa-6 pa-sm-12">
|
||||
<!-- SECTION Header -->
|
||||
<div class="invoice-header-preview d-flex flex-wrap justify-space-between flex-column flex-sm-row print-row bg-var-theme-background gap-6 rounded pa-6 mb-6">
|
||||
<!-- 👉 Left Content -->
|
||||
<div>
|
||||
<div class="d-flex align-center app-logo mb-6">
|
||||
<!-- 👉 Logo -->
|
||||
<VNodeRenderer :nodes="themeConfig.app.logo" />
|
||||
|
||||
<!-- 👉 Title -->
|
||||
<h6 class="app-logo-title">
|
||||
{{ themeConfig.app.title }}
|
||||
</h6>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Address -->
|
||||
<h6 class="text-h6 font-weight-regular">
|
||||
Office 149, 450 South Brand Brooklyn
|
||||
</h6>
|
||||
<h6 class="text-h6 font-weight-regular">
|
||||
San Diego County, CA 91905, USA
|
||||
</h6>
|
||||
<h6 class="text-h6 font-weight-regular">
|
||||
+1 (123) 456 7891, +44 (876) 543 2198
|
||||
</h6>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Right Content -->
|
||||
<div>
|
||||
<!-- 👉 Invoice ID -->
|
||||
<h6 class="font-weight-medium text-lg mb-6">
|
||||
Invoice #{{ invoice.id }}
|
||||
</h6>
|
||||
|
||||
<!-- 👉 Issue Date -->
|
||||
<h6 class="text-h6 font-weight-regular">
|
||||
<span>Date Issued: </span>
|
||||
<span>{{ new Date(invoice.issuedDate).toLocaleDateString('en-GB') }}</span>
|
||||
</h6>
|
||||
|
||||
<!-- 👉 Due Date -->
|
||||
<h6 class="text-h6 font-weight-regular">
|
||||
<span>Due Date: </span>
|
||||
<span>{{ new Date(invoice.dueDate).toLocaleDateString('en-GB') }}</span>
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !SECTION -->
|
||||
|
||||
<!-- 👉 Payment Details -->
|
||||
<VRow class="print-row mb-6">
|
||||
<VCol class="text-no-wrap">
|
||||
<h6 class="text-h6 mb-4">
|
||||
Invoice To:
|
||||
</h6>
|
||||
|
||||
<p class="mb-0">
|
||||
{{ invoice.client.name }}
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
{{ invoice.client.company }}
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
{{ invoice.client.address }}, {{ invoice.client.country }}
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
{{ invoice.client.contact }}
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
{{ invoice.client.companyEmail }}
|
||||
</p>
|
||||
</VCol>
|
||||
|
||||
<VCol class="text-no-wrap">
|
||||
<h6 class="text-h6 mb-4">
|
||||
Bill To:
|
||||
</h6>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="pe-4">
|
||||
Total Due:
|
||||
</td>
|
||||
<td>
|
||||
{{ paymentDetails.totalDue }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="pe-4">
|
||||
Bank Name:
|
||||
</td>
|
||||
<td>
|
||||
{{ paymentDetails.bankName }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="pe-4">
|
||||
Country:
|
||||
</td>
|
||||
<td>
|
||||
{{ paymentDetails.country }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="pe-4">
|
||||
IBAN:
|
||||
</td>
|
||||
<td>
|
||||
{{ paymentDetails.iban }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="pe-4">
|
||||
SWIFT Code:
|
||||
</td>
|
||||
<td>
|
||||
{{ paymentDetails.swiftCode }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<!-- 👉 invoice Table -->
|
||||
<VTable class="invoice-preview-table border text-high-emphasis overflow-hidden mb-6">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
ITEM
|
||||
</th>
|
||||
<th scope="col">
|
||||
DESCRIPTION
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="text-center"
|
||||
>
|
||||
HOURS
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="text-center"
|
||||
>
|
||||
QTY
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="text-center"
|
||||
>
|
||||
TOTAL
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="text-base">
|
||||
<tr
|
||||
v-for="item in purchasedProducts"
|
||||
:key="item.name"
|
||||
>
|
||||
<td class="text-no-wrap">
|
||||
{{ item.name }}
|
||||
</td>
|
||||
<td class="text-no-wrap">
|
||||
{{ item.description }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{{ item.hours }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{{ item.qty }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
${{ item.price }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
|
||||
<!-- 👉 Total -->
|
||||
<div class="d-flex justify-space-between flex-column flex-sm-row print-row">
|
||||
<div class="mb-2">
|
||||
<div class="d-flex align-center mb-1">
|
||||
<h6 class="text-h6 me-2">
|
||||
Salesperson:
|
||||
</h6>
|
||||
<span>Jenny Parker</span>
|
||||
</div>
|
||||
<p>Thanks for your business</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<table class="w-100">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="pe-16">
|
||||
Subtotal:
|
||||
</td>
|
||||
<td :class="$vuetify.locale.isRtl ? 'text-start' : 'text-end'">
|
||||
<h6 class="text-base font-weight-medium">
|
||||
$1800
|
||||
</h6>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="pe-16">
|
||||
Discount:
|
||||
</td>
|
||||
<td :class="$vuetify.locale.isRtl ? 'text-start' : 'text-end'">
|
||||
<h6 class="text-base font-weight-medium">
|
||||
$28
|
||||
</h6>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="pe-16">
|
||||
Tax:
|
||||
</td>
|
||||
<td :class="$vuetify.locale.isRtl ? 'text-start' : 'text-end'">
|
||||
<h6 class="text-base font-weight-medium">
|
||||
21%
|
||||
</h6>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<VDivider class="my-2" />
|
||||
|
||||
<table class="w-100">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="pe-16">
|
||||
Total:
|
||||
</td>
|
||||
<td :class="$vuetify.locale.isRtl ? 'text-start' : 'text-end'">
|
||||
<h6 class="text-base font-weight-medium">
|
||||
$1690
|
||||
</h6>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<VDivider class="my-6 border-dashed" />
|
||||
|
||||
<p class="mb-0">
|
||||
<span class="text-high-emphasis font-weight-medium me-1">
|
||||
Note:
|
||||
</span>
|
||||
<span>It was a pleasure working with you and your team. We hope you will keep us in mind for future freelance projects. Thank You!</span>
|
||||
</p>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="3"
|
||||
class="d-print-none"
|
||||
>
|
||||
<VCard>
|
||||
<VCardText>
|
||||
<!-- 👉 Send Invoice Trigger button -->
|
||||
<VBtn
|
||||
block
|
||||
prepend-icon="tabler-send"
|
||||
class="mb-4"
|
||||
@click="isSendPaymentSidebarVisible = true"
|
||||
>
|
||||
Send Invoice
|
||||
</VBtn>
|
||||
|
||||
<VBtn
|
||||
block
|
||||
color="secondary"
|
||||
variant="tonal"
|
||||
class="mb-4"
|
||||
>
|
||||
Download
|
||||
</VBtn>
|
||||
|
||||
<div class="d-flex flex-wrap gap-4">
|
||||
<VBtn
|
||||
variant="tonal"
|
||||
color="secondary"
|
||||
class="flex-grow-1"
|
||||
@click="printInvoice"
|
||||
>
|
||||
Print
|
||||
</VBtn>
|
||||
|
||||
<VBtn
|
||||
color="secondary"
|
||||
variant="tonal"
|
||||
class="mb-4 flex-grow-1"
|
||||
:to="{ name: 'apps-invoice-edit-id', params: { id: route.params.id } }"
|
||||
>
|
||||
Edit
|
||||
</VBtn>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Add Payment trigger button -->
|
||||
<VBtn
|
||||
block
|
||||
prepend-icon="tabler-currency-dollar"
|
||||
color="success"
|
||||
@click="isAddPaymentSidebarVisible = true"
|
||||
>
|
||||
Add Payment
|
||||
</VBtn>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<!-- 👉 Add Payment Sidebar -->
|
||||
<InvoiceAddPaymentDrawer v-model:is-drawer-open="isAddPaymentSidebarVisible" />
|
||||
|
||||
<!-- 👉 Send Invoice Sidebar -->
|
||||
<InvoiceSendInvoiceDrawer v-model:is-drawer-open="isSendPaymentSidebarVisible" />
|
||||
</section>
|
||||
<section v-else>
|
||||
<VAlert
|
||||
type="error"
|
||||
variant="tonal"
|
||||
>
|
||||
Invoice with ID {{ route.params.id }} not found!
|
||||
</VAlert>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.invoice-preview-table {
|
||||
--v-table-header-color: var(--v-theme-surface);
|
||||
|
||||
&.v-table .v-table__wrapper table thead tr th {
|
||||
border-block-end: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
.v-theme--dark {
|
||||
--v-theme-surface: 255, 255, 255;
|
||||
--v-theme-on-surface: 47, 43, 61;
|
||||
--v-theme-on-background: 47, 43, 61;
|
||||
}
|
||||
|
||||
body {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
.invoice-header-preview,
|
||||
.invoice-preview-wrapper {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.product-buy-now {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.v-navigation-drawer,
|
||||
.layout-vertical-nav,
|
||||
.app-customizer-toggler,
|
||||
.layout-footer,
|
||||
.layout-navbar,
|
||||
.layout-navbar-and-nav-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.v-card {
|
||||
box-shadow: none !important;
|
||||
|
||||
.print-row {
|
||||
flex-direction: row !important;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-content-wrapper {
|
||||
padding-inline-start: 0 !important;
|
||||
}
|
||||
|
||||
.v-table__wrapper {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.vue-devtools__anchor {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
85
resources/js/pages/apps/kanban/index.vue
Normal file
85
resources/js/pages/apps/kanban/index.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<script setup>
|
||||
import KanbanBoardComp from '@/views/apps/kanban/KanbanBoard.vue'
|
||||
|
||||
// 👉 initial kanban data fetch
|
||||
const {
|
||||
data: kanban,
|
||||
execute: refetchKanban,
|
||||
} = await useApi('/apps/kanban')
|
||||
|
||||
const addNewBoard = async newBoardName => {
|
||||
await $api('/apps/kanban/board/add', {
|
||||
method: 'POST',
|
||||
body: { title: newBoardName },
|
||||
})
|
||||
refetchKanban()
|
||||
}
|
||||
|
||||
const deleteBoard = async boardId => {
|
||||
await $api(`/apps/kanban/board/${ boardId }`, { method: 'DELETE' })
|
||||
refetchKanban()
|
||||
}
|
||||
|
||||
const renameTheBoard = async kanbanBoard => {
|
||||
await $api('/apps/kanban/board/rename', {
|
||||
method: 'PUT',
|
||||
body: kanbanBoard,
|
||||
})
|
||||
refetchKanban()
|
||||
}
|
||||
|
||||
const addNewItem = async newItem => {
|
||||
await $api('/apps/kanban/item/add', {
|
||||
method: 'POST',
|
||||
body: newItem,
|
||||
})
|
||||
refetchKanban()
|
||||
}
|
||||
|
||||
const editItemFn = async editItem => {
|
||||
await $api('/apps/kanban/item/update', {
|
||||
method: 'PUT',
|
||||
body: editItem,
|
||||
})
|
||||
refetchKanban()
|
||||
}
|
||||
|
||||
const deleteItemFn = async deleteItem => {
|
||||
if (deleteItem.item && deleteItem.item.id) {
|
||||
await $api(`/apps/kanban/item/${ deleteItem.item.id }`, {
|
||||
method: 'DELETE',
|
||||
body: deleteItem,
|
||||
})
|
||||
refetchKanban()
|
||||
}
|
||||
}
|
||||
|
||||
const updateItemState = async kanbanState => {
|
||||
await $api('/apps/kanban/item/state-update', {
|
||||
method: 'PUT',
|
||||
body: kanbanState,
|
||||
})
|
||||
}
|
||||
|
||||
const updateBoardState = async kanbanBoardIds => {
|
||||
await $api('/apps/kanban/board/state-update', {
|
||||
method: 'PUT',
|
||||
body: kanbanBoardIds,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<KanbanBoardComp
|
||||
v-if="kanban"
|
||||
:kanban-data="kanban"
|
||||
@add-new-board="addNewBoard"
|
||||
@delete-board="deleteBoard"
|
||||
@rename-board="renameTheBoard"
|
||||
@add-new-item="addNewItem"
|
||||
@edit-item="editItemFn"
|
||||
@delete-item="deleteItemFn"
|
||||
@update-items-state="updateItemState"
|
||||
@update-board-state="updateBoardState"
|
||||
/>
|
||||
</template>
|
||||
55
resources/js/pages/apps/logistics/dashboard.vue
Normal file
55
resources/js/pages/apps/logistics/dashboard.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<script setup>
|
||||
import LogisticsCardStatistics from '@/views/apps/logistics/LogisticsCardStatistics.vue'
|
||||
import LogisticsDeliveryExpectations from '@/views/apps/logistics/LogisticsDeliveryExpectations.vue'
|
||||
import LogisticsDeliveryPerformance from '@/views/apps/logistics/LogisticsDeliveryPerformance.vue'
|
||||
import LogisticsOrderByCountries from '@/views/apps/logistics/LogisticsOrderByCountries.vue'
|
||||
import LogisticsOverviewTable from '@/views/apps/logistics/LogisticsOverviewTable.vue'
|
||||
import LogisticsShipmentStatistics from '@/views/apps/logistics/LogisticsShipmentStatistics.vue'
|
||||
import LogisticsVehicleOverview from '@/views/apps/logistics/LogisticsVehicleOverview.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol cols="12">
|
||||
<LogisticsCardStatistics />
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<LogisticsVehicleOverview />
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<LogisticsShipmentStatistics />
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<LogisticsDeliveryPerformance />
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<LogisticsDeliveryExpectations />
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<LogisticsOrderByCountries />
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<LogisticsOverviewTable />
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
402
resources/js/pages/apps/logistics/fleet.vue
Normal file
402
resources/js/pages/apps/logistics/fleet.vue
Normal file
@@ -0,0 +1,402 @@
|
||||
// ❗ WARNING please use your access token from mapbox.com
|
||||
<script setup>
|
||||
import mapboxgl from 'mapbox-gl'
|
||||
import {
|
||||
onMounted,
|
||||
ref,
|
||||
} from 'vue'
|
||||
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import fleetImg from '@images/misc/fleet-car.png'
|
||||
|
||||
const { isLeftSidebarOpen } = useResponsiveLeftSidebar()
|
||||
const accessToken = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN
|
||||
const map = ref()
|
||||
const vuetifyDisplay = useDisplay()
|
||||
|
||||
definePage({ meta: { layoutWrapperClasses: 'layout-content-height-fixed' } })
|
||||
|
||||
const carImgs = ref([
|
||||
fleetImg,
|
||||
fleetImg,
|
||||
fleetImg,
|
||||
fleetImg,
|
||||
])
|
||||
|
||||
const refCars = ref([])
|
||||
|
||||
const showPanel = ref([
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
])
|
||||
|
||||
const geojson = {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [
|
||||
-73.999024,
|
||||
40.75249842,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [
|
||||
-74.03,
|
||||
40.75699842,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [
|
||||
-73.967524,
|
||||
40.7599842,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [
|
||||
-74.0325,
|
||||
40.742992,
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const activeIndex = ref(0)
|
||||
|
||||
onMounted(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
mapboxgl.accessToken = accessToken
|
||||
map.value = new mapboxgl.Map({
|
||||
container: 'mapContainer',
|
||||
style: 'mapbox://styles/mapbox/light-v9',
|
||||
center: [
|
||||
-73.999024,
|
||||
40.75249842,
|
||||
],
|
||||
zoom: 12.25,
|
||||
})
|
||||
for (let index = 0; index < geojson.features.length; index++)
|
||||
new mapboxgl.Marker({ element: refCars.value[index] }).setLngLat(geojson.features[index].geometry.coordinates).addTo(map.value)
|
||||
refCars.value[activeIndex.value].classList.add('marker-focus')
|
||||
})
|
||||
|
||||
const vehicleTrackingData = [
|
||||
{
|
||||
name: 'VOL-342808',
|
||||
location: 'Chelsea, NY, USA',
|
||||
progress: 88,
|
||||
driverName: 'Veronica Herman',
|
||||
},
|
||||
{
|
||||
name: 'VOL-954784',
|
||||
location: 'Lincoln Harbor, NY, USA',
|
||||
progress: 100,
|
||||
driverName: 'Myrtle Ullrich',
|
||||
},
|
||||
{
|
||||
name: 'VOL-342808',
|
||||
location: 'Midtown East, NY, USA',
|
||||
progress: 60,
|
||||
driverName: 'Barry Schowalter',
|
||||
},
|
||||
{
|
||||
name: 'VOL-343908',
|
||||
location: 'Hoboken, NY, USA',
|
||||
progress: 28,
|
||||
driverName: 'Helen Jacobs',
|
||||
},
|
||||
]
|
||||
|
||||
const flyToLocation = (geolocation, index) => {
|
||||
activeIndex.value = index
|
||||
showPanel.value.fill(false)
|
||||
showPanel.value[index] = !showPanel.value[index]
|
||||
if (vuetifyDisplay.mdAndDown.value)
|
||||
isLeftSidebarOpen.value = false
|
||||
map.value.flyTo({
|
||||
center: geolocation,
|
||||
zoom: 16,
|
||||
})
|
||||
}
|
||||
|
||||
watch(activeIndex, () => {
|
||||
refCars.value.forEach((car, index) => {
|
||||
if (index === activeIndex.value)
|
||||
car.classList.add('marker-focus')
|
||||
else
|
||||
car.classList.remove('marker-focus')
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VLayout class="fleet-app-layout">
|
||||
<VNavigationDrawer
|
||||
v-model="isLeftSidebarOpen"
|
||||
data-allow-mismatch
|
||||
width="360"
|
||||
absolute
|
||||
touchless
|
||||
location="start"
|
||||
border="none"
|
||||
>
|
||||
<VCard
|
||||
class="h-100 fleet-navigation-drawer"
|
||||
flat
|
||||
>
|
||||
<VCardItem>
|
||||
<VCardTitle>
|
||||
Fleet
|
||||
</VCardTitle>
|
||||
|
||||
<template #append>
|
||||
<IconBtn
|
||||
class="d-lg-none navigation-close-btn"
|
||||
@click="isLeftSidebarOpen = !isLeftSidebarOpen"
|
||||
>
|
||||
<VIcon icon="tabler-x" />
|
||||
</IconBtn>
|
||||
</template>
|
||||
</VCardItem>
|
||||
|
||||
<!-- 👉 Perfect Scrollbar -->
|
||||
<PerfectScrollbar
|
||||
:options="{ wheelPropagation: false, suppressScrollX: true }"
|
||||
style="block-size: calc(100% - 60px);"
|
||||
>
|
||||
<VCardText class="pt-0">
|
||||
<div
|
||||
v-for="(vehicle, index) in vehicleTrackingData"
|
||||
:key="index"
|
||||
class="mb-6"
|
||||
>
|
||||
<div
|
||||
class="d-flex align-center justify-space-between cursor-pointer"
|
||||
@click="flyToLocation(geojson.features[index].geometry.coordinates, index)"
|
||||
>
|
||||
<div class="d-flex gap-x-4 align-center">
|
||||
<VAvatar
|
||||
icon="tabler-car"
|
||||
variant="tonal"
|
||||
color="secondary"
|
||||
/>
|
||||
<div>
|
||||
<div class="text-body-1 text-high-emphasis">
|
||||
{{ vehicle.name }}
|
||||
</div>
|
||||
<div class="text-body-1">
|
||||
{{ vehicle.location }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<IconBtn size="small">
|
||||
<VIcon
|
||||
:icon="showPanel[index] ? 'tabler-chevron-down' : $vuetify.locale.isRtl ? 'tabler-chevron-left' : 'tabler-chevron-right'"
|
||||
class="text-high-emphasis"
|
||||
/>
|
||||
</IconBtn>
|
||||
</div>
|
||||
<VExpandTransition mode="out-in">
|
||||
<div v-show="showPanel[index]">
|
||||
<div class="py-8">
|
||||
<div class="d-flex justify-space-between mb-1">
|
||||
<span class="text-body-1 text-high-emphasis">Delivery Process</span>
|
||||
<span class="text-body-1">{{ vehicle.progress }}%</span>
|
||||
</div>
|
||||
<VProgressLinear
|
||||
:model-value="vehicle.progress"
|
||||
color="primary"
|
||||
rounded
|
||||
height="6"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<VTimeline
|
||||
align="start"
|
||||
truncate-line="both"
|
||||
side="end"
|
||||
density="compact"
|
||||
line-thickness="1"
|
||||
line-inset="6"
|
||||
class="ps-2 v-timeline--variant-outlined fleet-timeline"
|
||||
>
|
||||
<VTimelineItem
|
||||
icon="tabler-circle-check"
|
||||
dot-color="rgb(var(--v-theme-surface))"
|
||||
icon-color="success"
|
||||
fill-dot
|
||||
size="20"
|
||||
:elevation="0"
|
||||
>
|
||||
<div class="ps-1">
|
||||
<div class="text-caption text-success">
|
||||
TRACKING NUMBER CREATED
|
||||
</div>
|
||||
<div class="app-timeline-title">
|
||||
{{ vehicle.driverName }}
|
||||
</div>
|
||||
<div class="text-body-2">
|
||||
Sep 01, 7:53 AM
|
||||
</div>
|
||||
</div>
|
||||
</VTimelineItem>
|
||||
|
||||
<VTimelineItem
|
||||
icon="tabler-circle-check"
|
||||
dot-color="rgb(var(--v-theme-surface))"
|
||||
icon-color="success"
|
||||
fill-dot
|
||||
size="20"
|
||||
:elevation="0"
|
||||
>
|
||||
<div class="text-caption text-uppercase text-success">
|
||||
OUT FOR DELIVERY
|
||||
</div>
|
||||
<div class="app-timeline-title">
|
||||
Veronica Herman
|
||||
</div>
|
||||
<div class="text-body-2">
|
||||
Sep 03, 8:02 AM
|
||||
</div>
|
||||
</VTimelineItem>
|
||||
|
||||
<VTimelineItem
|
||||
icon="tabler-map-pin"
|
||||
dot-color="rgb(var(--v-theme-surface))"
|
||||
icon-color="primary"
|
||||
fill-dot
|
||||
size="20"
|
||||
:elevation="0"
|
||||
>
|
||||
<div class="text-caption text-uppercase text-success">
|
||||
ARRIVED
|
||||
</div>
|
||||
<div class="app-timeline-title">
|
||||
Veronica Herman
|
||||
</div>
|
||||
<div class="text-body-2">
|
||||
Sep 04, 8:18 AM
|
||||
</div>
|
||||
</VTimelineItem>
|
||||
</VTimeline>
|
||||
</div>
|
||||
</div>
|
||||
</VExpandTransition>
|
||||
</div>
|
||||
</VCardText>
|
||||
</PerfectScrollbar>
|
||||
</VCard>
|
||||
</VNavigationDrawer>
|
||||
|
||||
<VMain>
|
||||
<div class="h-100">
|
||||
<IconBtn
|
||||
class="d-lg-none navigation-toggle-btn rounded-sm"
|
||||
variant="elevated"
|
||||
@click="isLeftSidebarOpen = true"
|
||||
>
|
||||
<VIcon icon="tabler-menu-2" />
|
||||
</IconBtn>
|
||||
<!-- 👉 Fleet map -->
|
||||
<div
|
||||
id="mapContainer"
|
||||
class="basemap"
|
||||
/>
|
||||
|
||||
<img
|
||||
v-for="(car, index) in carImgs"
|
||||
:key="index"
|
||||
ref="refCars"
|
||||
:src="car"
|
||||
alt="car Img marker"
|
||||
height="42"
|
||||
width="20"
|
||||
>
|
||||
</div>
|
||||
</VMain>
|
||||
</VLayout>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@styles/variables/vuetify";
|
||||
@use "@core-scss/base/mixins";
|
||||
@import "mapbox-gl/dist/mapbox-gl.css";
|
||||
|
||||
.fleet-app-layout {
|
||||
border-radius: vuetify.$card-border-radius;
|
||||
|
||||
@include mixins.elevation(vuetify.$card-elevation);
|
||||
|
||||
$sel-fleet-app-layout: &;
|
||||
|
||||
@at-root {
|
||||
.skin--bordered {
|
||||
@include mixins.bordered-skin($sel-fleet-app-layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navigation-toggle-btn {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
inset-block-start: 1rem;
|
||||
inset-inline-start: 1rem;
|
||||
}
|
||||
|
||||
.navigation-close-btn {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
inset-block-start: 1rem;
|
||||
inset-inline-end: 1rem;
|
||||
}
|
||||
|
||||
.basemap {
|
||||
block-size: 100%;
|
||||
inline-size: 100%;
|
||||
}
|
||||
|
||||
.marker-focus {
|
||||
filter: drop-shadow(0 0 7px rgb(var(--v-theme-primary)));
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-bottom-left,
|
||||
.mapboxgl-ctrl-bottom-right {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fleet-navigation-drawer {
|
||||
.v-timeline .v-timeline-divider__dot .v-timeline-divider__inner-dot {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fleet-timeline {
|
||||
&.v-timeline .v-timeline-item:not(:last-child) {
|
||||
.v-timeline-item__body {
|
||||
margin-block-end: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line selector-id-pattern */
|
||||
#mapContainer {
|
||||
block-size: 100vh !important;
|
||||
}
|
||||
</style>
|
||||
201
resources/js/pages/apps/permissions/index.vue
Normal file
201
resources/js/pages/apps/permissions/index.vue
Normal file
@@ -0,0 +1,201 @@
|
||||
<script setup>
|
||||
const headers = [
|
||||
{
|
||||
title: 'Name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: 'Assigned To',
|
||||
key: 'assignedTo',
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
title: 'Created Date',
|
||||
key: 'createdDate',
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
title: 'Actions',
|
||||
key: 'actions',
|
||||
sortable: false,
|
||||
},
|
||||
]
|
||||
|
||||
const search = ref('')
|
||||
|
||||
// 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 isPermissionDialogVisible = ref(false)
|
||||
const isAddPermissionDialogVisible = ref(false)
|
||||
const permissionName = ref('')
|
||||
|
||||
const colors = {
|
||||
'support': {
|
||||
color: 'info',
|
||||
text: 'Support',
|
||||
},
|
||||
'users': {
|
||||
color: 'success',
|
||||
text: 'Users',
|
||||
},
|
||||
'manager': {
|
||||
color: 'warning',
|
||||
text: 'Manager',
|
||||
},
|
||||
'administrator': {
|
||||
color: 'primary',
|
||||
text: 'Administrator',
|
||||
},
|
||||
'restricted-user': {
|
||||
color: 'error',
|
||||
text: 'Restricted User',
|
||||
},
|
||||
}
|
||||
|
||||
const { data: permissionsData } = await useApi(createUrl('/apps/permissions', {
|
||||
query: {
|
||||
q: search,
|
||||
itemsPerPage,
|
||||
page,
|
||||
sortBy,
|
||||
orderBy,
|
||||
},
|
||||
}))
|
||||
|
||||
const permissions = computed(() => permissionsData.value.permissions)
|
||||
const totalPermissions = computed(() => permissionsData.value.totalPermissions)
|
||||
|
||||
const editPermission = name => {
|
||||
isPermissionDialogVisible.value = true
|
||||
permissionName.value = name
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardText class="d-flex align-center justify-space-between flex-wrap gap-4">
|
||||
<div class="d-flex gap-2 align-center">
|
||||
<p class="text-body-1 mb-0">
|
||||
Show
|
||||
</p>
|
||||
<AppSelect
|
||||
:model-value="itemsPerPage"
|
||||
:items="[
|
||||
{ value: 5, title: '5' },
|
||||
{ value: 25, title: '25' },
|
||||
{ value: 50, title: '50' },
|
||||
{ value: 100, title: '100' },
|
||||
{ value: -1, title: 'All' },
|
||||
]"
|
||||
style="inline-size: 5.5rem;"
|
||||
@update:model-value="itemsPerPage = parseInt($event, 10)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-center gap-4 flex-wrap">
|
||||
<AppTextField
|
||||
v-model="search"
|
||||
placeholder="Search Permission"
|
||||
style="inline-size: 15.625rem;"
|
||||
/>
|
||||
<VBtn
|
||||
density="default"
|
||||
prepend-icon="tabler-plus"
|
||||
@click="isAddPermissionDialogVisible = true"
|
||||
>
|
||||
Add Permission
|
||||
</VBtn>
|
||||
</div>
|
||||
</VCardText>
|
||||
|
||||
<VDivider />
|
||||
|
||||
<VDataTableServer
|
||||
v-model:items-per-page="itemsPerPage"
|
||||
v-model:page="page"
|
||||
:items-length="totalPermissions"
|
||||
:items-per-page-options="[
|
||||
{ value: 5, title: '5' },
|
||||
{ value: 10, title: '10' },
|
||||
{ value: -1, title: '$vuetify.dataFooter.itemsPerPageAll' },
|
||||
]"
|
||||
:headers="headers"
|
||||
:items="permissions"
|
||||
item-value="name"
|
||||
class="text-no-wrap"
|
||||
@update:options="updateOptions"
|
||||
>
|
||||
<!-- Name -->
|
||||
<template #item.name="{ item }">
|
||||
<div class="text-high-emphasis text-body-1">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Assigned To -->
|
||||
<template #item.assignedTo="{ item }">
|
||||
<div class="d-flex gap-4">
|
||||
<VChip
|
||||
v-for="text in item.assignedTo"
|
||||
:key="text"
|
||||
label
|
||||
size="small"
|
||||
:color="colors[text].color"
|
||||
class="font-weight-medium"
|
||||
>
|
||||
{{ colors[text].text }}
|
||||
</VChip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #bottom>
|
||||
<TablePagination
|
||||
v-model:page="page"
|
||||
:items-per-page="itemsPerPage"
|
||||
:total-items="totalPermissions"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Actions -->
|
||||
<template #item.actions="{ item }">
|
||||
<VBtn
|
||||
icon
|
||||
size="small"
|
||||
color="medium-emphasis"
|
||||
variant="text"
|
||||
@click="editPermission(item.name)"
|
||||
>
|
||||
<VIcon
|
||||
size="22"
|
||||
icon="tabler-edit"
|
||||
/>
|
||||
</VBtn>
|
||||
<IconBtn>
|
||||
<VIcon
|
||||
icon="tabler-dots-vertical"
|
||||
size="22"
|
||||
/>
|
||||
</IconBtn>
|
||||
</template>
|
||||
</VDataTableServer>
|
||||
</VCard>
|
||||
|
||||
<AddEditPermissionDialog
|
||||
v-model:is-dialog-visible="isPermissionDialogVisible"
|
||||
v-model:permission-name="permissionName"
|
||||
/>
|
||||
<AddEditPermissionDialog v-model:is-dialog-visible="isAddPermissionDialogVisible" />
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
36
resources/js/pages/apps/roles/index.vue
Normal file
36
resources/js/pages/apps/roles/index.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<script setup>
|
||||
import RoleCards from '@/views/apps/roles/RoleCards.vue'
|
||||
import UserList from '@/views/apps/roles/UserList.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<h4 class="text-h4 mb-1">
|
||||
Roles List
|
||||
</h4>
|
||||
<p class="text-body-1 mb-0">
|
||||
A role provided access to predefined menus and features so that depending on assigned role an administrator can have access to what he need
|
||||
</p>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Roles Cards -->
|
||||
<VCol cols="12">
|
||||
<RoleCards />
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<h4 class="text-h4 mb-1 mt-6">
|
||||
Total users with their roles
|
||||
</h4>
|
||||
<p class="text-body-1 mb-0">
|
||||
Find all of your company’s administrator accounts and their associate roles.
|
||||
</p>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 User List -->
|
||||
<UserList />
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
525
resources/js/pages/apps/user/list/index.vue
Normal file
525
resources/js/pages/apps/user/list/index.vue
Normal file
@@ -0,0 +1,525 @@
|
||||
<script setup>
|
||||
import AddNewUserDrawer from '@/views/apps/user/list/AddNewUserDrawer.vue'
|
||||
|
||||
// 👉 Store
|
||||
const searchQuery = ref('')
|
||||
const selectedRole = ref()
|
||||
const selectedPlan = ref()
|
||||
const selectedStatus = ref()
|
||||
|
||||
// Data table options
|
||||
const itemsPerPage = ref(10)
|
||||
const page = ref(1)
|
||||
const sortBy = ref()
|
||||
const orderBy = ref()
|
||||
const selectedRows = ref([])
|
||||
|
||||
const updateOptions = options => {
|
||||
sortBy.value = options.sortBy[0]?.key
|
||||
orderBy.value = options.sortBy[0]?.order
|
||||
}
|
||||
|
||||
// Headers
|
||||
const headers = [
|
||||
{
|
||||
title: 'User',
|
||||
key: 'user',
|
||||
},
|
||||
{
|
||||
title: 'Role',
|
||||
key: 'role',
|
||||
},
|
||||
{
|
||||
title: 'Plan',
|
||||
key: 'plan',
|
||||
},
|
||||
{
|
||||
title: 'Billing',
|
||||
key: 'billing',
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
key: 'status',
|
||||
},
|
||||
{
|
||||
title: 'Actions',
|
||||
key: 'actions',
|
||||
sortable: false,
|
||||
},
|
||||
]
|
||||
|
||||
const {
|
||||
data: usersData,
|
||||
execute: fetchUsers,
|
||||
} = await useApi(createUrl('/apps/users', {
|
||||
query: {
|
||||
q: searchQuery,
|
||||
status: selectedStatus,
|
||||
plan: selectedPlan,
|
||||
role: selectedRole,
|
||||
itemsPerPage,
|
||||
page,
|
||||
sortBy,
|
||||
orderBy,
|
||||
},
|
||||
}))
|
||||
|
||||
const users = computed(() => usersData.value.users)
|
||||
const totalUsers = computed(() => usersData.value.totalUsers)
|
||||
|
||||
// 👉 search filters
|
||||
const roles = [
|
||||
{
|
||||
title: 'Admin',
|
||||
value: 'admin',
|
||||
},
|
||||
{
|
||||
title: 'Author',
|
||||
value: 'author',
|
||||
},
|
||||
{
|
||||
title: 'Editor',
|
||||
value: 'editor',
|
||||
},
|
||||
{
|
||||
title: 'Maintainer',
|
||||
value: 'maintainer',
|
||||
},
|
||||
{
|
||||
title: 'Subscriber',
|
||||
value: 'subscriber',
|
||||
},
|
||||
]
|
||||
|
||||
const plans = [
|
||||
{
|
||||
title: 'Basic',
|
||||
value: 'basic',
|
||||
},
|
||||
{
|
||||
title: 'Company',
|
||||
value: 'company',
|
||||
},
|
||||
{
|
||||
title: 'Enterprise',
|
||||
value: 'enterprise',
|
||||
},
|
||||
{
|
||||
title: 'Team',
|
||||
value: 'team',
|
||||
},
|
||||
]
|
||||
|
||||
const status = [
|
||||
{
|
||||
title: 'Pending',
|
||||
value: 'pending',
|
||||
},
|
||||
{
|
||||
title: 'Active',
|
||||
value: 'active',
|
||||
},
|
||||
{
|
||||
title: 'Inactive',
|
||||
value: 'inactive',
|
||||
},
|
||||
]
|
||||
|
||||
const resolveUserRoleVariant = role => {
|
||||
const roleLowerCase = role.toLowerCase()
|
||||
if (roleLowerCase === 'subscriber')
|
||||
return {
|
||||
color: 'success',
|
||||
icon: 'tabler-user',
|
||||
}
|
||||
if (roleLowerCase === 'author')
|
||||
return {
|
||||
color: 'error',
|
||||
icon: 'tabler-device-desktop',
|
||||
}
|
||||
if (roleLowerCase === 'maintainer')
|
||||
return {
|
||||
color: 'info',
|
||||
icon: 'tabler-chart-pie',
|
||||
}
|
||||
if (roleLowerCase === 'editor')
|
||||
return {
|
||||
color: 'warning',
|
||||
icon: 'tabler-edit',
|
||||
}
|
||||
if (roleLowerCase === 'admin')
|
||||
return {
|
||||
color: 'primary',
|
||||
icon: 'tabler-crown',
|
||||
}
|
||||
|
||||
return {
|
||||
color: 'primary',
|
||||
icon: 'tabler-user',
|
||||
}
|
||||
}
|
||||
|
||||
const resolveUserStatusVariant = stat => {
|
||||
const statLowerCase = stat.toLowerCase()
|
||||
if (statLowerCase === 'pending')
|
||||
return 'warning'
|
||||
if (statLowerCase === 'active')
|
||||
return 'success'
|
||||
if (statLowerCase === 'inactive')
|
||||
return 'secondary'
|
||||
|
||||
return 'primary'
|
||||
}
|
||||
|
||||
const isAddNewUserDrawerVisible = ref(false)
|
||||
|
||||
const addNewUser = async userData => {
|
||||
await $api('/apps/users', {
|
||||
method: 'POST',
|
||||
body: userData,
|
||||
})
|
||||
|
||||
// Refetch User
|
||||
fetchUsers()
|
||||
}
|
||||
|
||||
const deleteUser = async id => {
|
||||
await $api(`/apps/users/${ id }`, { method: 'DELETE' })
|
||||
|
||||
// Delete from selectedRows
|
||||
const index = selectedRows.value.findIndex(row => row === id)
|
||||
if (index !== -1)
|
||||
selectedRows.value.splice(index, 1)
|
||||
|
||||
// Refetch User
|
||||
fetchUsers()
|
||||
}
|
||||
|
||||
const widgetData = ref([
|
||||
{
|
||||
title: 'Session',
|
||||
value: '21,459',
|
||||
change: 29,
|
||||
desc: 'Total Users',
|
||||
icon: 'tabler-users',
|
||||
iconColor: 'primary',
|
||||
},
|
||||
{
|
||||
title: 'Paid Users',
|
||||
value: '4,567',
|
||||
change: 18,
|
||||
desc: 'Last Week Analytics',
|
||||
icon: 'tabler-user-plus',
|
||||
iconColor: 'error',
|
||||
},
|
||||
{
|
||||
title: 'Active Users',
|
||||
value: '19,860',
|
||||
change: -14,
|
||||
desc: 'Last Week Analytics',
|
||||
icon: 'tabler-user-check',
|
||||
iconColor: 'success',
|
||||
},
|
||||
{
|
||||
title: 'Pending Users',
|
||||
value: '237',
|
||||
change: 42,
|
||||
desc: 'Last Week Analytics',
|
||||
icon: 'tabler-user-search',
|
||||
iconColor: 'warning',
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section>
|
||||
<!-- 👉 Widgets -->
|
||||
<div class="d-flex mb-6">
|
||||
<VRow>
|
||||
<template
|
||||
v-for="(data, id) in widgetData"
|
||||
:key="id"
|
||||
>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="3"
|
||||
sm="6"
|
||||
>
|
||||
<VCard>
|
||||
<VCardText>
|
||||
<div class="d-flex justify-space-between">
|
||||
<div class="d-flex flex-column gap-y-1">
|
||||
<div class="text-body-1 text-high-emphasis">
|
||||
{{ data.title }}
|
||||
</div>
|
||||
<div class="d-flex gap-x-2 align-center">
|
||||
<h4 class="text-h4">
|
||||
{{ data.value }}
|
||||
</h4>
|
||||
<div
|
||||
class="text-base"
|
||||
:class="data.change > 0 ? 'text-success' : 'text-error'"
|
||||
>
|
||||
({{ prefixWithPlus(data.change) }}%)
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
{{ data.desc }}
|
||||
</div>
|
||||
</div>
|
||||
<VAvatar
|
||||
:color="data.iconColor"
|
||||
variant="tonal"
|
||||
rounded
|
||||
size="42"
|
||||
>
|
||||
<VIcon
|
||||
:icon="data.icon"
|
||||
size="26"
|
||||
/>
|
||||
</VAvatar>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</template>
|
||||
</VRow>
|
||||
</div>
|
||||
|
||||
<VCard class="mb-6">
|
||||
<VCardItem class="pb-4">
|
||||
<VCardTitle>Filters</VCardTitle>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<!-- 👉 Select Role -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="4"
|
||||
>
|
||||
<AppSelect
|
||||
v-model="selectedRole"
|
||||
placeholder="Select Role"
|
||||
:items="roles"
|
||||
clearable
|
||||
clear-icon="tabler-x"
|
||||
/>
|
||||
</VCol>
|
||||
<!-- 👉 Select Plan -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="4"
|
||||
>
|
||||
<AppSelect
|
||||
v-model="selectedPlan"
|
||||
placeholder="Select Plan"
|
||||
:items="plans"
|
||||
clearable
|
||||
clear-icon="tabler-x"
|
||||
/>
|
||||
</VCol>
|
||||
<!-- 👉 Select Status -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="4"
|
||||
>
|
||||
<AppSelect
|
||||
v-model="selectedStatus"
|
||||
placeholder="Select Status"
|
||||
:items="status"
|
||||
clearable
|
||||
clear-icon="tabler-x"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
|
||||
<VDivider />
|
||||
|
||||
<VCardText class="d-flex flex-wrap gap-4">
|
||||
<div class="me-3 d-flex gap-3">
|
||||
<AppSelect
|
||||
:model-value="itemsPerPage"
|
||||
:items="[
|
||||
{ value: 10, title: '10' },
|
||||
{ value: 25, title: '25' },
|
||||
{ value: 50, title: '50' },
|
||||
{ value: 100, title: '100' },
|
||||
{ value: -1, title: 'All' },
|
||||
]"
|
||||
style="inline-size: 6.25rem;"
|
||||
@update:model-value="itemsPerPage = parseInt($event, 10)"
|
||||
/>
|
||||
</div>
|
||||
<VSpacer />
|
||||
|
||||
<div class="app-user-search-filter d-flex align-center flex-wrap gap-4">
|
||||
<!-- 👉 Search -->
|
||||
<div style="inline-size: 15.625rem;">
|
||||
<AppTextField
|
||||
v-model="searchQuery"
|
||||
placeholder="Search User"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Export button -->
|
||||
<VBtn
|
||||
variant="tonal"
|
||||
color="secondary"
|
||||
prepend-icon="tabler-upload"
|
||||
>
|
||||
Export
|
||||
</VBtn>
|
||||
|
||||
<!-- 👉 Add user button -->
|
||||
<VBtn
|
||||
prepend-icon="tabler-plus"
|
||||
@click="isAddNewUserDrawerVisible = true"
|
||||
>
|
||||
Add New User
|
||||
</VBtn>
|
||||
</div>
|
||||
</VCardText>
|
||||
|
||||
<VDivider />
|
||||
|
||||
<!-- SECTION datatable -->
|
||||
<VDataTableServer
|
||||
v-model:items-per-page="itemsPerPage"
|
||||
v-model:model-value="selectedRows"
|
||||
v-model:page="page"
|
||||
:items="users"
|
||||
item-value="id"
|
||||
:items-length="totalUsers"
|
||||
:headers="headers"
|
||||
class="text-no-wrap"
|
||||
show-select
|
||||
@update:options="updateOptions"
|
||||
>
|
||||
<!-- User -->
|
||||
<template #item.user="{ item }">
|
||||
<div class="d-flex align-center gap-x-4">
|
||||
<VAvatar
|
||||
size="34"
|
||||
:variant="!item.avatar ? 'tonal' : undefined"
|
||||
:color="!item.avatar ? resolveUserRoleVariant(item.role).color : undefined"
|
||||
>
|
||||
<VImg
|
||||
v-if="item.avatar"
|
||||
:src="item.avatar"
|
||||
/>
|
||||
<span v-else>{{ avatarText(item.fullName) }}</span>
|
||||
</VAvatar>
|
||||
<div class="d-flex flex-column">
|
||||
<h6 class="text-base">
|
||||
<RouterLink
|
||||
:to="{ name: 'apps-user-view-id', params: { id: item.id } }"
|
||||
class="font-weight-medium text-link"
|
||||
>
|
||||
{{ item.fullName }}
|
||||
</RouterLink>
|
||||
</h6>
|
||||
<div class="text-sm">
|
||||
{{ item.email }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 👉 Role -->
|
||||
<template #item.role="{ item }">
|
||||
<div class="d-flex align-center gap-x-2">
|
||||
<VIcon
|
||||
:size="22"
|
||||
:icon="resolveUserRoleVariant(item.role).icon"
|
||||
:color="resolveUserRoleVariant(item.role).color"
|
||||
/>
|
||||
|
||||
<div class="text-capitalize text-high-emphasis text-body-1">
|
||||
{{ item.role }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Plan -->
|
||||
<template #item.plan="{ item }">
|
||||
<div class="text-body-1 text-high-emphasis text-capitalize">
|
||||
{{ item.currentPlan }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Status -->
|
||||
<template #item.status="{ item }">
|
||||
<VChip
|
||||
:color="resolveUserStatusVariant(item.status)"
|
||||
size="small"
|
||||
label
|
||||
class="text-capitalize"
|
||||
>
|
||||
{{ item.status }}
|
||||
</VChip>
|
||||
</template>
|
||||
|
||||
<!-- Actions -->
|
||||
<template #item.actions="{ item }">
|
||||
<IconBtn @click="deleteUser(item.id)">
|
||||
<VIcon icon="tabler-trash" />
|
||||
</IconBtn>
|
||||
|
||||
<IconBtn>
|
||||
<VIcon icon="tabler-eye" />
|
||||
</IconBtn>
|
||||
|
||||
<VBtn
|
||||
icon
|
||||
variant="text"
|
||||
color="medium-emphasis"
|
||||
>
|
||||
<VIcon icon="tabler-dots-vertical" />
|
||||
<VMenu activator="parent">
|
||||
<VList>
|
||||
<VListItem :to="{ name: 'apps-user-view-id', params: { id: item.id } }">
|
||||
<template #prepend>
|
||||
<VIcon icon="tabler-eye" />
|
||||
</template>
|
||||
|
||||
<VListItemTitle>View</VListItemTitle>
|
||||
</VListItem>
|
||||
|
||||
<VListItem link>
|
||||
<template #prepend>
|
||||
<VIcon icon="tabler-pencil" />
|
||||
</template>
|
||||
<VListItemTitle>Edit</VListItemTitle>
|
||||
</VListItem>
|
||||
|
||||
<VListItem @click="deleteUser(item.id)">
|
||||
<template #prepend>
|
||||
<VIcon icon="tabler-trash" />
|
||||
</template>
|
||||
<VListItemTitle>Delete</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</VBtn>
|
||||
</template>
|
||||
|
||||
<!-- pagination -->
|
||||
<template #bottom>
|
||||
<TablePagination
|
||||
v-model:page="page"
|
||||
:items-per-page="itemsPerPage"
|
||||
:total-items="totalUsers"
|
||||
/>
|
||||
</template>
|
||||
</VDataTableServer>
|
||||
<!-- SECTION -->
|
||||
</VCard>
|
||||
<!-- 👉 Add New User -->
|
||||
<AddNewUserDrawer
|
||||
v-model:is-drawer-open="isAddNewUserDrawerVisible"
|
||||
@user-data="addNewUser"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
105
resources/js/pages/apps/user/view/[id].vue
Normal file
105
resources/js/pages/apps/user/view/[id].vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<script setup>
|
||||
import UserBioPanel from '@/views/apps/user/view/UserBioPanel.vue'
|
||||
import UserTabAccount from '@/views/apps/user/view/UserTabAccount.vue'
|
||||
import UserTabBillingsPlans from '@/views/apps/user/view/UserTabBillingsPlans.vue'
|
||||
import UserTabConnections from '@/views/apps/user/view/UserTabConnections.vue'
|
||||
import UserTabNotifications from '@/views/apps/user/view/UserTabNotifications.vue'
|
||||
import UserTabSecurity from '@/views/apps/user/view/UserTabSecurity.vue'
|
||||
|
||||
const route = useRoute('apps-user-view-id')
|
||||
const userTab = ref(null)
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
icon: 'tabler-users',
|
||||
title: 'Account',
|
||||
},
|
||||
{
|
||||
icon: 'tabler-lock',
|
||||
title: 'Security',
|
||||
},
|
||||
{
|
||||
icon: 'tabler-bookmark',
|
||||
title: 'Billing & Plan',
|
||||
},
|
||||
{
|
||||
icon: 'tabler-bell',
|
||||
title: 'Notifications',
|
||||
},
|
||||
{
|
||||
icon: 'tabler-link',
|
||||
title: 'Connections',
|
||||
},
|
||||
]
|
||||
|
||||
const { data: userData } = await useApi(`/apps/users/${ route.params.id }`)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow v-if="userData">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="5"
|
||||
lg="4"
|
||||
>
|
||||
<UserBioPanel :user-data="userData" />
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="7"
|
||||
lg="8"
|
||||
>
|
||||
<VTabs
|
||||
v-model="userTab"
|
||||
class="v-tabs-pill"
|
||||
>
|
||||
<VTab
|
||||
v-for="tab in tabs"
|
||||
:key="tab.icon"
|
||||
>
|
||||
<VIcon
|
||||
:size="18"
|
||||
:icon="tab.icon"
|
||||
class="me-1"
|
||||
/>
|
||||
<span>{{ tab.title }}</span>
|
||||
</VTab>
|
||||
</VTabs>
|
||||
|
||||
<VWindow
|
||||
v-model="userTab"
|
||||
class="mt-6 disable-tab-transition"
|
||||
:touch="false"
|
||||
>
|
||||
<VWindowItem>
|
||||
<UserTabAccount />
|
||||
</VWindowItem>
|
||||
|
||||
<VWindowItem>
|
||||
<UserTabSecurity />
|
||||
</VWindowItem>
|
||||
|
||||
<VWindowItem>
|
||||
<UserTabBillingsPlans />
|
||||
</VWindowItem>
|
||||
|
||||
<VWindowItem>
|
||||
<UserTabNotifications />
|
||||
</VWindowItem>
|
||||
|
||||
<VWindowItem>
|
||||
<UserTabConnections />
|
||||
</VWindowItem>
|
||||
</VWindow>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<div v-else>
|
||||
<VAlert
|
||||
type="error"
|
||||
variant="tonal"
|
||||
>
|
||||
Invoice with ID {{ route.params.id }} not found!
|
||||
</VAlert>
|
||||
</div>
|
||||
</template>
|
||||
245
resources/js/pages/charts/apex-chart.vue
Normal file
245
resources/js/pages/charts/apex-chart.vue
Normal file
@@ -0,0 +1,245 @@
|
||||
<script setup>
|
||||
import ApexChartAreaChart from '@/views/charts/apex-chart/ApexChartAreaChart.vue'
|
||||
import ApexChartBalance from '@/views/charts/apex-chart/ApexChartBalance.vue'
|
||||
import ApexChartDailySalesStates from '@/views/charts/apex-chart/ApexChartDailySalesStates.vue'
|
||||
import ApexChartDataScience from '@/views/charts/apex-chart/ApexChartDataScience.vue'
|
||||
import ApexChartExpenseRatio from '@/views/charts/apex-chart/ApexChartExpenseRatio.vue'
|
||||
import ApexChartHorizontalBar from '@/views/charts/apex-chart/ApexChartHorizontalBar.vue'
|
||||
import ApexChartMobileComparison from '@/views/charts/apex-chart/ApexChartMobileComparison.vue'
|
||||
import ApexChartNewTechnologiesData from '@/views/charts/apex-chart/ApexChartNewTechnologiesData.vue'
|
||||
import ApexChartStatistics from '@/views/charts/apex-chart/ApexChartStatistics.vue'
|
||||
import ApexChartStocksPrices from '@/views/charts/apex-chart/ApexChartStocksPrices.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow id="apex-chart-wrapper">
|
||||
<!-- 👉 Area chart -->
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
|
||||
<VCardTitle>Area Chart</VCardTitle>
|
||||
<VCardSubtitle>Commercial networks</VCardSubtitle>
|
||||
|
||||
<template #append>
|
||||
<div class="date-picker-wrapper">
|
||||
<AppDateTimePicker
|
||||
model-value="2022-06-09"
|
||||
prepend-inner-icon="tabler-calendar"
|
||||
placeholder="Select Date"
|
||||
:config="$vuetify.display.smAndDown ? { position: 'auto center' } : { position: 'auto right' }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<ApexChartAreaChart />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Data Science -->
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
|
||||
<VCardTitle>Data Science</VCardTitle>
|
||||
|
||||
<template #append>
|
||||
<div class="date-picker-wrapper">
|
||||
<AppDateTimePicker
|
||||
model-value="2022-06-09"
|
||||
prepend-inner-icon="tabler-calendar"
|
||||
placeholder="Select Date"
|
||||
:config="$vuetify.display.smAndDown ? { position: 'auto center' } : { position: 'auto right' }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<ApexChartDataScience />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 New Technologies Data -->
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
|
||||
<VCardTitle>New Technologies Data</VCardTitle>
|
||||
|
||||
<template #append>
|
||||
<VBtnToggle
|
||||
density="compact"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
divided
|
||||
>
|
||||
<VBtn>Daily</VBtn>
|
||||
<VBtn>Monthly</VBtn>
|
||||
<VBtn>Yearly</VBtn>
|
||||
</VBtnToggle>
|
||||
</template>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<ApexChartNewTechnologiesData />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Balance Line Chart -->
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
|
||||
<VCardTitle>Balance</VCardTitle>
|
||||
<VCardSubtitle>Commercial networks & enterprises</VCardSubtitle>
|
||||
|
||||
<template #append>
|
||||
<div class="d-flex align-center">
|
||||
<h6 class="text-h6 me-3">
|
||||
$221,267
|
||||
</h6>
|
||||
<VChip
|
||||
label
|
||||
color="success"
|
||||
>
|
||||
<VIcon
|
||||
start
|
||||
icon="tabler-arrow-up"
|
||||
size="15"
|
||||
/>
|
||||
<span>22%</span>
|
||||
</VChip>
|
||||
</div>
|
||||
</template>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<ApexChartBalance />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Balance Horizontal Bar -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
|
||||
<VCardTitle>Balance</VCardTitle>
|
||||
<VCardSubtitle>$74,382.72</VCardSubtitle>
|
||||
|
||||
<template #append>
|
||||
<div class="date-picker-wrapper">
|
||||
<AppDateTimePicker
|
||||
model-value="2022-06-09"
|
||||
prepend-inner-icon="tabler-calendar"
|
||||
placeholder="Select Date"
|
||||
:config="$vuetify.display.smAndDown ? { position: 'auto center' } : { position: 'auto right' }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<ApexChartHorizontalBar />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Stocks Prices -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
|
||||
<VCardTitle>Stocks Prices</VCardTitle>
|
||||
<VCardSubtitle>$50,863.98</VCardSubtitle>
|
||||
|
||||
<template #append>
|
||||
<div class="date-picker-wrapper">
|
||||
<AppDateTimePicker
|
||||
model-value="2022-06-09"
|
||||
prepend-inner-icon="tabler-calendar"
|
||||
placeholder="Select Date"
|
||||
:config="$vuetify.display.smAndDown ? { position: 'auto center' } : { position: 'auto right' }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<ApexChartStocksPrices />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Daily Sales States -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VCard title="Daily Sales States">
|
||||
<VCardText>
|
||||
<ApexChartDailySalesStates />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Statistics -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VCard title="Statistics">
|
||||
<VCardText>
|
||||
<ApexChartStatistics />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Mobile Comparison -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VCard title="Mobile Comparison">
|
||||
<VCardText>
|
||||
<ApexChartMobileComparison />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Expense Ratio Chart -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VCard
|
||||
title="Expense Ratio"
|
||||
subtitle="Spending on various categories"
|
||||
>
|
||||
<VCardText>
|
||||
<ApexChartExpenseRatio />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/libs/apex-chart.scss";
|
||||
|
||||
.date-picker-wrapper {
|
||||
inline-size: 10.5rem;
|
||||
}
|
||||
|
||||
#apex-chart-wrapper {
|
||||
.v-card-item__append {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
202
resources/js/pages/charts/chartjs.vue
Normal file
202
resources/js/pages/charts/chartjs.vue
Normal file
@@ -0,0 +1,202 @@
|
||||
<script setup>
|
||||
import ChartJsBarChart from '@/views/charts/chartjs/ChartJsBarChart.vue'
|
||||
import ChartJsBubbleChart from '@/views/charts/chartjs/ChartJsBubbleChart.vue'
|
||||
import ChartJsHorizontalBarChart from '@/views/charts/chartjs/ChartJsHorizontalBarChart.vue'
|
||||
import ChartJsLineAreaChart from '@/views/charts/chartjs/ChartJsLineAreaChart.vue'
|
||||
import ChartJsLineChart from '@/views/charts/chartjs/ChartJsLineChart.vue'
|
||||
import ChartJsPolarAreaChart from '@/views/charts/chartjs/ChartJsPolarAreaChart.vue'
|
||||
import ChartJsRadarChart from '@/views/charts/chartjs/ChartJsRadarChart.vue'
|
||||
import ChartJsScatterChart from '@/views/charts/chartjs/ChartJsScatterChart.vue'
|
||||
|
||||
const chartJsCustomColors = {
|
||||
white: '#fff',
|
||||
yellow: '#ffe802',
|
||||
primary: '#836af9',
|
||||
areaChartBlue: '#2c9aff',
|
||||
barChartYellow: '#ffcf5c',
|
||||
polarChartGrey: '#4f5d70',
|
||||
polarChartInfo: '#299aff',
|
||||
lineChartYellow: '#d4e157',
|
||||
polarChartGreen: '#28dac6',
|
||||
lineChartPrimary: '#9e69fd',
|
||||
lineChartWarning: '#ff9800',
|
||||
horizontalBarInfo: '#26c6da',
|
||||
polarChartWarning: '#ff8131',
|
||||
scatterChartGreen: '#28c76f',
|
||||
warningShade: '#ffbd1f',
|
||||
areaChartBlueLight: '#84d0ff',
|
||||
areaChartGreyLight: '#edf1f4',
|
||||
scatterChartWarning: '#ff9f43',
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow id="chartjs-wrapper">
|
||||
<!-- 👉 Statistics Line Chart -->
|
||||
<VCol cols="12">
|
||||
<VCard
|
||||
title="Statistics"
|
||||
subtitle="Commercial networks and enterprises"
|
||||
>
|
||||
<VCardText>
|
||||
<ChartJsLineChart :colors="chartJsCustomColors" />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Radar Chart -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VCard title="Radar Chart">
|
||||
<VCardText>
|
||||
<ChartJsRadarChart />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Average Skills Polar Area Chart -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VCard title="Average Skills">
|
||||
<VCardText>
|
||||
<ChartJsPolarAreaChart :colors="chartJsCustomColors" />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Bubble Chart -->
|
||||
<VCol cols="12">
|
||||
<VCard title="Bubble Chart">
|
||||
<template #append>
|
||||
<span class="text-subtitle-2">$ 100,000</span>
|
||||
</template>
|
||||
|
||||
<VCardText>
|
||||
<ChartJsBubbleChart :colors="chartJsCustomColors" />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 New Product Data Scatter Chart -->
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
|
||||
<VCardTitle>New Product Data</VCardTitle>
|
||||
|
||||
<template #append>
|
||||
<VBtnToggle
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
>
|
||||
<VBtn>Daily</VBtn>
|
||||
<VBtn>Monthly</VBtn>
|
||||
<VBtn>Yearly</VBtn>
|
||||
</VBtnToggle>
|
||||
</template>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<ChartJsScatterChart :colors="chartJsCustomColors" />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Data Science Area Line Chart -->
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
|
||||
<VCardTitle>
|
||||
Data Science
|
||||
</VCardTitle>
|
||||
|
||||
<template #append>
|
||||
<div class="date-picker-wrapper">
|
||||
<AppDateTimePicker
|
||||
model-value="2022-06-09"
|
||||
prepend-inner-icon="tabler-calendar"
|
||||
placeholder="Select Date"
|
||||
:config="$vuetify.display.smAndDown ? { position: 'auto center' } : { position: 'auto right' }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<ChartJsLineAreaChart :colors="chartJsCustomColors" />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Latest Statistics -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
|
||||
<VCardTitle>Latest Statistics</VCardTitle>
|
||||
|
||||
<template #append>
|
||||
<div class="date-picker-wrapper">
|
||||
<AppDateTimePicker
|
||||
model-value="2022-06-09"
|
||||
prepend-inner-icon="tabler-calendar"
|
||||
placeholder="Select Date"
|
||||
:config="$vuetify.display.smAndDown ? { position: 'auto center' } : { position: 'auto right' }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<ChartJsBarChart :colors="chartJsCustomColors" />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Balance Horizontal Bar Chart -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VCard>
|
||||
<VCardItem class="d-flex flex-wrap justify-space-between gap-4">
|
||||
<VCardTitle>Balance</VCardTitle>
|
||||
<VCardSubtitle>$74,123</VCardSubtitle>
|
||||
|
||||
<template #append>
|
||||
<div class="date-picker-wrapper">
|
||||
<AppDateTimePicker
|
||||
model-value="2022-06-09"
|
||||
prepend-inner-icon="tabler-calendar"
|
||||
placeholder="Select Date"
|
||||
:config="$vuetify.display.smAndDown ? { position: 'auto center' } : { position: 'auto right' }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<ChartJsHorizontalBarChart :colors="chartJsCustomColors" />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.date-picker-wrapper {
|
||||
inline-size: 10.5rem;
|
||||
}
|
||||
|
||||
#chartjs-wrapper {
|
||||
.v-card-item__append {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
161
resources/js/pages/components/alert.vue
Normal file
161
resources/js/pages/components/alert.vue
Normal file
@@ -0,0 +1,161 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/components/alert/demoCodeAlert'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<DemoAlertBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Colors -->
|
||||
<AppCardCode
|
||||
title="Colors"
|
||||
:code="demoCode.colors"
|
||||
>
|
||||
<p>The <code>color</code> prop is used to change the background color of the alert.</p>
|
||||
|
||||
<DemoAlertColors />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Icons -->
|
||||
<AppCardCode
|
||||
title="Icons"
|
||||
:code="demoCode.icons"
|
||||
>
|
||||
<p>The <code>icon</code> prop allows you to add an icon to the beginning of the alert component. If a <code>type</code> is provided, this will override the default type icon. Additionally, setting the <code>icon</code> prop to false will remove the icon altogether.</p>
|
||||
|
||||
<DemoAlertIcons />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Border -->
|
||||
<AppCardCode
|
||||
title="Border"
|
||||
:code="demoCode.border"
|
||||
>
|
||||
<p>The <code>border</code> prop adds a simple border to one of the 4 sides of the alert. This can be combined with props like <code>color</code>, <code>type</code> and <code>icon</code> to provide unique accents to the alert.</p>
|
||||
|
||||
<DemoAlertBorder />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Colored Border -->
|
||||
<AppCardCode
|
||||
title="Colored Border"
|
||||
:code="demoCode.coloredBorder"
|
||||
>
|
||||
<p>The <code>colored-border</code> prop removes the alert background in order to accent the <code>border</code> prop. If a type is set, it will use the type's default color. If no <code>color</code> or <code>type</code> is set, the color will default to the inverted color of the applied theme (black for light and white/gray for dark).</p>
|
||||
|
||||
<DemoAlertColoredBorder />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Density -->
|
||||
<AppCardCode
|
||||
title="Density"
|
||||
:code="demoCode.density"
|
||||
>
|
||||
<p>The <code>density</code> prop decreases the height of the alert based upon 1 of 3 levels of density. <code>default</code>, <code>comfortable</code>, and <code>compact</code>.</p>
|
||||
|
||||
<DemoAlertDensity />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Type -->
|
||||
<AppCardCode
|
||||
title="Type"
|
||||
:code="demoCode.type"
|
||||
>
|
||||
<p>The <code>type</code> prop provides 4 default v-alert styles: <code>success</code>, <code>info</code>, <code>warning</code>, and <code>error</code>. Each of these styles provide a default icon and color.</p>
|
||||
|
||||
<DemoAlertType />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Closable -->
|
||||
<AppCardCode
|
||||
title="Closable"
|
||||
:code="demoCode.closable"
|
||||
>
|
||||
<p>The <code>closable</code> prop adds a close button to the end of the alert component. Clicking this button will set its value to false and effectively hide the alert.</p>
|
||||
|
||||
<DemoAlertClosable />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 V-model support -->
|
||||
<AppCardCode
|
||||
title="v-model support"
|
||||
:code="demoCode.vModelSupport"
|
||||
>
|
||||
<p>Clicking this button will set its value to <code>false</code> and effectively hide the alert. You can restore the alert by binding <code>v-model</code> and setting it to true.</p>
|
||||
|
||||
<DemoAlertVModelSupport />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Outlined -->
|
||||
<AppCardCode
|
||||
title="Outlined"
|
||||
:code="demoCode.outlined"
|
||||
>
|
||||
<p>The <code>variant="outlined"</code> prop inverts the style of an alert, inheriting the currently applied <code>color</code>, applying it to the text and border, and making its background transparent.</p>
|
||||
|
||||
<DemoAlertOutlined />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Tonal -->
|
||||
<AppCardCode
|
||||
title="Tonal"
|
||||
:code="demoCode.tonal"
|
||||
>
|
||||
<p>The <code>variant</code> prop provides an easy way to change the overall style of your alerts. The <code>variant="tonal"</code> prop is a simple alert variant that applies a reduced opacity background of the provided color.</p>
|
||||
|
||||
<DemoAlertTonal />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Elevation -->
|
||||
<AppCardCode
|
||||
title="Elevation"
|
||||
:code="demoCode.elevation"
|
||||
>
|
||||
<p>Use <code>elevation</code> prop to set a box-shadow to alert.</p>
|
||||
|
||||
<DemoAlertElevation />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Prominent -->
|
||||
<AppCardCode
|
||||
title="Prominent"
|
||||
:code="demoCode.prominent"
|
||||
>
|
||||
<p>The <code>prominent</code> prop provides a more pronounced alert by increasing the size of the icon.</p>
|
||||
|
||||
<DemoAlertProminent />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
112
resources/js/pages/components/avatar.vue
Normal file
112
resources/js/pages/components/avatar.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/components/avatar/demoCodeAvatar'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Colors -->
|
||||
<AppCardCode
|
||||
title="Colors"
|
||||
:code="demoCode.colors"
|
||||
>
|
||||
<p>Use <code>color</code> prop to change the background color of avatar.</p>
|
||||
|
||||
<DemoAvatarColors />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Initials -->
|
||||
<AppCardCode
|
||||
title="Tonal"
|
||||
:code="demoCode.tonal"
|
||||
>
|
||||
<p>Use <code>variant="tonal"</code> to create light background avatars.</p>
|
||||
|
||||
<DemoAvatarTonal />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
md="6"
|
||||
cols="12"
|
||||
>
|
||||
<!-- 👉 Sizes -->
|
||||
<AppCardCode
|
||||
title="Sizes"
|
||||
:code="demoCode.sizes"
|
||||
>
|
||||
<p>The <code>size</code> prop allows you to change the height and width of the avatar.</p>
|
||||
|
||||
<DemoAvatarSizes />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Icons -->
|
||||
<AppCardCode
|
||||
title="Icons"
|
||||
:code="demoCode.icons"
|
||||
>
|
||||
<p>You can use <code>icon</code> prop of <code>v-avatar</code> component for rendering icons.</p>
|
||||
|
||||
<DemoAvatarIcons />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Rounded -->
|
||||
<AppCardCode
|
||||
title="Rounded"
|
||||
:code="demoCode.rounded"
|
||||
>
|
||||
<p>The <code>rounded</code> prop can be used to change the border radius of <code>v-avatar</code>.</p>
|
||||
|
||||
<DemoAvatarRounded />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Images -->
|
||||
<AppCardCode
|
||||
title="Images"
|
||||
:code="demoCode.images"
|
||||
>
|
||||
<p>You can use <code>image</code> prop of <code>v-avatar</code> component for rendering image.</p>
|
||||
|
||||
<DemoAvatarImages />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Group -->
|
||||
<AppCardCode
|
||||
title="Group"
|
||||
:code="demoCode.group"
|
||||
>
|
||||
<p>Use <code>v-avatar-group</code> class as a wrapper of avatars.</p>
|
||||
|
||||
<DemoAvatarGroup />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
157
resources/js/pages/components/badge.vue
Normal file
157
resources/js/pages/components/badge.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/components/badge/demoCodeBadge'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Style -->
|
||||
<AppCardCode
|
||||
title="Style"
|
||||
:code="demoCode.style"
|
||||
>
|
||||
<p>You can use various props like <code>bordered</code>, <code>dot</code>, <code>inline</code>, <code>rounded</code> etc. to style the badge.</p>
|
||||
|
||||
<DemoBadgeStyle />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Color -->
|
||||
<AppCardCode
|
||||
title="Color"
|
||||
:code="demoCode.color"
|
||||
>
|
||||
<p>Use <code>color</code> prop to create various background badges.</p>
|
||||
|
||||
<DemoBadgeColor />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Position -->
|
||||
<AppCardCode
|
||||
title="Position"
|
||||
:code="demoCode.position"
|
||||
>
|
||||
<p>You can use <code>location</code> prop to change the position of the badge. Possible values are <code>top-end</code>, <code>bottom-end</code>, <code>bottom-start</code>, <code>top-start</code>.</p>
|
||||
|
||||
<DemoBadgePosition />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Icon -->
|
||||
<AppCardCode
|
||||
title="Icon"
|
||||
:code="demoCode.icon"
|
||||
>
|
||||
<p>You can use <code>icon</code> prop or use <code>slot</code> to render the icon</p>
|
||||
|
||||
<DemoBadgeIcon />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Avatar Status -->
|
||||
<AppCardCode
|
||||
title="Avatar Status"
|
||||
:code="demoCode.avatarStatus"
|
||||
>
|
||||
<p>You can use badge with avatar as status.</p>
|
||||
|
||||
<DemoBadgeAvatarStatus />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Dynamic notifications -->
|
||||
<AppCardCode
|
||||
title="Dynamic notifications"
|
||||
:code="demoCode.dynamicNotifications"
|
||||
>
|
||||
<p>You can incorporate badges with dynamic content to make things such as a notification system.</p>
|
||||
|
||||
<DemoBadgeDynamicNotifications />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Show on hover -->
|
||||
<AppCardCode
|
||||
title="Show on hover"
|
||||
:code="demoCode.showOnHover"
|
||||
>
|
||||
<p>You can do many things with visibility control, for example, show badge on hover.</p>
|
||||
|
||||
<DemoBadgeShowOnHover />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Tabs -->
|
||||
<AppCardCode
|
||||
title="Tabs"
|
||||
:code="demoCode.tabs"
|
||||
>
|
||||
<p>Badges help convey information to the user in a variety of ways.</p>
|
||||
|
||||
<DemoBadgeTabs />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Maximum value content -->
|
||||
<AppCardCode
|
||||
title="Maximum Value"
|
||||
:code="demoCode.maximumValue"
|
||||
>
|
||||
<p>Use <code>max</code> prop to cap the value of the badge content</p>
|
||||
|
||||
<DemoBadgeMaximumValue />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Tonal Badge -->
|
||||
<AppCardCode
|
||||
title="Tonal"
|
||||
:code="demoCode.tonal"
|
||||
>
|
||||
<p>Use class <code>v-badge--tonal</code> for using tonal variant badge.</p>
|
||||
|
||||
<DemoBadgeTonal />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
189
resources/js/pages/components/button.vue
Normal file
189
resources/js/pages/components/button.vue
Normal file
@@ -0,0 +1,189 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/components/button/demoCodeButton'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Colors -->
|
||||
<AppCardCode
|
||||
title="Colors"
|
||||
:code="demoCode.colors"
|
||||
>
|
||||
<p>The <code>color</code> prop is used to change the background color of the alert.</p>
|
||||
<DemoButtonColors />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Outlined -->
|
||||
<AppCardCode
|
||||
title="Outlined"
|
||||
:code="demoCode.outlined"
|
||||
>
|
||||
<p>The <code>outlined</code> variant option is used to create outlined buttons.</p>
|
||||
<DemoButtonOutlined />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Flat -->
|
||||
<AppCardCode
|
||||
title="Flat"
|
||||
:code="demoCode.flat"
|
||||
>
|
||||
<p>The <code>flat</code> buttons still maintain their background color, but have no box shadow.</p>
|
||||
<DemoButtonFlat />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Rounded -->
|
||||
<AppCardCode
|
||||
title="Rounded"
|
||||
:code="demoCode.rounded"
|
||||
>
|
||||
<p>Use the <code>rounded</code> prop to control the border radius of buttons.</p>
|
||||
<DemoButtonRounded />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Text -->
|
||||
<AppCardCode
|
||||
title="Text"
|
||||
:code="demoCode.text"
|
||||
>
|
||||
<p>Use <code>text</code> variant option to create text button. Text buttons have no box shadow and no background.</p>
|
||||
<DemoButtonText />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Plain -->
|
||||
<AppCardCode
|
||||
title="Plain"
|
||||
:code="demoCode.plain"
|
||||
>
|
||||
<p>Use <code>plain</code> variant option to a create a plain button. Plain buttons have a lower baseline opacity that reacts to hover and focus.</p>
|
||||
<DemoButtonPlain />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Tonal -->
|
||||
<AppCardCode
|
||||
title="Tonal"
|
||||
:code="demoCode.tonal"
|
||||
>
|
||||
<p>Use <code>tonal</code> variant option to a create a light background button.</p>
|
||||
<DemoButtonTonal />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Icon -->
|
||||
<AppCardCode
|
||||
title="Icon"
|
||||
:code="demoCode.icon"
|
||||
>
|
||||
<p>Icons can be used inside of buttons to add emphasis to the action.</p>
|
||||
<DemoButtonIcon />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Icon Only -->
|
||||
<AppCardCode
|
||||
title="Icon Only"
|
||||
:code="demoCode.iconOnly"
|
||||
>
|
||||
<p>Use <code>VIcon</code> component inside button to create buttons that looks like rest of the theme.</p>
|
||||
<DemoButtonIconOnly />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Sizing -->
|
||||
<AppCardCode
|
||||
title="Sizing"
|
||||
:code="demoCode.sizing"
|
||||
>
|
||||
<p>Buttons can be given different sizing options to fit a multitude of scenarios.</p>
|
||||
<DemoButtonSizing />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Block -->
|
||||
<AppCardCode
|
||||
title="Block"
|
||||
:code="demoCode.block"
|
||||
>
|
||||
<p>The <code>block</code> prop allows buttons to extend the full available width.</p>
|
||||
<DemoButtonBlock />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Loaders -->
|
||||
<AppCardCode
|
||||
title="Loaders"
|
||||
:code="demoCode.loaders"
|
||||
>
|
||||
<p>Using the <code>loading</code> prop, you can notify a user that there is processing taking place. The default behavior is to use a <code>v-progress-circular</code> component but this can be customized.</p>
|
||||
|
||||
<DemoButtonLoaders />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Router -->
|
||||
<AppCardCode
|
||||
title="Router"
|
||||
:code="demoCode.router"
|
||||
>
|
||||
<p>Use <code>to</code> prop to create button with router support.</p>
|
||||
<VAlert
|
||||
color="warning"
|
||||
variant="tonal"
|
||||
class="mb-4"
|
||||
>
|
||||
Note: On click of the link button, You will get redirected to another page.
|
||||
</VAlert>
|
||||
<DemoButtonRouter />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Link -->
|
||||
<AppCardCode
|
||||
title="Link"
|
||||
:code="demoCode.link"
|
||||
>
|
||||
<p>Designates that the component is a link. This is automatic when using the <code>href</code> or <code>to</code> prop.</p>
|
||||
<VAlert
|
||||
color="warning"
|
||||
variant="tonal"
|
||||
class="mb-4"
|
||||
>
|
||||
Note: On click of the link button, You will get redirected to another page.
|
||||
</VAlert>
|
||||
<DemoButtonLink />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Button Group -->
|
||||
<AppCardCode
|
||||
title="Group"
|
||||
:code="demoCode.group"
|
||||
>
|
||||
<p>
|
||||
Wrap buttons with the <code>v-btn-toggle</code> component to create a group button. You can add a visual divider between buttons with the <code>divided</code> prop.
|
||||
</p>
|
||||
<DemoButtonGroup />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
157
resources/js/pages/components/chip.vue
Normal file
157
resources/js/pages/components/chip.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/components/chip/demoCodeChip'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Color -->
|
||||
<AppCardCode
|
||||
title="Color"
|
||||
:code="demoCode.color"
|
||||
>
|
||||
<p>Use <code>color</code> prop to change the background color of chips.</p>
|
||||
|
||||
<DemoChipColor />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Elevated -->
|
||||
<AppCardCode
|
||||
title="Elevated"
|
||||
:code="demoCode.elevated"
|
||||
>
|
||||
<p>Use <code>elevated</code> variant option to create filled chips.</p>
|
||||
|
||||
<DemoChipElevated />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Outlined -->
|
||||
<AppCardCode
|
||||
title="Outlined"
|
||||
:code="demoCode.outlined"
|
||||
>
|
||||
<p>Use <code>outlined</code> variant option to create outline border chips.</p>
|
||||
|
||||
<DemoChipOutlined />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Label -->
|
||||
<AppCardCode
|
||||
title="Rounded"
|
||||
:code="demoCode.rounded"
|
||||
>
|
||||
<p>To use the rounded chip, set <code>label</code> props value to <strong>false</strong>.</p>
|
||||
|
||||
<DemoChipRounded />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Closable -->
|
||||
<AppCardCode
|
||||
title="Closable"
|
||||
:code="demoCode.closable"
|
||||
>
|
||||
<p>Closable chips can be controlled with a <code>v-model</code>.</p>
|
||||
|
||||
<DemoChipClosable />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Chip With Icon -->
|
||||
<AppCardCode
|
||||
title="With Icon"
|
||||
:code="demoCode.withIcon"
|
||||
>
|
||||
<p>Chips can use text or any icon available in the Material Icons font library.</p>
|
||||
|
||||
<DemoChipWithIcon />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Avatar -->
|
||||
<AppCardCode
|
||||
title="With Avatar"
|
||||
:code="demoCode.withAvatar"
|
||||
>
|
||||
<p>Use <code>pill</code> prop to remove the <code>v-avatar</code> padding.</p>
|
||||
|
||||
<DemoChipWithAvatar />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Sizes -->
|
||||
<AppCardCode
|
||||
title="Sizes"
|
||||
:code="demoCode.sizes"
|
||||
>
|
||||
<p>The <code>v-chip</code> component can have various sizes from <code>x-small</code> to <code>x-large</code>.</p>
|
||||
|
||||
<DemoChipSizes />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 In Selects -->
|
||||
<AppCardCode
|
||||
title="In Selects"
|
||||
:code="demoCode.inSelects"
|
||||
>
|
||||
<p>Selects can use <code>chips</code> to display the selected data. Try adding your own tags below.</p>
|
||||
|
||||
<DemoChipInSelects />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Expandable -->
|
||||
<AppCardCode
|
||||
title="Expandable"
|
||||
:code="demoCode.expandable"
|
||||
>
|
||||
<p>Chips can be combined with <code>v-menu</code> to enable a specific set of actions for a chip.</p>
|
||||
|
||||
<DemoChipExpandable />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
127
resources/js/pages/components/dialog.vue
Normal file
127
resources/js/pages/components/dialog.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/components/dialog/demoCodeDialog'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<p>A dialog contains two slots, one for its activator and one for its content (default). Good for Privacy Policies.</p>
|
||||
|
||||
<DemoDialogBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Persistent -->
|
||||
<AppCardCode
|
||||
title="Persistent"
|
||||
:code="demoCode.persistent"
|
||||
>
|
||||
<p>Use <code>persistent</code> prop to create persistent dialog.</p>
|
||||
|
||||
<DemoDialogPersistent />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Scrollable -->
|
||||
<AppCardCode
|
||||
title="Scrollable"
|
||||
:code="demoCode.scrollable"
|
||||
>
|
||||
<p>Use <code>scrollable</code> prop to create scrollable dialog.</p>
|
||||
|
||||
<DemoDialogScrollable />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Loader -->
|
||||
<AppCardCode
|
||||
title="Loader"
|
||||
:code="demoCode.loader"
|
||||
>
|
||||
<p>The <code>v-dialog</code> component makes it easy to create a customized loading experience for your application.</p>
|
||||
|
||||
<DemoDialogLoader />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
col="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Nesting -->
|
||||
<AppCardCode
|
||||
title="Nesting"
|
||||
:code="demoCode.nesting"
|
||||
>
|
||||
<p>Dialogs can be nested: you can open one dialog from another.</p>
|
||||
|
||||
<DemoDialogNesting />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Overflowed -->
|
||||
<AppCardCode
|
||||
title="Overflowed"
|
||||
:code="demoCode.overflowed"
|
||||
>
|
||||
<p>Modals that do not fit within the available window space will scroll the container.</p>
|
||||
|
||||
<DemoDialogOverflowed />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Fullscreen -->
|
||||
<AppCardCode
|
||||
title="Fullscreen"
|
||||
:code="demoCode.fullscreen"
|
||||
>
|
||||
<p>Due to limited space, full-screen dialogs may be more appropriate for mobile devices than dialogs used on devices with larger screens.</p>
|
||||
|
||||
<DemoDialogFullscreen />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Form -->
|
||||
<AppCardCode
|
||||
title="Form"
|
||||
:code="demoCode.form"
|
||||
>
|
||||
<p>Just a simple example of a form in a dialog.</p>
|
||||
|
||||
<DemoDialogForm />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
121
resources/js/pages/components/expansion-panel.vue
Normal file
121
resources/js/pages/components/expansion-panel.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/components/expansion-panel/demoCodeExpansionPanel'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
variant="outlined"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<p>Expansion panels in their simplest form display a list of expandable items. However, with the <code>multiple</code> prop, the expansion-panel can remain open until explicitly closed.</p>
|
||||
|
||||
<DemoExpansionPanelBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Accordion -->
|
||||
<AppCardCode
|
||||
title="Accordion"
|
||||
variant="outlined"
|
||||
:code="demoCode.accordion"
|
||||
>
|
||||
<p>Use <code>accordion</code> variant option to create <strong>Accordion</strong> Panels. Accordion expansion-panel hasn't got margins around active panel.</p>
|
||||
|
||||
<DemoExpansionPanelAccordion />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Inset -->
|
||||
<AppCardCode
|
||||
title="Inset"
|
||||
variant="outlined"
|
||||
:code="demoCode.inset"
|
||||
>
|
||||
<p>Use <code>inset</code> variant option to create Inset Panels. The Inset expansion-panel becomes smaller when activated.</p>
|
||||
|
||||
<DemoExpansionPanelInset />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Popout -->
|
||||
<AppCardCode
|
||||
title="Popout"
|
||||
variant="outlined"
|
||||
:code="demoCode.popout"
|
||||
>
|
||||
<p>
|
||||
Use <code>popout</code> variant option to create expansion-panel with popout design. With it, expansion-panel is enlarged when activated.
|
||||
</p>
|
||||
|
||||
<DemoExpansionPanelPopout />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Custom Icon -->
|
||||
<AppCardCode
|
||||
title="Custom Icon"
|
||||
variant="outlined"
|
||||
:code="demoCode.customIcon"
|
||||
>
|
||||
<p>Expand action icon can be customized with <code>expand-icon</code> prop or the <code>actions</code> slot. Also, use the <code>.no-icon-rotate</code> class in conjunction with the <code>VExpansionPanels</code> component to disable icon rotation.</p>
|
||||
|
||||
<DemoExpansionPanelCustomIcon />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Model -->
|
||||
<AppCardCode
|
||||
title="Model"
|
||||
variant="outlined"
|
||||
:code="demoCode.model"
|
||||
>
|
||||
<p>Expansion panels can be controlled externally by modifying the <code>v-model</code>. If <code>multiple</code> prop is used then it is an array containing the indices of the open items.</p>
|
||||
|
||||
<DemoExpansionPanelModel />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 With Border -->
|
||||
<AppCardCode
|
||||
title="With Border"
|
||||
variant="outlined"
|
||||
:code="demoCode.withBorder"
|
||||
>
|
||||
<p>Please use the <code>.expansion-panels-width-border</code> class in conjunction with the <code>VExpansionPanels</code> component to create panels with borders.</p>
|
||||
|
||||
<DemoExpansionPanelWithBorder />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
205
resources/js/pages/components/list.vue
Normal file
205
resources/js/pages/components/list.vue
Normal file
@@ -0,0 +1,205 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/components/list/demoCodeList'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
no-padding
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<VCardText><code>v-list</code> component can contain an avatar, content, actions and much more.</VCardText>
|
||||
|
||||
<VCardText>
|
||||
<DemoListBasic />
|
||||
</VCardText>
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Rounded -->
|
||||
<AppCardCode
|
||||
title="Rounded"
|
||||
no-padding
|
||||
:code="demoCode.rounded"
|
||||
>
|
||||
<VCardText>You can make <code>v-list-item</code> rounded using <code>rounded</code> prop.</VCardText>
|
||||
|
||||
<VCardText>
|
||||
<DemoListRounded />
|
||||
</VCardText>
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Density -->
|
||||
<AppCardCode
|
||||
title="Density"
|
||||
:code="demoCode.density"
|
||||
no-padding
|
||||
>
|
||||
<VCardText>Use <code>density</code> prop to adjusts the spacing within the component. Available options are: <code>default</code>, <code>comfortable</code>, and <code>compact</code>.</VCardText>
|
||||
|
||||
<VCardText>
|
||||
<DemoListDensity />
|
||||
</VCardText>
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Nav -->
|
||||
<AppCardCode
|
||||
title="Nav"
|
||||
no-padding
|
||||
:code="demoCode.nav"
|
||||
>
|
||||
<VCardText>Lists can receive an alternative <code>nav</code> styling that reduces the width <code>v-list-item</code> takes up as well as adding a border radius.</VCardText>
|
||||
|
||||
<VCardText>
|
||||
<DemoListNav />
|
||||
</VCardText>
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Action and item group -->
|
||||
<AppCardCode
|
||||
title="Action and item group"
|
||||
no-padding
|
||||
:code="demoCode.actionAndItemGroup"
|
||||
>
|
||||
<VCardText>A <code>three-line</code> list with actions. Utilizing <code>v-list-group</code>, easily connect actions to your tiles.</VCardText>
|
||||
|
||||
<VCardText>
|
||||
<DemoListActionAndItemGroup />
|
||||
</VCardText>
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Sub Group -->
|
||||
<AppCardCode
|
||||
title="Sub Group"
|
||||
no-padding
|
||||
:code="demoCode.subGroup"
|
||||
>
|
||||
<VCardText>
|
||||
Using the <code>v-list-group</code> component you can create up to 2 levels in depth using the sub-group prop.
|
||||
</VCardText>
|
||||
|
||||
<VCardText>
|
||||
<DemoListSubGroup />
|
||||
</VCardText>
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Two lines and Subheader -->
|
||||
<AppCardCode
|
||||
title="Two lines and subheader"
|
||||
no-padding
|
||||
:code="demoCode.twoLinesAndSubheader"
|
||||
>
|
||||
<VCardText>Lists can contain subheaders, dividers, and can contain 1 or more lines. The subtitle will overflow with ellipsis if it extends past one line.</VCardText>
|
||||
|
||||
<VCardText>
|
||||
<DemoListTwoLinesAndSubheader />
|
||||
</VCardText>
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Three Line -->
|
||||
<AppCardCode
|
||||
title="Three Line"
|
||||
no-padding
|
||||
:code="demoCode.threeLine"
|
||||
>
|
||||
<VCardText>For three line lists, the subtitle will clamp vertically at 2 lines and then ellipsis. This feature uses line-clamp and is not supported in all browsers.</VCardText>
|
||||
|
||||
<VCardText>
|
||||
<DemoListThreeLine />
|
||||
</VCardText>
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Users List -->
|
||||
<AppCardCode
|
||||
title="User List"
|
||||
no-padding
|
||||
:code="demoCode.userList"
|
||||
>
|
||||
<VCardText>
|
||||
<DemoListUserList />
|
||||
</VCardText>
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Progress List -->
|
||||
<AppCardCode
|
||||
title="Progress List"
|
||||
no-padding
|
||||
:code="demoCode.progressList"
|
||||
>
|
||||
<VCardText>
|
||||
<DemoListProgressList />
|
||||
</VCardText>
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Shaped -->
|
||||
<AppCardCode
|
||||
title="Shaped"
|
||||
no-padding
|
||||
:code="demoCode.shaped"
|
||||
>
|
||||
<VCardText>
|
||||
Shaped lists have rounded borders on one side of the <code>v-list-item</code>.
|
||||
</VCardText>
|
||||
|
||||
<VCardText>
|
||||
<DemoListShaped />
|
||||
</VCardText>
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
99
resources/js/pages/components/menu.vue
Normal file
99
resources/js/pages/components/menu.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/components/menu/demoCodeMenu'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<p>
|
||||
Remember to put the element that activates the menu in the activator slot.
|
||||
</p>
|
||||
|
||||
<DemoMenuBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Custom transitions -->
|
||||
<AppCardCode
|
||||
title="Custom transitions"
|
||||
:code="demoCode.customTransitions"
|
||||
>
|
||||
<p>Vuetify comes with 3 standard transitions, <code>scale</code>, <code>slide-x</code> and <code>slide-y</code>. Use <code>transition</code> prop to add transition to a menu.</p>
|
||||
|
||||
<DemoMenuCustomTransitions />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Location -->
|
||||
<AppCardCode
|
||||
title="Location"
|
||||
:code="demoCode.location"
|
||||
>
|
||||
<p>Menu can be offset relative to the activator by using the <code>location</code> prop.</p>
|
||||
|
||||
<DemoMenuLocation />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Open on hover -->
|
||||
<AppCardCode
|
||||
title="Open on hover"
|
||||
:code="demoCode.openOnHover"
|
||||
>
|
||||
<p>Menus can be accessed using hover instead of clicking with the <code>open-on-hover</code> prop.</p>
|
||||
|
||||
<DemoMenuOpenOnHover />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Popover -->
|
||||
<AppCardCode
|
||||
title="Popover"
|
||||
:code="demoCode.popover"
|
||||
>
|
||||
<p>A menu can be configured to be static when opened, allowing it to function as a popover. This can be useful when there are multiple interactive items within the menu contents.</p>
|
||||
|
||||
<DemoMenuPopover />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Activator and tooltip -->
|
||||
<AppCardCode
|
||||
title="Activator and tooltip"
|
||||
:code="demoCode.activatorAndTooltip"
|
||||
>
|
||||
<p>With the new <code>v-slot</code> syntax, nested activators such as those seen with a <code>v-menu</code> and <code>v-tooltip</code> attached to the same activator button, need a particular setup in order to function correctly</p>
|
||||
|
||||
<DemoMenuActivatorAndTooltip />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
157
resources/js/pages/components/pagination.vue
Normal file
157
resources/js/pages/components/pagination.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/components/pagination/demoCodePagination'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<p>The <code>v-pagination</code> component is used to separate long sets of data.</p>
|
||||
|
||||
<DemoPaginationBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 outline basic variant -->
|
||||
<AppCardCode
|
||||
title="Outline"
|
||||
:code="demoCode.outline"
|
||||
>
|
||||
<p>The <code>variant='outline'</code> prop is used to give outline to pagination item.</p>
|
||||
|
||||
<DemoPaginationOutline />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Circle -->
|
||||
<AppCardCode
|
||||
title="Circle"
|
||||
:code="demoCode.circle"
|
||||
>
|
||||
<p>The <code>rounded</code> prop allows you to render pagination buttons with alternative styles.</p>
|
||||
|
||||
<DemoPaginationCircle />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 outline circle variant -->
|
||||
<AppCardCode
|
||||
title="Outline Circle"
|
||||
:code="demoCode.outlineCircle"
|
||||
>
|
||||
<p>The <code>variant='outline'</code> and <code>rounded</code> prop is used to give rounded outline to pagination item.</p>
|
||||
|
||||
<DemoPaginationOutlineCircle />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Disabled -->
|
||||
<AppCardCode
|
||||
title="Disabled"
|
||||
:code="demoCode.disabled"
|
||||
>
|
||||
<p>Pagination items can be manually deactivated using the <code>disabled</code> prop.</p>
|
||||
|
||||
<DemoPaginationDisabled />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Icons -->
|
||||
<AppCardCode
|
||||
title="Icons"
|
||||
:code="demoCode.icons"
|
||||
>
|
||||
<p>Previous and next page icons can be customized with the <code>prev-icon</code> and <code>next-icon</code> props.</p>
|
||||
|
||||
<DemoPaginationIcons />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Length -->
|
||||
<AppCardCode
|
||||
title="Length"
|
||||
:code="demoCode.length"
|
||||
>
|
||||
<p>Using the <code>length</code> prop you can set the length of <code>v-pagination</code>, if the number of page buttons exceeds the parent container, it will truncate the list.</p>
|
||||
|
||||
<DemoPaginationLength />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Total visible -->
|
||||
<AppCardCode
|
||||
title="Total visible"
|
||||
:code="demoCode.totalVisible"
|
||||
>
|
||||
<p>You can also manually set the maximum number of visible page buttons with the <code>total-visible</code> prop.</p>
|
||||
|
||||
<DemoPaginationTotalVisible />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Color -->
|
||||
<AppCardCode
|
||||
title="Color"
|
||||
:code="demoCode.color"
|
||||
>
|
||||
<p>Use <code>active-color</code> prop for create different color pagination.</p>
|
||||
|
||||
<DemoPaginationColor />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Size -->
|
||||
<AppCardCode
|
||||
title="Size"
|
||||
:code="demoCode.size"
|
||||
>
|
||||
<p>Use <code>size</code> prop to sets the height and width of the component. Default unit is px. Can also use the following predefined sizes: <strong>x-small</strong>, <strong>small</strong>, <strong>default</strong>, <strong>large</strong>, and <strong>x-large</strong>.</p>
|
||||
|
||||
<DemoPaginationSize />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
67
resources/js/pages/components/progress-circular.vue
Normal file
67
resources/js/pages/components/progress-circular.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/components/progress-circular/demoCodeProgressCircular'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<!-- 👉 Progress circular color -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="color"
|
||||
:code="demoCode.color"
|
||||
>
|
||||
<p>Alternate colors can be applied to <code>v-progress-circular</code> using the <code>color</code> prop.</p>
|
||||
|
||||
<DemoProgressCircularColor />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Progress circular Indeterminate -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Indeterminate"
|
||||
:code="demoCode.indeterminate"
|
||||
>
|
||||
<p>Using the <code>indeterminate</code> prop, a <code>v-progress-circular</code> continues to animate indefinitely.</p>
|
||||
|
||||
<DemoProgressCircularIndeterminate />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Progress circular Rotate -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Rotate"
|
||||
:code="demoCode.rotate"
|
||||
>
|
||||
<p>The <code>rotate</code> prop gives you the ability to customize the <code>v-progress-circular</code>'s origin.</p>
|
||||
|
||||
<DemoProgressCircularRotate />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Progress circular Size and Width -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Size"
|
||||
:code="demoCode.size"
|
||||
>
|
||||
<p>The <code>size</code> and <code>width</code> props allow you to easily alter the size and width of the <code>v-progress-circular</code> component.</p>
|
||||
|
||||
<DemoProgressCircularSize />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
113
resources/js/pages/components/progress-linear.vue
Normal file
113
resources/js/pages/components/progress-linear.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/components/progress-linear/demoCodeProgressLinear'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<!-- 👉 Progress linear color -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Color"
|
||||
:code="demoCode.color"
|
||||
>
|
||||
<p>Use the props <code>color</code> and <code>background-color</code> to set colors.</p>
|
||||
|
||||
<DemoProgressLinearColor />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Progress linear Buffering -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Buffering"
|
||||
:code="demoCode.buffering"
|
||||
>
|
||||
<p>The primary value is controlled by <code>v-model</code>, whereas the buffer is controlled by the <code>buffer-value</code> prop.</p>
|
||||
|
||||
<DemoProgressLinearBuffering />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Progress linear indeterminate -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Indeterminate"
|
||||
:code="demoCode.indeterminate"
|
||||
>
|
||||
<p>for continuously animating progress bar,use prop <code>indeterminate</code>. This indicates continuous process. </p>
|
||||
|
||||
<DemoProgressLinearIndeterminate />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Progress linear Reversed -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Reversed"
|
||||
:code="demoCode.reversed"
|
||||
>
|
||||
<p>Use prop <code>reverse</code> to animate continuously in reverse direction. The component also has RTL support.</p>
|
||||
|
||||
<DemoProgressLinearReversed />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Progress linear Rounded -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Rounded"
|
||||
:code="demoCode.rounded"
|
||||
>
|
||||
<p>
|
||||
The <code> rounded </code>prop is used to apply a border radius to the v-progress-linear component.
|
||||
By default we have set <code>rounded</code> prop true. You can disable it by using <code>:rounded='false'</code>.
|
||||
</p>
|
||||
<DemoProgressLinearRounded />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Progress linear Slot -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Slots"
|
||||
:code="demoCode.slots"
|
||||
>
|
||||
<p>The v-progress-linear component will be responsive to user input when using <code>v-model</code>. You can use the default slot or bind a local model to display inside of the progress.</p>
|
||||
|
||||
<DemoProgressLinearSlots />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Progress Linear Striped -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Striped"
|
||||
:code="demoCode.striped"
|
||||
>
|
||||
<p> The <code>striped</code> prop is used to apply striped background.</p>
|
||||
<DemoProgressLinearStriped />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
126
resources/js/pages/components/snackbar.vue
Normal file
126
resources/js/pages/components/snackbar.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/components/snackbar/demoCodeSnackbar'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<p>The <code>v-snackbar</code> component is used to display a quick message to a user. Snackbars support positioning, removal delay, and callbacks.</p>
|
||||
|
||||
<DemoSnackbarBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 With Action -->
|
||||
<AppCardCode
|
||||
title="With Action"
|
||||
:code="demoCode.withAction"
|
||||
>
|
||||
<p>Use <code>actions</code> slot to add action button. A <code>v-snackbar</code> in its simplest form displays a temporary and closable notification to the user.</p>
|
||||
|
||||
<DemoSnackbarWithAction />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Multi line -->
|
||||
<AppCardCode
|
||||
title="Multi Line"
|
||||
:code="demoCode.multiLine"
|
||||
>
|
||||
<p>The <code>multi-line</code> property extends the height of the <code>v-snackbar</code> to give you a little more room for content.</p>
|
||||
|
||||
<DemoSnackbarMultiLine />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Timeout -->
|
||||
<AppCardCode
|
||||
title="Timeout"
|
||||
:code="demoCode.timeout"
|
||||
>
|
||||
<p>The <code>timeout</code> property lets you customize the delay before the <code>v-snackbar</code> is hidden.</p>
|
||||
|
||||
<DemoSnackbarTimeout />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Vertical -->
|
||||
<AppCardCode
|
||||
title="Vertical"
|
||||
:code="demoCode.vertical"
|
||||
>
|
||||
<p>The <code>vertical</code> property allows you to stack the content of your <code>v-snackbar</code>.</p>
|
||||
|
||||
<DemoSnackbarVertical />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Position -->
|
||||
<AppCardCode
|
||||
title="Position"
|
||||
:code="demoCode.position"
|
||||
>
|
||||
<p>Use <code>location</code> prop to change the position of snackbar.</p>
|
||||
|
||||
<DemoSnackbarPosition />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Variants -->
|
||||
<AppCardCode
|
||||
title="Variants"
|
||||
:code="demoCode.variants"
|
||||
>
|
||||
<p>Apply different styles to the snackbar using props such as <code>shaped</code>, <code>rounded</code>, <code>color</code>, <code>text</code>, <code>outlined</code>, <code>tile</code> and more.</p>
|
||||
|
||||
<DemoSnackbarVariants />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Transition -->
|
||||
<AppCardCode
|
||||
title="Transition"
|
||||
:code="demoCode.transition"
|
||||
>
|
||||
<p>Use transition prop to sets the component transition.</p>
|
||||
|
||||
<DemoSnackbarTransition />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
197
resources/js/pages/components/tabs.vue
Normal file
197
resources/js/pages/components/tabs.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/components/tabs/demoCodeTabs'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
variant="outlined"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<p>The <code>v-tabs</code> component is used for hiding content behind a selectable item.</p>
|
||||
|
||||
<DemoTabsBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Stacked -->
|
||||
<AppCardCode
|
||||
title="Stacked"
|
||||
variant="outlined"
|
||||
:code="demoCode.stacked"
|
||||
>
|
||||
<p>Using <code>stacked</code> prop you can have buttons that use both icons and text.</p>
|
||||
|
||||
<DemoTabsStacked />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Vertical -->
|
||||
<AppCardCode
|
||||
title="Vertical"
|
||||
variant="outlined"
|
||||
:code="demoCode.vertical"
|
||||
>
|
||||
<p>The <code>vertical</code> prop allows for <code>v-tab</code> components to stack vertically.</p>
|
||||
|
||||
<DemoTabsVertical />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Alignment -->
|
||||
<AppCardCode
|
||||
title="Alignment"
|
||||
variant="outlined"
|
||||
:code="demoCode.alignment"
|
||||
>
|
||||
<p>Use <code>align-tabs</code> prop to change the tabs alignment.</p>
|
||||
|
||||
<DemoTabsAlignment />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Pagination -->
|
||||
<AppCardCode
|
||||
title="Pagination"
|
||||
variant="outlined"
|
||||
:code="demoCode.pagination"
|
||||
>
|
||||
<p>If the tab items overflow their container, pagination controls will appear on desktop.</p>
|
||||
|
||||
<DemoTabsPagination />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Custom Icons -->
|
||||
<AppCardCode
|
||||
title="Custom Icons"
|
||||
variant="outlined"
|
||||
:code="demoCode.customIcons"
|
||||
>
|
||||
<p><code>prev-icon</code> and <code>next-icon</code> props can be used for applying custom pagination icons.</p>
|
||||
|
||||
<DemoTabsCustomIcons />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Fixed tabs -->
|
||||
<AppCardCode
|
||||
title="Fixed"
|
||||
variant="outlined"
|
||||
:code="demoCode.fixed"
|
||||
>
|
||||
<p>The <code>fixed-tabs</code> prop forces <code>v-tab</code> to take up all available space up to the maximum width (300px).</p>
|
||||
|
||||
<DemoTabsFixed />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Grow -->
|
||||
<AppCardCode
|
||||
title="Grow"
|
||||
variant="outlined"
|
||||
:code="demoCode.grow"
|
||||
>
|
||||
<p>The <code>grow</code> prop will make the tab items take up all available space with no limit.</p>
|
||||
|
||||
<DemoTabsGrow />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Programmatic Navigation -->
|
||||
<AppCardCode
|
||||
title="Programmatic Navigation"
|
||||
variant="outlined"
|
||||
:code="demoCode.programmaticNavigation"
|
||||
>
|
||||
<DemoTabsProgrammaticNavigation />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Dynamic Tabs -->
|
||||
<AppCardCode
|
||||
title="Dynamic"
|
||||
variant="outlined"
|
||||
:code="demoCode.dynamic"
|
||||
>
|
||||
<p>Tabs can be dynamically added and removed. This allows you to update to any number and the <code>v-tabs</code> component will react.</p>
|
||||
|
||||
<DemoTabsDynamic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic Pill -->
|
||||
<AppCardCode
|
||||
title="Basic Pill"
|
||||
variant="outlined"
|
||||
:code="demoCode.basicPill"
|
||||
>
|
||||
<p>Use our custom class <code>.v-tabs-pill</code> along with <code>v-tabs</code> component to style pill tabs.</p>
|
||||
|
||||
<DemoTabsBasicPill />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Vertical Pill -->
|
||||
<AppCardCode
|
||||
title="Vertical Pill"
|
||||
variant="outlined"
|
||||
:code="demoCode.verticalPill"
|
||||
>
|
||||
<p>Use our custom class .v-tabs-pill along with v-tabs component to style pill tabs.</p>
|
||||
|
||||
<DemoTabsVerticalPill />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
21
resources/js/pages/components/timeline.vue
Normal file
21
resources/js/pages/components/timeline.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<TimelineBasic />
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<TimelineOutlined />
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<TimelineWithIcons />
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
77
resources/js/pages/components/tooltip.vue
Normal file
77
resources/js/pages/components/tooltip.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/components/tooltip/demoCodeTooltip'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Location -->
|
||||
<AppCardCode
|
||||
title="Location"
|
||||
:code="demoCode.location"
|
||||
>
|
||||
<p>Use the <code>location</code> prop to specify on which side of the element the tooltip should show</p>
|
||||
|
||||
<DemoTooltipLocation />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Events -->
|
||||
<AppCardCode
|
||||
title="Events"
|
||||
:code="demoCode.events"
|
||||
>
|
||||
<DemoTooltipEvents />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Delay On Hover -->
|
||||
<AppCardCode
|
||||
title="Delay On Hover"
|
||||
:code="demoCode.delayOnHover"
|
||||
>
|
||||
<p>Delay (in ms) after which tooltip opens (when <code>open-on-hover</code> prop is set to true)</p>
|
||||
|
||||
<DemoTooltipDelayOnHover />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 V-model Support -->
|
||||
<AppCardCode
|
||||
title="V-Model Support"
|
||||
:code="demoCode.vModelSupport"
|
||||
>
|
||||
<p>Tooltip visibility can be programmatically changed using <code>v-model</code>.</p>
|
||||
|
||||
<DemoTooltipVModelSupport />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Transition -->
|
||||
<AppCardCode
|
||||
title="Transition"
|
||||
:code="demoCode.transition"
|
||||
>
|
||||
<p>Use <code>transition</code> prop to sets the component transition.</p>
|
||||
|
||||
<DemoTooltipTransition />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Tooltip on Various Elements -->
|
||||
<AppCardCode
|
||||
title="Tooltip on Various Elements"
|
||||
:code="demoCode.tooltipOnVariousElements"
|
||||
>
|
||||
<p>Tooltips can wrap any element.</p>
|
||||
|
||||
<DemoTooltipTooltipOnVariousElements />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
106
resources/js/pages/dashboards/analytics.vue
Normal file
106
resources/js/pages/dashboards/analytics.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<script setup>
|
||||
import AnalyticsAverageDailySales from '@/views/dashboards/analytics/AnalyticsAverageDailySales.vue'
|
||||
import AnalyticsEarningReportsWeeklyOverview from '@/views/dashboards/analytics/AnalyticsEarningReportsWeeklyOverview.vue'
|
||||
import AnalyticsMonthlyCampaignState from '@/views/dashboards/analytics/AnalyticsMonthlyCampaignState.vue'
|
||||
import AnalyticsProjectTable from '@/views/dashboards/analytics/AnalyticsProjectTable.vue'
|
||||
import AnalyticsSalesByCountries from '@/views/dashboards/analytics/AnalyticsSalesByCountries.vue'
|
||||
import AnalyticsSalesOverview from '@/views/dashboards/analytics/AnalyticsSalesOverview.vue'
|
||||
import AnalyticsSourceVisits from '@/views/dashboards/analytics/AnalyticsSourceVisits.vue'
|
||||
import AnalyticsSupportTracker from '@/views/dashboards/analytics/AnalyticsSupportTracker.vue'
|
||||
import AnalyticsTotalEarning from '@/views/dashboards/analytics/AnalyticsTotalEarning.vue'
|
||||
import AnalyticsWebsiteAnalytics from '@/views/dashboards/analytics/AnalyticsWebsiteAnalytics.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<!-- 👉 Website analytics -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AnalyticsWebsiteAnalytics />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Average Daily Sales -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="3"
|
||||
sm="6"
|
||||
>
|
||||
<AnalyticsAverageDailySales />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Sales Overview -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="3"
|
||||
sm="6"
|
||||
>
|
||||
<AnalyticsSalesOverview />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Earning Reports Weekly Overview -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AnalyticsEarningReportsWeeklyOverview />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Support Tracker -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AnalyticsSupportTracker />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Sales by Countries -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
lg="4"
|
||||
>
|
||||
<AnalyticsSalesByCountries />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Total Earning -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
lg="4"
|
||||
>
|
||||
<AnalyticsTotalEarning />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Monthly Campaign State -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
lg="4"
|
||||
>
|
||||
<AnalyticsMonthlyCampaignState />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Source Visits -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
lg="4"
|
||||
>
|
||||
<AnalyticsSourceVisits />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Project Table -->
|
||||
<VCol
|
||||
cols="12"
|
||||
lg="8"
|
||||
>
|
||||
<AnalyticsProjectTable />
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/libs/apex-chart.scss";
|
||||
</style>
|
||||
958
resources/js/pages/dashboards/crm.vue
Normal file
958
resources/js/pages/dashboards/crm.vue
Normal file
@@ -0,0 +1,958 @@
|
||||
<script setup>
|
||||
import { ref, computed, nextTick, onMounted, watch, onUnmounted } from "vue";
|
||||
|
||||
import CrmActiveProject from "@/views/dashboards/crm/CrmActiveProject.vue";
|
||||
import CrmActivityTimeline from "@/views/dashboards/crm/CrmActivityTimeline.vue";
|
||||
import CrmAnalyticsSales from "@/views/dashboards/crm/CrmAnalyticsSales.vue";
|
||||
import CrmEarningReportsYearlyOverview from "@/views/dashboards/crm/CrmEarningReportsYearlyOverview.vue";
|
||||
import CrmProjectStatus from "@/views/dashboards/crm/CrmProjectStatus.vue";
|
||||
import CrmRecentTransactions from "@/views/dashboards/crm/CrmRecentTransactions.vue";
|
||||
import CrmSalesByCountries from "@/views/dashboards/crm/CrmSalesByCountries.vue";
|
||||
|
||||
import ProjectActivityBarChart from "@/components/ProjectActivityBarChart.vue";
|
||||
import AnalysisCard from "@/components/AnalysisCard.vue";
|
||||
import CostOverview from "@/components/CostOverview.vue";
|
||||
import GeneratedLeadsCard from "@/views/dashboards/ecommerce/EcommerceGeneratedLeads.vue";
|
||||
|
||||
import EcommerceCongratulationsJohn from '@/views/dashboards/ecommerce/EcommerceCongratulationsJohn.vue'
|
||||
import EcommerceEarningReports from '@/views/dashboards/ecommerce/EcommerceEarningReports.vue'
|
||||
import EcommerceExpensesRadialBarCharts from '@/views/dashboards/ecommerce/EcommerceExpensesRadialBarCharts.vue'
|
||||
import EcommerceInvoiceTable from '@/views/dashboards/ecommerce/EcommerceInvoiceTable.vue'
|
||||
import EcommerceOrder from '@/views/dashboards/ecommerce/EcommerceOrder.vue'
|
||||
import EcommercePopularProducts from '@/views/dashboards/ecommerce/EcommercePopularProducts.vue'
|
||||
import EcommerceRevenueReport from '@/views/dashboards/ecommerce/EcommerceRevenueReport.vue'
|
||||
import EcommerceStatistics from '@/views/dashboards/ecommerce/EcommerceStatistics.vue'
|
||||
import EcommerceTotalProfitLineCharts from '@/views/dashboards/ecommerce/EcommerceTotalProfitLineCharts.vue'
|
||||
import EcommerceTransactions from '@/views/dashboards/ecommerce/EcommerceTransactions.vue'
|
||||
|
||||
|
||||
const isEditMode = ref(false);
|
||||
|
||||
const handleEditModeChange = (event) => {
|
||||
if (event.detail) {
|
||||
isEditMode.value = event.detail.isActive;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDropFromSidebar = async (e) => {
|
||||
console.log('Drop event triggered', { isEditMode: isEditMode.value });
|
||||
if (!isEditMode.value) return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const widgetId = e.dataTransfer.getData("text/plain");
|
||||
const widgetData = e.dataTransfer.getData("application/json");
|
||||
|
||||
console.log('Widget ID from dataTransfer:', widgetId);
|
||||
console.log('Widget Data:', widgetData);
|
||||
|
||||
if (widgetId && !cardOrder.value.includes(widgetId)) {
|
||||
console.log('Adding widget to dashboard:', widgetId);
|
||||
|
||||
if (widgetData && !cardComponents[widgetId]) {
|
||||
const widget = JSON.parse(widgetData);
|
||||
|
||||
cardComponents[widgetId] = {
|
||||
component: GeneratedLeadsCard,
|
||||
props: widget.props || {}
|
||||
};
|
||||
|
||||
if (!cardSizes.value[widgetId]) {
|
||||
cardSizes.value[widgetId] = { cols: 6, height: 33.33 };
|
||||
}
|
||||
}
|
||||
|
||||
cardOrder.value.push(widgetId);
|
||||
await nextTick();
|
||||
await calculateRowHeights();
|
||||
|
||||
const event = new CustomEvent('widget-added-to-dashboard', {
|
||||
detail: { widgetId }
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
|
||||
window.dispatchEvent(new CustomEvent('dashboard-widgets-state', {
|
||||
detail: { widgets: cardOrder.value }
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
const handleDragOverFromSidebar = (e) => {
|
||||
if (!isEditMode.value) return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.dataTransfer.dropEffect = "copy";
|
||||
|
||||
const container = e.currentTarget;
|
||||
container.classList.add('drag-over');
|
||||
};
|
||||
|
||||
const handleDragLeaveFromSidebar = (e) => {
|
||||
if (!isEditMode.value) return;
|
||||
|
||||
const container = e.currentTarget;
|
||||
container.classList.remove('drag-over');
|
||||
};
|
||||
|
||||
const cardOrder = ref([
|
||||
"leads1",
|
||||
"leads2",
|
||||
"leads3",
|
||||
"project-activity",
|
||||
"analysis1",
|
||||
"analysis2",
|
||||
"cost-overview",
|
||||
"earning-reports",
|
||||
"analytics-sales",
|
||||
"sales-countries",
|
||||
"project-status",
|
||||
"active-project",
|
||||
"recent-transactions",
|
||||
"activity-timeline",
|
||||
]);
|
||||
|
||||
const defaultWidgetIds = [
|
||||
"leads1",
|
||||
"leads2",
|
||||
"leads3",
|
||||
"project-activity",
|
||||
"analysis1",
|
||||
"analysis2",
|
||||
"cost-overview",
|
||||
"earning-reports",
|
||||
"analytics-sales",
|
||||
"sales-countries",
|
||||
"project-status",
|
||||
"active-project",
|
||||
"recent-transactions",
|
||||
"activity-timeline"
|
||||
];
|
||||
|
||||
const cardSizes = ref({
|
||||
leads1: { cols: 4, height: 33.33 },
|
||||
leads2: { cols: 4, height: 33.33 },
|
||||
leads3: { cols: 4, height: 33.33 },
|
||||
"project-activity": { cols: 8, height: 33.33 },
|
||||
analysis1: { cols: 4, height: 33.33 },
|
||||
analysis2: { cols: 4, height: 33.33 },
|
||||
"cost-overview": { cols: 8, height: 33.33 },
|
||||
"earning-reports": { cols: 8, height: 33.33 },
|
||||
"analytics-sales": { cols: 4, height: 33.33 },
|
||||
"sales-countries": { cols: 4, height: 33.33 },
|
||||
"project-status": { cols: 4, height: 33.33 },
|
||||
"active-project": { cols: 4, height: 33.33 },
|
||||
"recent-transactions": { cols: 6, height: 33.33 },
|
||||
"activity-timeline": { cols: 6, height: 33.33 },
|
||||
"ecommerce-congratulations": { cols: 6, height: 33.33 },
|
||||
"ecommerce-earning-reports": { cols: 8, height: 33.33 },
|
||||
"ecommerce-expenses": { cols: 4, height: 33.33 },
|
||||
"ecommerce-generated-leads": { cols: 4, height: 33.33 },
|
||||
"ecommerce-invoice-table": { cols: 12, height: 33.33 },
|
||||
"ecommerce-order": { cols: 6, height: 33.33 },
|
||||
"ecommerce-popular-products": { cols: 6, height: 33.33 },
|
||||
"ecommerce-revenue-report": { cols: 8, height: 33.33 },
|
||||
"ecommerce-statistics": { cols: 12, height: 33.33 },
|
||||
"ecommerce-total-profit": { cols: 6, height: 33.33 },
|
||||
"ecommerce-transactions": { cols: 8, height: 33.33 },
|
||||
|
||||
});
|
||||
|
||||
const cardComponents = {
|
||||
leads1: { component: GeneratedLeadsCard, props: { progress: 33 } },
|
||||
leads2: {
|
||||
component: GeneratedLeadsCard,
|
||||
props: { donutColors: ["primary"], progress: 71 },
|
||||
},
|
||||
leads3: {
|
||||
component: GeneratedLeadsCard,
|
||||
props: { donutColors: ["warning"], progress: 56 },
|
||||
},
|
||||
"project-activity": { component: ProjectActivityBarChart, props: {} },
|
||||
analysis1: {
|
||||
component: AnalysisCard,
|
||||
props: { chartName: "Active Projects Progress" },
|
||||
},
|
||||
analysis2: { component: AnalysisCard, props: { chartName: "Cost overview" } },
|
||||
"cost-overview": { component: CostOverview, props: {} },
|
||||
"earning-reports": { component: CrmEarningReportsYearlyOverview, props: {} },
|
||||
"analytics-sales": { component: CrmAnalyticsSales, props: {} },
|
||||
"sales-countries": { component: CrmSalesByCountries, props: {} },
|
||||
"project-status": { component: CrmProjectStatus, props: {} },
|
||||
"active-project": { component: CrmActiveProject, props: {} },
|
||||
"recent-transactions": { component: CrmRecentTransactions, props: {} },
|
||||
"activity-timeline": { component: CrmActivityTimeline, props: {} },
|
||||
"ecommerce-congratulations": { component: EcommerceCongratulationsJohn, props: {} },
|
||||
"ecommerce-earning-reports": { component: EcommerceEarningReports, props: {} },
|
||||
"ecommerce-expenses": { component: EcommerceExpensesRadialBarCharts, props: {} },
|
||||
"ecommerce-invoice-table": { component: EcommerceInvoiceTable, props: {} },
|
||||
"ecommerce-order": { component: EcommerceOrder, props: {} },
|
||||
"ecommerce-popular-products": { component: EcommercePopularProducts, props: {} },
|
||||
"ecommerce-revenue-report": { component: EcommerceRevenueReport, props: {} },
|
||||
"ecommerce-statistics": { component: EcommerceStatistics, props: {} },
|
||||
"ecommerce-total-profit": { component: EcommerceTotalProfitLineCharts, props: {} },
|
||||
"ecommerce-transactions": { component: EcommerceTransactions, props: {} },
|
||||
};
|
||||
|
||||
window.addEventListener('remove-widget-from-dashboard', async (event) => {
|
||||
const widgetId = event.detail?.widgetId
|
||||
if (!widgetId) return
|
||||
const idx = cardOrder.value.indexOf(widgetId)
|
||||
if (idx > -1) {
|
||||
cardOrder.value.splice(idx, 1)
|
||||
await nextTick()
|
||||
await calculateRowHeights()
|
||||
window.dispatchEvent(new CustomEvent('dashboard-widgets-state', {
|
||||
detail: { widgets: cardOrder.value }
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
const handleDeleteCard = async (cardId) => {
|
||||
const cardIndex = cardOrder.value.indexOf(cardId)
|
||||
if (cardIndex > -1) {
|
||||
cardOrder.value.splice(cardIndex, 1)
|
||||
await nextTick()
|
||||
await calculateRowHeights()
|
||||
window.dispatchEvent(new CustomEvent('dashboard-widgets-state', {
|
||||
detail: { widgets: cardOrder.value }
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddWidgetToDashboard = async (event) => {
|
||||
if (event.detail && event.detail.widgetId) {
|
||||
const widgetId = event.detail.widgetId;
|
||||
const widget = event.detail.widget;
|
||||
|
||||
if (!cardComponents[widgetId] && widget.component) {
|
||||
cardComponents[widgetId] = {
|
||||
component: widget.component,
|
||||
props: widget.props || {}
|
||||
};
|
||||
|
||||
if (!cardSizes.value[widgetId]) {
|
||||
cardSizes.value[widgetId] = widget.size || { cols: 6, height: 33.33 };
|
||||
}
|
||||
}
|
||||
|
||||
if (!cardOrder.value.includes(widgetId)) {
|
||||
cardOrder.value.unshift(widgetId);
|
||||
await nextTick();
|
||||
await calculateRowHeights();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const restoreAllCards = async () => {
|
||||
cardOrder.value = [
|
||||
"leads1",
|
||||
"leads2",
|
||||
"leads3",
|
||||
"project-activity",
|
||||
"analysis1",
|
||||
"analysis2",
|
||||
"cost-overview",
|
||||
"earning-reports",
|
||||
"analytics-sales",
|
||||
"sales-countries",
|
||||
"project-status",
|
||||
"active-project",
|
||||
"recent-transactions",
|
||||
"activity-timeline",
|
||||
];
|
||||
cardOrder.value = defaultWidgetIds.slice();
|
||||
await nextTick();
|
||||
await calculateRowHeights();
|
||||
window.dispatchEvent(new CustomEvent('dashboard-widgets-state', {
|
||||
detail: { widgets: cardOrder.value }
|
||||
}));
|
||||
};
|
||||
|
||||
const cardRows = computed(() => {
|
||||
const rows = [];
|
||||
let currentRow = [];
|
||||
let currentRowCols = 0;
|
||||
cardOrder.value.forEach((cardId) => {
|
||||
const cardCols = cardSizes.value[cardId].cols;
|
||||
if (currentRowCols + cardCols > 12) {
|
||||
if (currentRow.length > 0) {
|
||||
rows.push([...currentRow]);
|
||||
}
|
||||
currentRow = [cardId];
|
||||
currentRowCols = cardCols;
|
||||
} else {
|
||||
currentRow.push(cardId);
|
||||
currentRowCols += cardCols;
|
||||
}
|
||||
});
|
||||
if (currentRow.length > 0) {
|
||||
rows.push(currentRow);
|
||||
}
|
||||
return rows;
|
||||
});
|
||||
|
||||
const rowHeights = ref({});
|
||||
|
||||
const calculateRowHeights = async () => {
|
||||
await nextTick();
|
||||
const newRowHeights = {};
|
||||
cardRows.value.forEach((row, rowIndex) => {
|
||||
let maxHeight = 0;
|
||||
row.forEach((cardId) => {
|
||||
const cardElement = document.querySelector(
|
||||
`[data-card-id="${cardId}"] .card-content`
|
||||
);
|
||||
if (cardElement) {
|
||||
const height = cardElement.scrollHeight;
|
||||
maxHeight = Math.max(maxHeight, height);
|
||||
}
|
||||
});
|
||||
if (maxHeight > 0) {
|
||||
newRowHeights[rowIndex] = `${maxHeight}px`;
|
||||
}
|
||||
});
|
||||
rowHeights.value = newRowHeights;
|
||||
};
|
||||
|
||||
const getCardHeight = (cardId) => {
|
||||
if (cardSizes.value[cardId].height !== "auto") {
|
||||
return cardSizes.value[cardId].height;
|
||||
}
|
||||
return "auto";
|
||||
};
|
||||
|
||||
let draggedElement = null;
|
||||
let draggedIndex = -1;
|
||||
|
||||
const handleDragStart = (e, cardId) => {
|
||||
if (!isEditMode.value) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
draggedElement = e.target.closest(".draggable-card");
|
||||
draggedIndex = cardOrder.value.indexOf(cardId);
|
||||
draggedElement.classList.add("dragging");
|
||||
e.dataTransfer.effectAllowed = "move";
|
||||
e.dataTransfer.setData("text/html", cardId);
|
||||
};
|
||||
|
||||
const handleDragEnd = async () => {
|
||||
if (draggedElement) {
|
||||
draggedElement.classList.remove("dragging");
|
||||
draggedElement = null;
|
||||
draggedIndex = -1;
|
||||
await calculateRowHeights();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
if (!isEditMode.value) return;
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = "move";
|
||||
};
|
||||
|
||||
const handleDrop = async (e, targetCardId) => {
|
||||
if (!isEditMode.value) return;
|
||||
e.preventDefault();
|
||||
if (draggedIndex === -1) return;
|
||||
const targetIndex = cardOrder.value.indexOf(targetCardId);
|
||||
if (draggedIndex !== targetIndex) {
|
||||
const draggedCard = cardOrder.value.splice(draggedIndex, 1)[0];
|
||||
cardOrder.value.splice(targetIndex, 0, draggedCard);
|
||||
await nextTick();
|
||||
await calculateRowHeights();
|
||||
}
|
||||
};
|
||||
|
||||
const isResizing = ref(false);
|
||||
const resizingCard = ref(null);
|
||||
const resizeDirection = ref(null);
|
||||
const startX = ref(0);
|
||||
const startCols = ref(0);
|
||||
|
||||
const cleanupResize = () => {
|
||||
if (isResizing.value) {
|
||||
isResizing.value = false;
|
||||
resizingCard.value = null;
|
||||
resizeDirection.value = null;
|
||||
document.removeEventListener("mousemove", handleResize);
|
||||
document.removeEventListener("mouseup", handleResizeEnd);
|
||||
document.body.style.cursor = "";
|
||||
document.body.style.userSelect = "";
|
||||
}
|
||||
};
|
||||
|
||||
const handleResizeStart = (e, cardId, direction) => {
|
||||
if (!isEditMode.value) return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (direction !== "horizontal") return;
|
||||
|
||||
cleanupResize();
|
||||
|
||||
isResizing.value = true;
|
||||
resizingCard.value = cardId;
|
||||
resizeDirection.value = direction;
|
||||
startX.value = e.clientX;
|
||||
startCols.value = cardSizes.value[cardId].cols;
|
||||
|
||||
document.addEventListener("mousemove", handleResize);
|
||||
document.addEventListener("mouseup", handleResizeEnd);
|
||||
document.body.style.cursor = "ew-resize";
|
||||
document.body.style.userSelect = "none";
|
||||
};
|
||||
|
||||
const handleResize = async (e) => {
|
||||
if (!isResizing.value || !resizingCard.value || resizeDirection.value !== "horizontal") return;
|
||||
|
||||
const deltaX = e.clientX - startX.value;
|
||||
const containerWidth = document.querySelector(".v-row")?.offsetWidth || 1200;
|
||||
const colWidth = containerWidth / 12;
|
||||
const deltaColumns = Math.round(deltaX / colWidth);
|
||||
const newCols = Math.max(4, Math.min(12, startCols.value + deltaColumns));
|
||||
|
||||
if (cardSizes.value[resizingCard.value].cols !== newCols) {
|
||||
cardSizes.value[resizingCard.value].cols = newCols;
|
||||
await nextTick();
|
||||
await calculateRowHeights();
|
||||
}
|
||||
};
|
||||
|
||||
const handleResizeEnd = async () => {
|
||||
if (!isResizing.value) return;
|
||||
|
||||
isResizing.value = false;
|
||||
resizingCard.value = null;
|
||||
resizeDirection.value = null;
|
||||
startX.value = 0;
|
||||
startCols.value = 0;
|
||||
|
||||
document.removeEventListener("mousemove", handleResize);
|
||||
document.removeEventListener("mouseup", handleResizeEnd);
|
||||
document.body.style.cursor = "";
|
||||
document.body.style.userSelect = "";
|
||||
|
||||
await calculateRowHeights();
|
||||
};
|
||||
|
||||
watch(
|
||||
[cardOrder, cardSizes],
|
||||
async () => {
|
||||
await nextTick();
|
||||
await calculateRowHeights();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
window.addEventListener("crm-edit-mode-changed", handleEditModeChange);
|
||||
window.addEventListener("add-widget-to-dashboard", handleAddWidgetToDashboard);
|
||||
|
||||
window.addEventListener('get-dashboard-widgets', () => {
|
||||
const event = new CustomEvent('dashboard-widgets-state', {
|
||||
detail: { widgets: cardOrder.value }
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
setTimeout(calculateRowHeights, 100);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("crm-edit-mode-changed", handleEditModeChange);
|
||||
window.removeEventListener("add-widget-to-dashboard", handleAddWidgetToDashboard);
|
||||
cleanupResize();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height" :class="{ 'edit-mode-active': isEditMode, 'drag-over': false }"
|
||||
@dragover="handleDragOverFromSidebar" @dragleave="handleDragLeaveFromSidebar" @drop="handleDropFromSidebar">
|
||||
|
||||
<VCol v-if="isEditMode" cols="12" class="pb-0">
|
||||
<div class="edit-mode-banner">
|
||||
<div class="banner-background">
|
||||
<div class="banner-glow"></div>
|
||||
<div class="banner-particles"></div>
|
||||
</div>
|
||||
|
||||
<div class="banner-content">
|
||||
<div class="banner-header">
|
||||
<div class="banner-icon">
|
||||
<VIcon icon="tabler-edit" size="24" class="edit-icon" />
|
||||
<div class="icon-pulse"></div>
|
||||
</div>
|
||||
<div class="banner-text">
|
||||
<h3 class="banner-title">✨ Edit Mode Enabled</h3>
|
||||
<p class="banner-subtitle">
|
||||
Customize your dashboard with drag & drop, resize, and delete
|
||||
features
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="banner-actions">
|
||||
<VBtn variant="elevated" size="default" color="white" @click="restoreAllCards" class="restore-btn-enhanced">
|
||||
<VIcon icon="tabler-reload" size="18" class="me-2" />
|
||||
Restore All Cards
|
||||
</VBtn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="banner-decorations">
|
||||
<div class="decoration decoration-1"></div>
|
||||
<div class="decoration decoration-2"></div>
|
||||
<div class="decoration decoration-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</VCol>
|
||||
|
||||
<template v-for="cardId in cardOrder" :key="cardId">
|
||||
<VCol :cols="12" :md="cardSizes[cardId].cols" class="draggable-card" :class="{
|
||||
resizing: isResizing && resizingCard === cardId,
|
||||
'edit-mode': isEditMode,
|
||||
'view-mode': !isEditMode
|
||||
}" :draggable="isEditMode" :data-card-id="cardId" @dragstart="handleDragStart($event, cardId)"
|
||||
@dragend="handleDragEnd" @dragover.prevent.stop="handleDragOver($event)"
|
||||
@drop.prevent.stop="handleDrop($event, cardId)">
|
||||
|
||||
<div class="card-wrapper" :style="{
|
||||
height: getCardHeight(cardId),
|
||||
}">
|
||||
<div v-if="isEditMode" class="drag-handle" :title="'Move ' + cardId">
|
||||
<v-icon size="18">mdi-drag-horizontal-variant</v-icon>
|
||||
</div>
|
||||
|
||||
<VBtn v-if="isEditMode" variant="text" size="small" color="error" class="delete-btn"
|
||||
:title="`Delete ${cardId}`" @click.stop="handleDeleteCard(cardId)">
|
||||
<VIcon icon="tabler-trash-x" size="16" class="me-1" />
|
||||
Delete
|
||||
</VBtn>
|
||||
|
||||
<div v-if="isEditMode" class="resize-handle resize-horizontal" :title="'Resize ' + cardId"
|
||||
@mousedown="handleResizeStart($event, cardId, 'horizontal')">
|
||||
<v-icon size="16">mdi-resize</v-icon>
|
||||
</div>
|
||||
|
||||
<component :is="cardComponents[cardId].component" v-bind="cardComponents[cardId].props"
|
||||
class="card-content" />
|
||||
</div>
|
||||
</VCol>
|
||||
</template>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.draggable-card {
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.draggable-card.edit-mode {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.draggable-card.edit-mode:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.draggable-card.edit-mode .card-wrapper {
|
||||
border: 2px dashed rgba(var(--v-theme-primary), 0.3);
|
||||
background: rgba(var(--v-theme-primary), 0.02);
|
||||
}
|
||||
|
||||
.draggable-card.edit-mode:hover .card-wrapper {
|
||||
border-color: rgba(var(--v-theme-primary), 0.6);
|
||||
background: rgba(var(--v-theme-primary), 0.05);
|
||||
}
|
||||
|
||||
.draggable-card.view-mode {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.draggable-card.view-mode .card-wrapper {
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.dragging {
|
||||
opacity: 0.5;
|
||||
transform: rotate(5deg) scale(1.05);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.resizing {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
.card-wrapper {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.edit-mode-banner {
|
||||
position: relative;
|
||||
margin: 0 0 2rem 0;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(var(--v-theme-primary), 0.9) 0%,
|
||||
rgba(var(--v-theme-secondary), 0.8) 50%,
|
||||
rgba(var(--v-theme-accent), 0.9) 100%);
|
||||
box-shadow: 0 8px 32px rgba(var(--v-theme-primary), 0.3),
|
||||
0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
animation: bannerSlideIn 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.banner-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.banner-glow {
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: radial-gradient(circle,
|
||||
rgba(255, 255, 255, 0.1) 0%,
|
||||
transparent 70%);
|
||||
animation: bannerGlow 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.banner-particles {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-image: radial-gradient(2px 2px at 20% 30%,
|
||||
rgba(255, 255, 255, 0.3),
|
||||
transparent),
|
||||
radial-gradient(1px 1px at 60% 70%, rgba(255, 255, 255, 0.2), transparent),
|
||||
radial-gradient(1px 1px at 90% 40%, rgba(255, 255, 255, 0.3), transparent),
|
||||
radial-gradient(2px 2px at 40% 80%, rgba(255, 255, 255, 0.2), transparent);
|
||||
background-size: 200px 200px, 150px 150px, 100px 100px, 250px 250px;
|
||||
animation: particlesFloat 15s linear infinite;
|
||||
}
|
||||
|
||||
.banner-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1.5rem 2rem;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.banner-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.banner-icon {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 12px;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.edit-icon {
|
||||
color: white;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.icon-pulse {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
animation: iconPulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.banner-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.banner-title {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
margin: 0 0 0.25rem 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.banner-subtitle {
|
||||
font-size: 0.9rem;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.banner-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.restore-btn-enhanced {
|
||||
background: rgba(255, 255, 255, 0.95) !important;
|
||||
color: rgba(var(--v-theme-primary), 0.9) !important;
|
||||
border-radius: 12px !important;
|
||||
padding: 12px 20px !important;
|
||||
font-weight: 600 !important;
|
||||
text-transform: none !important;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.05) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3) !important;
|
||||
backdrop-filter: blur(10px);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
height: auto !important;
|
||||
min-height: 44px !important;
|
||||
}
|
||||
|
||||
.restore-btn-enhanced:hover {
|
||||
background: white !important;
|
||||
transform: translateY(-2px) scale(1.02);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15), 0 4px 8px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
.restore-btn-enhanced:active {
|
||||
transform: translateY(-1px) scale(1.01);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.05) !important;
|
||||
}
|
||||
|
||||
.restore-btn-enhanced .v-icon {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.restore-btn-enhanced:hover .v-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.banner-decorations {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.decoration {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.decoration-1 {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
top: -40px;
|
||||
right: 10%;
|
||||
animation: decorationFloat1 8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.decoration-2 {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
bottom: -30px;
|
||||
left: 15%;
|
||||
animation: decorationFloat2 6s ease-in-out infinite reverse;
|
||||
}
|
||||
|
||||
.decoration-3 {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
top: 50%;
|
||||
right: 5%;
|
||||
animation: decorationFloat3 10s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 20;
|
||||
cursor: grab;
|
||||
padding: 6px;
|
||||
border-radius: 50%;
|
||||
background: rgba(var(--v-theme-primary), 0.9);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.drag-handle:hover {
|
||||
background: rgba(var(--v-theme-primary), 1);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.drag-handle:active {
|
||||
cursor: grabbing;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
z-index: 5;
|
||||
opacity: 1 !important;
|
||||
background: rgba(255, 82, 82, 0.9) !important;
|
||||
color: white !important;
|
||||
border-radius: 6px !important;
|
||||
padding: 4px 8px !important;
|
||||
font-size: 12px !important;
|
||||
min-width: auto !important;
|
||||
height: 28px !important;
|
||||
box-shadow: 0 2px 6px rgba(255, 82, 82, 0.3);
|
||||
transition: all 0.2s ease;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.delete-btn:hover {
|
||||
background: rgba(220, 38, 38, 1) !important;
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.4);
|
||||
}
|
||||
|
||||
.delete-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.resize-handle {
|
||||
position: absolute;
|
||||
z-index: 15;
|
||||
background: rgba(var(--v-theme-secondary), 0.9);
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.resize-horizontal {
|
||||
right: -6px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 14px;
|
||||
height: 50px;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.resize-handle:hover {
|
||||
background: rgba(var(--v-theme-secondary), 1);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.resize-horizontal:hover {
|
||||
transform: translateY(-50%) scale(1.1);
|
||||
}
|
||||
|
||||
.edit-mode:hover .drag-handle,
|
||||
.edit-mode:hover .resize-handle {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.draggable-card.drag-over .card-wrapper {
|
||||
border: 2px dashed rgba(var(--v-theme-success), 0.5) !important;
|
||||
background: rgba(var(--v-theme-success), 0.05) !important;
|
||||
}
|
||||
|
||||
.edit-mode-active {
|
||||
padding: 1rem;
|
||||
background: rgba(var(--v-theme-surface), 0.5);
|
||||
border-radius: 12px;
|
||||
border: 1px dashed rgba(var(--v-theme-primary), 0.2);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.banner-content {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 1rem;
|
||||
padding: 1.25rem 1rem;
|
||||
}
|
||||
|
||||
.banner-header {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.banner-title {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.banner-subtitle {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.restore-btn-enhanced {
|
||||
padding: 10px 16px !important;
|
||||
font-size: 0.9rem !important;
|
||||
}
|
||||
|
||||
.edit-mode .drag-handle,
|
||||
.edit-mode .resize-handle {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
font-size: 11px !important;
|
||||
padding: 3px 6px !important;
|
||||
height: 24px !important;
|
||||
}
|
||||
|
||||
.resize-handle {
|
||||
background: rgba(var(--v-theme-secondary), 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
.resizing * {
|
||||
user-select: none !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
</style>
|
||||
97
resources/js/pages/dashboards/demo.vue
Normal file
97
resources/js/pages/dashboards/demo.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<script setup>
|
||||
import { onMounted, nextTick } from 'vue'
|
||||
import { GridStack } from 'gridstack'
|
||||
|
||||
import EcommerceCongratulationsJohn from '@/views/dashboards/ecommerce/EcommerceCongratulationsJohn.vue'
|
||||
import EcommerceEarningReports from '@/views/dashboards/ecommerce/EcommerceEarningReports.vue'
|
||||
import EcommerceExpensesRadialBarCharts from '@/views/dashboards/ecommerce/EcommerceExpensesRadialBarCharts.vue'
|
||||
import EcommerceGeneratedLeads from '@/views/dashboards/ecommerce/EcommerceGeneratedLeads.vue'
|
||||
import EcommerceInvoiceTable from '@/views/dashboards/ecommerce/EcommerceInvoiceTable.vue'
|
||||
import EcommerceOrder from '@/views/dashboards/ecommerce/EcommerceOrder.vue'
|
||||
import EcommercePopularProducts from '@/views/dashboards/ecommerce/EcommercePopularProducts.vue'
|
||||
import EcommerceRevenueReport from '@/views/dashboards/ecommerce/EcommerceRevenueReport.vue'
|
||||
import EcommerceStatistics from '@/views/dashboards/ecommerce/EcommerceStatistics.vue'
|
||||
import EcommerceTotalProfitLineCharts from '@/views/dashboards/ecommerce/EcommerceTotalProfitLineCharts.vue'
|
||||
import EcommerceTransactions from '@/views/dashboards/ecommerce/EcommerceTransactions.vue'
|
||||
|
||||
onMounted(async () => {
|
||||
const grid = GridStack.init({
|
||||
column: 12,
|
||||
cellHeight: 50,
|
||||
float: true,
|
||||
draggable: { handle: '.grid-stack-item-content' },
|
||||
resizable: true
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="grid-stack">
|
||||
<div class="grid-stack-item" gs-w="8" gs-h="4" gs-max-h="4" gs-x="0" gs-y="0">
|
||||
<div class="grid-stack-item-content">
|
||||
<EcommerceCongratulationsJohn />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-stack-item" gs-w="4" gs-h="4" gs-max-h="4" gs-x="8" gs-y="0">
|
||||
<div class="grid-stack-item-content">
|
||||
<EcommerceStatistics />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-stack-item" gs-w="7" gs-h="3" gs-max-h="3" gs-x="0" gs-y="4">
|
||||
<div class="grid-stack-item-content">
|
||||
<EcommerceTotalProfitLineCharts />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-stack-item" gs-w="5" gs-h="3" gs-max-h="3" gs-x="7" gs-y="4">
|
||||
<div class="grid-stack-item-content">
|
||||
<EcommerceExpensesRadialBarCharts />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-stack-item" gs-w="3" gs-h="5" gs-max-h="5" gs-x="0" gs-y="7">
|
||||
<div class="grid-stack-item-content">
|
||||
<EcommerceGeneratedLeads />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-stack-item" gs-w="6" gs-h="5" gs-max-h="5" gs-x="3" gs-y="7">
|
||||
<div class="grid-stack-item-content">
|
||||
<EcommerceRevenueReport />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-stack-item" gs-w="3" gs-h="3" gs-max-h="3" gs-x="9" gs-y="7">
|
||||
<div class="grid-stack-item-content">
|
||||
<EcommerceOrder />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-stack-item" gs-w="4" gs-h="10" gs-max-h="10" gs-x="0" gs-y="12">
|
||||
<div class="grid-stack-item-content">
|
||||
<EcommerceEarningReports />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-stack-item" gs-w="4" gs-h="10" gs-max-h="10" gs-x="4" gs-y="12">
|
||||
<div class="grid-stack-item-content">
|
||||
<EcommercePopularProducts />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-stack-item" gs-w="4" gs-h="4" gs-max-h="4" gs-x="8" gs-y="12">
|
||||
<div class="grid-stack-item-content">
|
||||
<EcommerceTransactions />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-stack-item" gs-w="12" gs-h="6" gs-max-h="6" gs-x="0" gs-y="16">
|
||||
<div class="grid-stack-item-content">
|
||||
<EcommerceInvoiceTable />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
129
resources/js/pages/dashboards/ecommerce.vue
Normal file
129
resources/js/pages/dashboards/ecommerce.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<script setup>
|
||||
import EcommerceCongratulationsJohn from '@/views/dashboards/ecommerce/EcommerceCongratulationsJohn.vue'
|
||||
import EcommerceEarningReports from '@/views/dashboards/ecommerce/EcommerceEarningReports.vue'
|
||||
import EcommerceExpensesRadialBarCharts from '@/views/dashboards/ecommerce/EcommerceExpensesRadialBarCharts.vue'
|
||||
import EcommerceGeneratedLeads from '@/views/dashboards/ecommerce/EcommerceGeneratedLeads.vue'
|
||||
import EcommerceInvoiceTable from '@/views/dashboards/ecommerce/EcommerceInvoiceTable.vue'
|
||||
import EcommerceOrder from '@/views/dashboards/ecommerce/EcommerceOrder.vue'
|
||||
import EcommercePopularProducts from '@/views/dashboards/ecommerce/EcommercePopularProducts.vue'
|
||||
import EcommerceRevenueReport from '@/views/dashboards/ecommerce/EcommerceRevenueReport.vue'
|
||||
import EcommerceStatistics from '@/views/dashboards/ecommerce/EcommerceStatistics.vue'
|
||||
import EcommerceTotalProfitLineCharts from '@/views/dashboards/ecommerce/EcommerceTotalProfitLineCharts.vue'
|
||||
import EcommerceTransactions from '@/views/dashboards/ecommerce/EcommerceTransactions.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<!-- 👉 Congratulation John -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="5"
|
||||
lg="4"
|
||||
>
|
||||
<EcommerceCongratulationsJohn />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Ecommerce Transition -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="7"
|
||||
lg="8"
|
||||
>
|
||||
<EcommerceStatistics class="h-100" />
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
lg="4"
|
||||
>
|
||||
<VRow>
|
||||
<!-- 👉 Total Profit Line -->
|
||||
<VCol
|
||||
cols="12"
|
||||
lg="6"
|
||||
md="3"
|
||||
sm="6"
|
||||
>
|
||||
<EcommerceTotalProfitLineCharts />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Expenses Radial Bar Charts -->
|
||||
<VCol
|
||||
cols="12"
|
||||
lg="6"
|
||||
md="3"
|
||||
sm="6"
|
||||
>
|
||||
<EcommerceExpensesRadialBarCharts />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Generated Leads -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
lg="12"
|
||||
>
|
||||
<EcommerceGeneratedLeads />
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Revenue Report -->
|
||||
<VCol
|
||||
cols="12"
|
||||
lg="8"
|
||||
>
|
||||
<EcommerceRevenueReport />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Earning Reports -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
lg="4"
|
||||
>
|
||||
<EcommerceEarningReports />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Popular Products -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
lg="4"
|
||||
>
|
||||
<EcommercePopularProducts />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Order -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
lg="4"
|
||||
>
|
||||
<EcommerceOrder />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Transaction -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
lg="4"
|
||||
>
|
||||
<EcommerceTransactions />
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Invoice Table -->
|
||||
<VCol
|
||||
cols="12"
|
||||
lg="8"
|
||||
>
|
||||
<EcommerceInvoiceTable />
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/libs/apex-chart.scss";
|
||||
</style>
|
||||
|
||||
<!-- کد اول -->
|
||||
1260
resources/js/pages/dashboards/gantt.vue
Normal file
1260
resources/js/pages/dashboards/gantt.vue
Normal file
File diff suppressed because it is too large
Load Diff
156
resources/js/pages/extensions/swiper.vue
Normal file
156
resources/js/pages/extensions/swiper.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/components/swiper/demoCodeSwiper'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol>
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<DemoSwiperBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol>
|
||||
<AppCardCode
|
||||
title="Navigation"
|
||||
:code="demoCode.navigation"
|
||||
>
|
||||
<DemoSwiperNavigation />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol>
|
||||
<AppCardCode
|
||||
title="Pagination"
|
||||
:code="demoCode.pagination"
|
||||
>
|
||||
<DemoSwiperPagination />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol>
|
||||
<AppCardCode
|
||||
title="Progress"
|
||||
:code="demoCode.progress"
|
||||
>
|
||||
<DemoSwiperProgress />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol>
|
||||
<AppCardCode
|
||||
title="Multiple Slides Per View"
|
||||
:code="demoCode.multipleSlidesPerView"
|
||||
>
|
||||
<DemoSwiperMultipleSlidesPerView />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol>
|
||||
<AppCardCode
|
||||
title="Grid"
|
||||
:code="demoCode.grid"
|
||||
>
|
||||
<DemoSwiperGrid />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol>
|
||||
<AppCardCode
|
||||
variant="text"
|
||||
title="Centered Slides Option 1"
|
||||
:code="demoCode.centeredSlidesOption1"
|
||||
>
|
||||
<DemoSwiperCenteredSlidesOption1 />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol>
|
||||
<AppCardCode
|
||||
title="Centered Slides Option 2"
|
||||
:code="demoCode.centeredSlidesOption2"
|
||||
>
|
||||
<DemoSwiperCenteredSlidesOption2 />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol>
|
||||
<AppCardCode
|
||||
title="Fade"
|
||||
:code="demoCode.fade"
|
||||
>
|
||||
<DemoSwiperFade />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol>
|
||||
<AppCardCode
|
||||
title="Cube Effect"
|
||||
:code="demoCode.cubeEffect"
|
||||
>
|
||||
<DemoSwiperCubeEffect />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol>
|
||||
<AppCardCode
|
||||
title="Coverflow Effect"
|
||||
:code="demoCode.coverflowEffect"
|
||||
>
|
||||
<DemoSwiperCoverflowEffect />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol>
|
||||
<AppCardCode
|
||||
title="Autoplay"
|
||||
:code="demoCode.autoplay"
|
||||
>
|
||||
<DemoSwiperAutoplay />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol>
|
||||
<AppCardCode
|
||||
title="Gallery"
|
||||
:code="demoCode.gallery"
|
||||
>
|
||||
<DemoSwiperGallery />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol>
|
||||
<AppCardCode
|
||||
title="Lazy Loading"
|
||||
:code="demoCode.lazyLoading"
|
||||
>
|
||||
<DemoSwiperLazyLoading />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol>
|
||||
<AppCardCode
|
||||
title="Responsive Breakpoints"
|
||||
:code="demoCode.responsiveBreakpoints"
|
||||
>
|
||||
<DemoSwiperResponsiveBreakpoints />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol>
|
||||
<AppCardCode
|
||||
title="Virtual Slides"
|
||||
:code="demoCode.virtualSlides"
|
||||
>
|
||||
<DemoSwiperVirtualSlides />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/libs/swiper.scss"
|
||||
</style>
|
||||
134
resources/js/pages/extensions/tour.vue
Normal file
134
resources/js/pages/extensions/tour.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<script setup>
|
||||
import { useShepherd } from 'vue-shepherd'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
// 👉 Hotkey
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
const { ctrl_k, meta_k } = useMagicKeys()
|
||||
|
||||
// 👉 Tour initialization
|
||||
let tour = null
|
||||
|
||||
// 👉 watch command palette and route change
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
watch([
|
||||
ctrl_k,
|
||||
meta_k,
|
||||
() => route.path,
|
||||
], () => {
|
||||
if (tour.isActive())
|
||||
tour.cancel()
|
||||
})
|
||||
|
||||
/* eslint-enable */
|
||||
onMounted(() => {
|
||||
const navbar = document.querySelector('.layout-navbar')
|
||||
|
||||
tour = useShepherd({
|
||||
useModalOverlay: true,
|
||||
stepsContainer: document.querySelector('.layout-wrapper'),
|
||||
modelContainer: document.querySelector('.layout-wrapper'),
|
||||
defaultStepOptions: {
|
||||
cancelIcon: { enabled: true },
|
||||
modalOverlayOpeningPadding: 2,
|
||||
modalOverlayOpeningRadius: 5,
|
||||
},
|
||||
})
|
||||
|
||||
// 👉 Tour steps
|
||||
tour.addSteps([
|
||||
{
|
||||
id: 'welcome',
|
||||
title: 'Welcome',
|
||||
arrow: true,
|
||||
attachTo: {
|
||||
element: navbar,
|
||||
on: 'bottom',
|
||||
},
|
||||
text: 'Welcome to our tour page, Guide users to the key features of the product.',
|
||||
buttons: [
|
||||
{
|
||||
action: tour.cancel,
|
||||
classes: 'backBtnClass',
|
||||
text: 'Back',
|
||||
},
|
||||
{
|
||||
action: tour.next,
|
||||
text: 'Next',
|
||||
classes: 'nextBtnClass',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'notification',
|
||||
title: 'Notifications',
|
||||
arrow: true,
|
||||
attachTo: {
|
||||
element: document.querySelector('#notification-btn'),
|
||||
on: 'bottom',
|
||||
},
|
||||
text: 'Manage your notifications and stay up-to-date with latest updates.',
|
||||
buttons: [
|
||||
{
|
||||
label: 'Back',
|
||||
text: 'Back',
|
||||
action: tour.back,
|
||||
classes: 'backBtnClass',
|
||||
},
|
||||
{
|
||||
label: 'Next',
|
||||
text: 'Next',
|
||||
action: tour.next,
|
||||
classes: 'nextBtnClass',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'footer',
|
||||
title: 'Footer',
|
||||
arrow: true,
|
||||
attachTo: {
|
||||
element: document.querySelector('.layout-footer'),
|
||||
on: 'bottom',
|
||||
},
|
||||
text: 'Footer section of the page.',
|
||||
buttons: [
|
||||
{
|
||||
label: 'Back',
|
||||
text: 'Back',
|
||||
action: tour.back,
|
||||
classes: 'backBtnClass',
|
||||
},
|
||||
{
|
||||
label: 'Finish',
|
||||
text: 'Finish',
|
||||
action: tour.complete,
|
||||
classes: 'nextBtnClass',
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<VCard title="Tour">
|
||||
<VCardText>
|
||||
<VBtn
|
||||
variant="elevated"
|
||||
@click="() => { tour && tour.start() }"
|
||||
>
|
||||
Start Tour
|
||||
</VBtn>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/libs/shepherd.scss";
|
||||
</style>
|
||||
129
resources/js/pages/forgot-password.vue
Normal file
129
resources/js/pages/forgot-password.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<script setup>
|
||||
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
|
||||
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
|
||||
import { themeConfig } from '@themeConfig'
|
||||
import authV2ForgotPasswordIllustrationDark from '@images/pages/auth-v2-forgot-password-illustration-dark.png'
|
||||
import authV2ForgotPasswordIllustrationLight from '@images/pages/auth-v2-forgot-password-illustration-light.png'
|
||||
import authV2MaskDark from '@images/pages/misc-mask-dark.png'
|
||||
import authV2MaskLight from '@images/pages/misc-mask-light.png'
|
||||
|
||||
const email = ref('')
|
||||
const authThemeImg = useGenerateImageVariant(authV2ForgotPasswordIllustrationLight, authV2ForgotPasswordIllustrationDark)
|
||||
const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
unauthenticatedOnly: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterLink to="/">
|
||||
<div class="auth-logo d-flex align-center gap-x-3">
|
||||
<VNodeRenderer :nodes="themeConfig.app.logo" />
|
||||
<h1 class="auth-title">
|
||||
{{ themeConfig.app.title }}
|
||||
</h1>
|
||||
</div>
|
||||
</RouterLink>
|
||||
|
||||
<VRow
|
||||
class="auth-wrapper bg-surface"
|
||||
no-gutters
|
||||
>
|
||||
<VCol
|
||||
md="8"
|
||||
class="d-none d-md-flex"
|
||||
>
|
||||
<div class="position-relative bg-background w-100 me-0">
|
||||
<div
|
||||
class="d-flex align-center justify-center w-100 h-100"
|
||||
style="padding-inline: 150px;"
|
||||
>
|
||||
<VImg
|
||||
max-width="468"
|
||||
:src="authThemeImg"
|
||||
class="auth-illustration mt-16 mb-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<img
|
||||
class="auth-footer-mask"
|
||||
:src="authThemeMask"
|
||||
alt="auth-footer-mask"
|
||||
height="280"
|
||||
width="100"
|
||||
>
|
||||
</div>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
class="d-flex align-center justify-center"
|
||||
>
|
||||
<VCard
|
||||
flat
|
||||
:max-width="500"
|
||||
class="mt-12 mt-sm-0 pa-4"
|
||||
>
|
||||
<VCardText>
|
||||
<h4 class="text-h4 mb-1">
|
||||
Forgot Password? 🔒
|
||||
</h4>
|
||||
<p class="mb-0">
|
||||
Enter your email and we'll send you instructions to reset your password
|
||||
</p>
|
||||
</VCardText>
|
||||
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<VRow>
|
||||
<!-- email -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="email"
|
||||
autofocus
|
||||
label="Email"
|
||||
type="email"
|
||||
placeholder="johndoe@email.com"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- Reset link -->
|
||||
<VCol cols="12">
|
||||
<VBtn
|
||||
block
|
||||
type="submit"
|
||||
>
|
||||
Send Reset Link
|
||||
</VBtn>
|
||||
</VCol>
|
||||
|
||||
<!-- back to login -->
|
||||
<VCol cols="12">
|
||||
<RouterLink
|
||||
class="d-flex align-center justify-center"
|
||||
:to="{ name: 'login' }"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-chevron-left"
|
||||
size="20"
|
||||
class="me-1 flip-in-rtl"
|
||||
/>
|
||||
<span>Back to login</span>
|
||||
</RouterLink>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/pages/page-auth.scss";
|
||||
</style>
|
||||
176
resources/js/pages/forms/autocomplete.vue
Normal file
176
resources/js/pages/forms/autocomplete.vue
Normal file
@@ -0,0 +1,176 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-elements/autocomplete/demoCodeAutocomplete'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<p>
|
||||
The <code> v-autocomplete </code> component offers simple and flexible type-ahead functionality. This is useful when searching large sets of data or even dynamically fetching information from an API.
|
||||
</p>
|
||||
|
||||
<DemoAutocompleteBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Density -->
|
||||
<AppCardCode
|
||||
title="Density"
|
||||
:code="demoCode.density"
|
||||
>
|
||||
<p>
|
||||
You can use <code> density </code> prop to adjusts vertical spacing within the component. Available options are: <code>default</code>, <code>comfortable</code>, and <code>compact</code>.
|
||||
</p>
|
||||
|
||||
<DemoAutocompleteDensity />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="12"
|
||||
>
|
||||
<!-- 👉 Variant -->
|
||||
<AppCardCode
|
||||
title="Variant"
|
||||
:code="demoCode.variant"
|
||||
>
|
||||
<p>Use <code>Solo</code>, <code>Outlined</code>, <code>Underlined</code>, <code>Filled</code> and <code>Plain</code> options of <code>variant</code> prop to change the look of Autocomplete. </p>
|
||||
|
||||
<DemoAutocompleteVariant />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Multiple Select -->
|
||||
<AppCardCode
|
||||
title="Multiple"
|
||||
:code="demoCode.multiple"
|
||||
>
|
||||
<p>Use <code>multiple</code> prop to select multiple. Accepts array for value</p>
|
||||
|
||||
<DemoAutocompleteMultiple />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Clearable input -->
|
||||
<AppCardCode
|
||||
title="Clearable"
|
||||
:code="demoCode.clearable"
|
||||
>
|
||||
<p>Use <code>clearable</code> prop to add input clear functionality.</p>
|
||||
|
||||
<DemoAutocompleteClearable />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Chips -->
|
||||
<AppCardCode
|
||||
title="Chips"
|
||||
:code="demoCode.chips"
|
||||
>
|
||||
<p>Use <code> chips </code> prop to use chips in select.</p>
|
||||
|
||||
<DemoAutocompleteChips />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Custom Filter -->
|
||||
<AppCardCode
|
||||
title="Custom-Filter"
|
||||
:code="demoCode.customFilter"
|
||||
>
|
||||
<p>The <code> custom-filter </code> prop can be used to filter each individual item with custom logic.In example we will filter state based on their name and abbreviations </p>
|
||||
|
||||
<DemoAutocompleteCustomFilter />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 With slots -->
|
||||
<AppCardCode
|
||||
title="Slots"
|
||||
:code="demoCode.slots"
|
||||
>
|
||||
<p>With the power of slots, you can customize the visual output of the select. In this example we add a profile picture for both the chips and list items using their props. </p>
|
||||
|
||||
<DemoAutocompleteSlots />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Asynchronous Items -->
|
||||
<AppCardCode
|
||||
title="Async items"
|
||||
:code="demoCode.asyncItems"
|
||||
>
|
||||
<p>Sometimes you need to load data externally based upon a search query. </p>
|
||||
|
||||
<DemoAutocompleteAsyncItems />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 State Selector -->
|
||||
<AppCardCode
|
||||
title="State Selector"
|
||||
:code="demoCode.stateSelector"
|
||||
>
|
||||
<p>Using a combination of v-autocomplete slots and transitions, you can create a stylish toggle able autocomplete field such as below state selector.</p>
|
||||
|
||||
<DemoAutocompleteStateSelector />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Validation -->
|
||||
<AppCardCode
|
||||
title="validation"
|
||||
:code="demoCode.validation"
|
||||
>
|
||||
<p>Use <code>rules</code> prop to validate autocomplete. Accepts a mixed array of types function, boolean and string. Functions pass an input value as an argument and must return either true / false or a string containing an error message.</p>
|
||||
|
||||
<DemoAutocompleteValidation />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
142
resources/js/pages/forms/checkbox.vue
Normal file
142
resources/js/pages/forms/checkbox.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-elements/checkbox/demoCodeCheckbox'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<p><code>v-checkbox</code> in its simplest form provides a toggle between 2 values.</p>
|
||||
|
||||
<DemoCheckboxBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Density -->
|
||||
<AppCardCode
|
||||
title="Density"
|
||||
:code="demoCode.density"
|
||||
>
|
||||
<p>Use <code>density</code> prop to reduces the input height. Available options are: <code>default</code>, <code>comfortable</code>, and <code>compact</code>.</p>
|
||||
|
||||
<DemoCheckboxDensity />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Colors -->
|
||||
<AppCardCode
|
||||
title="Colors"
|
||||
:code="demoCode.colors"
|
||||
>
|
||||
<p>Checkboxes can be colored by using any of the builtin colors and contextual names using the <code>color</code> prop.</p>
|
||||
|
||||
<DemoCheckboxColors />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Model as array -->
|
||||
<AppCardCode
|
||||
title="Model as array"
|
||||
:code="demoCode.modelAsArray"
|
||||
>
|
||||
<p>Multiple <code>v-checkbox</code>'s can share the same <code>v-model</code> by using an array.</p>
|
||||
|
||||
<DemoCheckboxModelAsArray />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Icon -->
|
||||
<AppCardCode
|
||||
title="Icon"
|
||||
:code="demoCode.icon"
|
||||
>
|
||||
<p>Use <code>false-icon</code> and <code>true-icon</code> prop to change the icon on the checkbox.</p>
|
||||
|
||||
<DemoCheckboxIcon />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Checkbox Value -->
|
||||
<AppCardCode
|
||||
title="Checkbox Value"
|
||||
:code="demoCode.checkboxValue"
|
||||
>
|
||||
<p>Use <code>false-value</code> and <code>true-value</code> prop to sets value for truthy and falsy state</p>
|
||||
|
||||
<DemoCheckboxCheckboxValue />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 States -->
|
||||
<AppCardCode
|
||||
title="States"
|
||||
:code="demoCode.states"
|
||||
>
|
||||
<p><code>v-checkbox</code> can have different states such as <code>default</code>, <code>disabled</code>, and <code>indeterminate</code>.</p>
|
||||
|
||||
<DemoCheckboxStates />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Label Slot -->
|
||||
<AppCardCode
|
||||
title="Label Slot"
|
||||
:code="demoCode.labelSlot"
|
||||
>
|
||||
<p>Checkbox labels can be defined in <code>label</code> slot - that will allow to use HTML content.</p>
|
||||
|
||||
<DemoCheckboxLabelSlot />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Inline text-field -->
|
||||
<AppCardCode
|
||||
title="Inline text-field"
|
||||
:code="demoCode.inlineTextField"
|
||||
>
|
||||
<p>You can place <code>v-checkbox</code> in line with other components such as <code>v-text-field</code>.</p>
|
||||
|
||||
<DemoCheckboxInlineTextField />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
100
resources/js/pages/forms/combobox.vue
Normal file
100
resources/js/pages/forms/combobox.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-elements/combobox/demoCodeCombobox'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<p>With Combobox, you can allow a user to create new values that may not be present in a provided items list.</p>
|
||||
|
||||
<DemoComboboxBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Density -->
|
||||
<AppCardCode
|
||||
title="Density"
|
||||
:code="demoCode.density"
|
||||
>
|
||||
<p>
|
||||
You can use <code>Density</code> prop to reduce combobox height and lower max height of list items. Available options are: <code>default</code>, <code>comfortable</code>, and <code>compact</code>.
|
||||
</p>
|
||||
|
||||
<DemoComboboxDensity />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Variant -->
|
||||
<AppCardCode
|
||||
title="Variant"
|
||||
:code="demoCode.variant"
|
||||
>
|
||||
<p>Use <code>solo</code>, <code>outlined</code>, <code>underlined</code>, <code>filled</code> and <code>plain</code> options of <code>variant</code> prop to change the look of combobox. </p>
|
||||
|
||||
<DemoComboboxVariant />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Multiple -->
|
||||
<AppCardCode
|
||||
title="Multiple"
|
||||
:code="demoCode.multiple"
|
||||
>
|
||||
<p>Previously known as tags - user is allowed to enter more than 1 value</p>
|
||||
|
||||
<DemoComboboxMultiple />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 No data with chips -->
|
||||
<AppCardCode
|
||||
title="No data with chips"
|
||||
:code="demoCode.noDataWithChips"
|
||||
>
|
||||
<p>Previously known as tags - user is allowed to enter more than 1 value</p>
|
||||
|
||||
<DemoComboboxNoDataWithChips />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Clearable -->
|
||||
|
||||
<AppCardCode
|
||||
title="Clearable"
|
||||
:code="demoCode.clearable"
|
||||
>
|
||||
<p>Use <code>clearable</code> prop to clear combobox.</p>
|
||||
|
||||
<DemoComboboxClearable />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
85
resources/js/pages/forms/custom-input.vue
Normal file
85
resources/js/pages/forms/custom-input.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-elements/custom-input/demoCodeCustomInput'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<!-- 👉 Custom Radios -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Custom Radios"
|
||||
:code="demoCode.customRadios"
|
||||
>
|
||||
<DemoCustomInputCustomRadios />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Custom Checkboxes -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Custom Checkboxes"
|
||||
:code="demoCode.customCheckboxes"
|
||||
>
|
||||
<DemoCustomInputCustomCheckboxes />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Custom Radios With Icon -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Custom Radios With Icon"
|
||||
:code="demoCode.customRadiosWithIcon"
|
||||
>
|
||||
<DemoCustomInputCustomRadiosWithIcon />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Custom Checkboxes with icon -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Custom Checkboxes With Icon"
|
||||
:code="demoCode.customCheckboxesWithIcon"
|
||||
>
|
||||
<DemoCustomInputCustomCheckboxesWithIcon />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Custom Radios with image -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Custom Radios With Image"
|
||||
:code="demoCode.customRadiosWithImage"
|
||||
>
|
||||
<DemoCustomInputCustomRadiosWithImage />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Custom Checkboxes with Image -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Custom Checkboxes With Image"
|
||||
:code="demoCode.customCheckboxesWithImage"
|
||||
>
|
||||
<DemoCustomInputCustomCheckboxesWithImage />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
111
resources/js/pages/forms/date-time-picker.vue
Normal file
111
resources/js/pages/forms/date-time-picker.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-elements/date-time-picker/demoCodeDateTimePicker'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<!-- 👉 Basic -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<DemoDateTimePickerBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Time Picker -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Time Picker"
|
||||
:code="demoCode.timePicker"
|
||||
>
|
||||
<DemoDateTimePickerTimePicker />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Date & TIme -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Date and Time"
|
||||
:code="demoCode.dateAndTime"
|
||||
>
|
||||
<DemoDateTimePickerDateAndTime />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Multiple Dates -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Multiple Dates"
|
||||
:code="demoCode.multipleDates"
|
||||
>
|
||||
<DemoDateTimePickerMultipleDates />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Range -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Range"
|
||||
:code="demoCode.range"
|
||||
>
|
||||
<DemoDateTimePickerRange />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Human Friendly -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Human Friendly"
|
||||
:code="demoCode.humanFriendly"
|
||||
>
|
||||
<DemoDateTimePickerHumanFriendly />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Disabled Range -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Disabled Range"
|
||||
:code="demoCode.disabledRange"
|
||||
>
|
||||
<DemoDateTimePickerDisabledRange />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Inline -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppCardCode
|
||||
title="Inline"
|
||||
:code="demoCode.inline"
|
||||
>
|
||||
<DemoDateTimePickerInline />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
26
resources/js/pages/forms/editors.vue
Normal file
26
resources/js/pages/forms/editors.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-elements/editor/demoCodeEditor'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<!-- 👉 Basic Editor -->
|
||||
<VCol cols="12">
|
||||
<AppCardCode
|
||||
title="Basic Editor"
|
||||
:code="demoCode.basicEditor"
|
||||
>
|
||||
<DemoEditorBasicEditor />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<AppCardCode
|
||||
title="Custom Editor"
|
||||
:code="demoCode.customEditor"
|
||||
>
|
||||
<DemoEditorCustomEditor />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
188
resources/js/pages/forms/file-input.vue
Normal file
188
resources/js/pages/forms/file-input.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-elements/file-input/demoCodeFileInput'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<p>The <code>v-file-input</code> component is used to selecting files.</p>
|
||||
|
||||
<DemoFileInputBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Density -->
|
||||
<AppCardCode
|
||||
title="Density"
|
||||
:code="demoCode.density"
|
||||
>
|
||||
<p>You can reduces the file input height with <code>density</code> prop. Available options are: <code>default</code>, <code>comfortable</code>, and <code>compact</code>.</p>
|
||||
|
||||
<DemoFileInputDensity />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Variant -->
|
||||
<AppCardCode
|
||||
title="Variant"
|
||||
:code="demoCode.variant"
|
||||
>
|
||||
<p>use <code>solo</code>, <code>filled</code>, <code>outlined</code>, <code>plain</code> and <code>underlined</code> option of <code>variant</code> prop to change the look of file input.</p>
|
||||
|
||||
<DemoFileInputVariant />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Accept -->
|
||||
<AppCardCode
|
||||
title="Accept"
|
||||
:code="demoCode.accept"
|
||||
>
|
||||
<p><code>v-file-input</code> component can accept only specific media formats/file types if you want.</p>
|
||||
|
||||
<DemoFileInputAccept />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Chips -->
|
||||
<AppCardCode
|
||||
title="Chips"
|
||||
:code="demoCode.chips"
|
||||
>
|
||||
<p>Use <code>chip</code> prop to display the selected file as a chip.</p>
|
||||
|
||||
<DemoFileInputChips />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Counter -->
|
||||
<AppCardCode
|
||||
title="Counter"
|
||||
:code="demoCode.counter"
|
||||
>
|
||||
<p>When using the <code>show-size</code> property along with <code>counter</code>, the total number of files and size will be displayed under the input.</p>
|
||||
|
||||
<DemoFileInputCounter />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Multiple -->
|
||||
<AppCardCode
|
||||
title="Multiple"
|
||||
:code="demoCode.multiple"
|
||||
>
|
||||
<p>
|
||||
The <code>v-file-input</code> can contain multiple files at the same time when using the <code>multiple</code> prop.
|
||||
</p>
|
||||
|
||||
<DemoFileInputMultiple />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Prepend icon -->
|
||||
<AppCardCode
|
||||
title="Prepend icon"
|
||||
:code="demoCode.prependIcon"
|
||||
>
|
||||
<p>
|
||||
The <code>v-file-input</code> has a default <code>prepend-icon</code> that can be set on the component or adjusted globally.
|
||||
</p>
|
||||
|
||||
<DemoFileInputPrependIcon />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Show size -->
|
||||
<AppCardCode
|
||||
title="Show size"
|
||||
:code="demoCode.showSize"
|
||||
>
|
||||
<p>The displayed size of the selected file(s) can be configured with the <code>show-size</code> property.</p>
|
||||
|
||||
<DemoFileInputShowSize />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Validation -->
|
||||
<AppCardCode
|
||||
title="Validation"
|
||||
:code="demoCode.validation"
|
||||
>
|
||||
<p>You can use the <code>rules</code> prop to create your own custom validation parameters.</p>
|
||||
|
||||
<DemoFileInputValidation />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Selection slot -->
|
||||
<AppCardCode
|
||||
title="Selection slot"
|
||||
:code="demoCode.selectionSlot"
|
||||
>
|
||||
<p>Using the <code>selection</code> slot, you can customize the appearance of your input selections.</p>
|
||||
|
||||
<DemoFileInputSelectionSlot />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Loading -->
|
||||
<AppCardCode
|
||||
title="Loading"
|
||||
:code="demoCode.loading"
|
||||
>
|
||||
<p>Use <code>loading</code> prop to displays linear progress bar.</p>
|
||||
|
||||
<DemoFileInputLoading />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
129
resources/js/pages/forms/form-layouts.vue
Normal file
129
resources/js/pages/forms/form-layouts.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-layout/demoCodeFormLayout'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Horizontal Form -->
|
||||
<AppCardCode
|
||||
title="Horizontal Form"
|
||||
:code="demoCode.horizontalForm"
|
||||
>
|
||||
<DemoFormLayoutHorizontalForm />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Horizontal Form with Icons -->
|
||||
<AppCardCode
|
||||
title="Horizontal Form with Icons"
|
||||
:code="demoCode.horizontalFormWithIcons"
|
||||
>
|
||||
<DemoFormLayoutHorizontalFormWithIcons />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Vertical Form -->
|
||||
<AppCardCode
|
||||
title="Vertical Form"
|
||||
:code="demoCode.verticalForm"
|
||||
>
|
||||
<DemoFormLayoutVerticalForm />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Vertical Form with Icons -->
|
||||
<AppCardCode
|
||||
title="Vertical Form with Icons"
|
||||
:code="demoCode.verticalFormWithIcons"
|
||||
>
|
||||
<DemoFormLayoutVerticalFormWithIcons />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Multiple Column -->
|
||||
<AppCardCode
|
||||
title="Multiple Column"
|
||||
:code="demoCode.multipleColumn"
|
||||
>
|
||||
<DemoFormLayoutMultipleColumn />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<VRow class="match-height my-3">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Form Hint -->
|
||||
<AppCardCode
|
||||
title="Form Hint"
|
||||
:code="demoCode.formHint"
|
||||
>
|
||||
<DemoFormLayoutFormHint />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Form Validation -->
|
||||
<AppCardCode
|
||||
title="Form Validation"
|
||||
:code="demoCode.formValidation"
|
||||
>
|
||||
<DemoFormLayoutFormValidation />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Form with Tabs -->
|
||||
<AppCardCode
|
||||
title="Form with Tabs"
|
||||
no-padding
|
||||
:code="demoCode.formWithTabs"
|
||||
>
|
||||
<DemoFormLayoutFormWithTabs />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Collapsible Section -->
|
||||
<h6 class="text-h6 mb-6">
|
||||
Collapsible Section
|
||||
</h6>
|
||||
|
||||
<DemoFormLayoutCollapsible />
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Collapsible Section -->
|
||||
<h6 class="text-h6 mb-6">
|
||||
Sticky Section
|
||||
</h6>
|
||||
|
||||
<DemoFormLayoutSticky />
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
</template>
|
||||
39
resources/js/pages/forms/form-validation.vue
Normal file
39
resources/js/pages/forms/form-validation.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-validation/demoCodeFormValidation'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Simple Form Validation -->
|
||||
<AppCardCode
|
||||
title="Simple Form Validation"
|
||||
:code="demoCode.simpleFormValidation"
|
||||
>
|
||||
<p>Use <code>Rules</code> prop to validate the input.</p>
|
||||
|
||||
<DemoFormValidationSimpleFormValidation />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Validating Multiple Rules -->
|
||||
<AppCardCode
|
||||
title="Validating Multiple Rules"
|
||||
:code="demoCode.validatingMultipleRules"
|
||||
>
|
||||
<DemoFormValidationValidatingMultipleRules />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Validation Types -->
|
||||
<AppCardCode
|
||||
title="Validation Types"
|
||||
:code="demoCode.validationTypes"
|
||||
>
|
||||
<DemoFormValidationValidationTypes />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
73
resources/js/pages/forms/form-wizard-icons.vue
Normal file
73
resources/js/pages/forms/form-wizard-icons.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-wizard/form-wizard-icons/demoCodeFormWizardIcons'
|
||||
import DemoFormWizardIconsBasic from '@/views/demos/forms/form-wizard/form-wizard-icons/DemoFormWizardIconsBasic.vue'
|
||||
import DemoFormWizardIconsModernBasic from '@/views/demos/forms/form-wizard/form-wizard-icons/DemoFormWizardIconsModernBasic.vue'
|
||||
import DemoFormWizardIconsValidation from '@/views/demos/forms/form-wizard/form-wizard-icons/DemoFormWizardIconsValidation.vue'
|
||||
import DemoFormWizardIconsVertical from '@/views/demos/forms/form-wizard/form-wizard-icons/DemoFormWizardIconsVertical.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<!-- 👉 Basic -->
|
||||
<VCol cols="12">
|
||||
<AppCardCode
|
||||
variant="outlined"
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<DemoFormWizardIconsBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Validation -->
|
||||
<VCol cols="12">
|
||||
<AppCardCode
|
||||
variant="outlined"
|
||||
title="Validation"
|
||||
:code="demoCode.validation"
|
||||
>
|
||||
<DemoFormWizardIconsValidation />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Vertical -->
|
||||
<VCol cols="12">
|
||||
<AppCardCode
|
||||
variant="outlined"
|
||||
title="Vertical"
|
||||
:code="demoCode.vertical"
|
||||
>
|
||||
<DemoFormWizardIconsVertical />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<VDivider class="my-10 mx-n6" />
|
||||
<h3 class="text-h3 my-4">
|
||||
Modern
|
||||
</h3>
|
||||
|
||||
<VRow>
|
||||
<!-- 👉 Modern Basic -->
|
||||
<VCol cols="12">
|
||||
<AppCardCode
|
||||
variant="outlined"
|
||||
title="Modern basic"
|
||||
:code="demoCode.modernBasic"
|
||||
>
|
||||
<DemoFormWizardIconsModernBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Modern Vertical -->
|
||||
<VCol cols="12">
|
||||
<AppCardCode
|
||||
variant="outlined"
|
||||
title="Modern Vertical"
|
||||
:code="demoCode.modernVertical"
|
||||
>
|
||||
<DemoFormWizardIconsModernVertical />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
74
resources/js/pages/forms/form-wizard-numbered.vue
Normal file
74
resources/js/pages/forms/form-wizard-numbered.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-wizard/form-wizard-numbered/demoCodeFormWizardNumbered'
|
||||
import DemoFormWizardNumberedModernBasic from '@/views/demos/forms/form-wizard/form-wizard-numbered/DemoFormWizardNumberdModernBasic.vue'
|
||||
import DemoFormWizardNumberedBasic from '@/views/demos/forms/form-wizard/form-wizard-numbered/DemoFormWizardNumberedBasic.vue'
|
||||
import DemoFormWizardNumberedModernVertical from '@/views/demos/forms/form-wizard/form-wizard-numbered/DemoFormWizardNumberedModernVertical.vue'
|
||||
import DemoFormWizardNumberedValidation from '@/views/demos/forms/form-wizard/form-wizard-numbered/DemoFormWizardNumberedValidation.vue'
|
||||
import DemoFormWizardNumberedVertical from '@/views/demos/forms/form-wizard/form-wizard-numbered/DemoFormWizardNumberedVertical.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<!-- 👉 Basic -->
|
||||
<VCol cols="12">
|
||||
<AppCardCode
|
||||
variant="outlined"
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<DemoFormWizardNumberedBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Validation -->
|
||||
<VCol cols="12">
|
||||
<AppCardCode
|
||||
variant="outlined"
|
||||
title="Validation"
|
||||
:code="demoCode.validation"
|
||||
>
|
||||
<DemoFormWizardNumberedValidation />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉 Vertical -->
|
||||
<VCol cols="12">
|
||||
<AppCardCode
|
||||
variant="outlined"
|
||||
title="Vertical"
|
||||
:code="demoCode.vertical"
|
||||
>
|
||||
<DemoFormWizardNumberedVertical />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<VDivider class="my-10 mx-n6" />
|
||||
<h3 class="text-h3 my-4">
|
||||
Modern
|
||||
</h3>
|
||||
|
||||
<VRow>
|
||||
<!-- 👉 Modern Vertical -->
|
||||
<VCol cols="12">
|
||||
<AppCardCode
|
||||
variant="outlined"
|
||||
title="Vertical"
|
||||
:code="demoCode.vertical"
|
||||
>
|
||||
<DemoFormWizardNumberedModernVertical />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<!-- 👉Modern Basic -->
|
||||
<VCol cols="12">
|
||||
<AppCardCode
|
||||
variant="outlined"
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<DemoFormWizardNumberedModernBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
112
resources/js/pages/forms/radio.vue
Normal file
112
resources/js/pages/forms/radio.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-elements/radio/demoCodeRadio'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<p>The <code>v-radio</code> component is a simple radio button.</p>
|
||||
|
||||
<DemoRadioBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Colors -->
|
||||
<AppCardCode
|
||||
title="Colors"
|
||||
:code="demoCode.colors"
|
||||
>
|
||||
<p>Radios can be colored by using any of the built-in colors and contextual names using the <code>color</code> prop.</p>
|
||||
|
||||
<DemoRadioColors />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Inline -->
|
||||
<AppCardCode
|
||||
title="Inline"
|
||||
:code="demoCode.inline"
|
||||
>
|
||||
<p>Use <code>inline</code> prop to displays radio buttons in row.</p>
|
||||
|
||||
<DemoRadioInline />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Density -->
|
||||
<AppCardCode
|
||||
title="Density"
|
||||
:code="demoCode.density"
|
||||
>
|
||||
<p>Use <code>density</code> prop to adjusts the spacing within the component. Available options are: <code>default</code>, <code>comfortable</code>, and <code>compact</code>.</p>
|
||||
|
||||
<DemoRadioDensity />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Label Slot -->
|
||||
<AppCardCode
|
||||
title="Label Slot"
|
||||
:code="demoCode.labelSlot"
|
||||
>
|
||||
<p>Radio Group labels can be defined in <code>label</code> slot - that will allow to use HTML content.</p>
|
||||
|
||||
<DemoRadioLabelSlot />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Icon -->
|
||||
<AppCardCode
|
||||
title="Icon"
|
||||
:code="demoCode.icon"
|
||||
>
|
||||
<p>Use <code>false-icon</code> and <code>true-icon</code> prop to set icon on inactive and active state.</p>
|
||||
|
||||
<DemoRadioIcon />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Validation -->
|
||||
<AppCardCode
|
||||
title="Validation"
|
||||
:code="demoCode.validation"
|
||||
>
|
||||
<p>Use <code>rules</code> prop to validate a radio. Accepts a mixed array of types <code>function</code>, <code>boolean</code> and <code>string</code>. Functions pass an input value as an argument and must return either <code>true</code> / <code>false</code> or a string containing an error message.</p>
|
||||
|
||||
<DemoRadioValidation />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
99
resources/js/pages/forms/range-slider.vue
Normal file
99
resources/js/pages/forms/range-slider.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-elements/range-slider/demoCodeRangeSlider'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<p>The <code>v-slider</code> component is a better visualization of the number input.</p>
|
||||
|
||||
<DemoRangeSliderBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Disabled -->
|
||||
<AppCardCode
|
||||
title="Disabled"
|
||||
:code="demoCode.disabled"
|
||||
>
|
||||
<p>You cannot interact with <code>disabled</code> sliders.</p>
|
||||
|
||||
<DemoRangeSliderDisabled />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Color -->
|
||||
<AppCardCode
|
||||
title="Color"
|
||||
:code="demoCode.color"
|
||||
>
|
||||
<p>Use <code>color</code> prop to the sets the slider color. <code>track-color</code> prop to sets the color of slider's unfilled track.</p>
|
||||
|
||||
<DemoRangeSliderColor />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Step -->
|
||||
<AppCardCode
|
||||
title="Step"
|
||||
:code="demoCode.step"
|
||||
>
|
||||
<p><code>v-range-slider</code> can have steps other than 1. This can be helpful for some applications where you need to adjust values with more or less accuracy.</p>
|
||||
|
||||
<DemoRangeSliderStep />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Thumb label -->
|
||||
<AppCardCode
|
||||
title="Thumb label"
|
||||
:code="demoCode.thumbLabel"
|
||||
>
|
||||
<p>
|
||||
Using the <code>tick-labels</code> prop along with the <code>thumb-label</code> slot, you can create a very customized solution.
|
||||
</p>
|
||||
|
||||
<DemoRangeSliderThumbLabel />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Vertical -->
|
||||
<AppCardCode
|
||||
title="Vertical"
|
||||
:code="demoCode.vertical"
|
||||
>
|
||||
<p>You can use the <code>vertical</code> prop to switch sliders to a vertical orientation. If you need to change the height of the slider, use css.</p>
|
||||
|
||||
<DemoRangeSliderVertical />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
157
resources/js/pages/forms/rating.vue
Normal file
157
resources/js/pages/forms/rating.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-elements/rating/demoCodeRating'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<p>The <code>v-rating</code> component provides a simple interface for gathering user feedback.</p>
|
||||
|
||||
<DemoRatingBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Density -->
|
||||
<AppCardCode
|
||||
title="Density"
|
||||
:code="demoCode.density"
|
||||
>
|
||||
<p>Control the space occupied by <code>v-rating</code> items using the <code>density</code> prop.</p>
|
||||
|
||||
<DemoRatingDensity />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Colors -->
|
||||
<AppCardCode
|
||||
title="Colors"
|
||||
:code="demoCode.colors"
|
||||
>
|
||||
<p>The <code>v-rating</code> component can be colored as you want, you can set both selected and not selected colors.</p>
|
||||
|
||||
<DemoRatingColors />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Size -->
|
||||
<AppCardCode
|
||||
title="Size"
|
||||
:code="demoCode.size"
|
||||
>
|
||||
<p>Utilize the same sizing classes available in <code>v-icon</code> or provide your own with the <code>size</code> prop.</p>
|
||||
|
||||
<DemoRatingSize />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Length -->
|
||||
<AppCardCode
|
||||
title="Length"
|
||||
:code="demoCode.length"
|
||||
>
|
||||
<p>Change the number of items by modifying the the <code>length</code> prop.</p>
|
||||
|
||||
<DemoRatingLength />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Clearable -->
|
||||
<AppCardCode
|
||||
title="Clearable"
|
||||
:code="demoCode.clearable"
|
||||
>
|
||||
<p>Use <code>clearable</code> prop to allows for the component to be cleared. Triggers when the icon containing the current value is clicked.</p>
|
||||
|
||||
<DemoRatingClearable />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Readonly -->
|
||||
<AppCardCode
|
||||
title="Readonly"
|
||||
:code="demoCode.readonly"
|
||||
>
|
||||
<p>For ratings that are not meant to be changed you can use <code>readonly</code> prop.</p>
|
||||
|
||||
<DemoRatingReadonly />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Hover -->
|
||||
<AppCardCode
|
||||
title="Hover"
|
||||
:code="demoCode.hover"
|
||||
>
|
||||
<p>Provides visual feedback when hovering over icons</p>
|
||||
|
||||
<DemoRatingHover />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Incremented -->
|
||||
<AppCardCode
|
||||
title="Incremented"
|
||||
:code="demoCode.incremented"
|
||||
>
|
||||
<p>The <code>half-increments</code> prop increases the granularity of the ratings, allow for .5 values as well.</p>
|
||||
|
||||
<DemoRatingIncremented />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Item slot -->
|
||||
<AppCardCode
|
||||
title="Item slot"
|
||||
:code="demoCode.itemSlot"
|
||||
>
|
||||
<p>Slots enable advanced customization possibilities and provide you with more freedom in how you display the rating.</p>
|
||||
|
||||
<DemoRatingItemSlot />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
139
resources/js/pages/forms/select.vue
Normal file
139
resources/js/pages/forms/select.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-elements/select/demoCodeSelect'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<p>Select fields components are used for collecting user provided information from a list of options.</p>
|
||||
<DemoSelectBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Density -->
|
||||
<AppCardCode
|
||||
title="Density"
|
||||
:code="demoCode.density"
|
||||
>
|
||||
<p>You can use <code>density</code> prop to reduce the field height and lower max height of list items.</p>
|
||||
<DemoSelectDensity />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Variant -->
|
||||
<AppCardCode
|
||||
title="Variant"
|
||||
:code="demoCode.variant"
|
||||
>
|
||||
<p>
|
||||
Use <code>filled</code>, <code>outlined</code>, <code>solo</code>, <code>underlined</code> and <code>plain</code> options of <code>variant</code> prop to change appearance of select.
|
||||
</p>
|
||||
|
||||
<DemoSelectVariant />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Custom text and value -->
|
||||
<AppCardCode
|
||||
title="Custom text and value"
|
||||
:code="demoCode.customTextAndValue"
|
||||
>
|
||||
<p>You can specify the specific properties within your items array that correspond to the title and value fields. In this example we also use the return-object prop which will return the entire object of the selected item on selection.</p>
|
||||
|
||||
<DemoSelectCustomTextAndValue />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Icons -->
|
||||
<AppCardCode
|
||||
title="Icons"
|
||||
:code="demoCode.icons"
|
||||
>
|
||||
<p>Use a custom <code>prepend</code> or <code>appended</code> icon.</p>
|
||||
|
||||
<DemoSelectIcons />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Chips -->
|
||||
<AppCardCode
|
||||
title="Chips"
|
||||
:code="demoCode.chips"
|
||||
>
|
||||
<p>Use <code>chips</code> prop to make selected option as chip.</p>
|
||||
|
||||
<DemoSelectChips />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Menu Props -->
|
||||
<AppCardCode
|
||||
title="Menu Props"
|
||||
:code="demoCode.menuProps"
|
||||
>
|
||||
<p>Custom props can be passed directly to <code>v-menu</code> using <code>menuProps</code> prop.</p>
|
||||
|
||||
<DemoSelectMenuProps />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Multiple -->
|
||||
<AppCardCode
|
||||
title="Multiple"
|
||||
:code="demoCode.multiple"
|
||||
>
|
||||
<p>Use <code>multiple</code> prop to select multiple option.</p>
|
||||
|
||||
<DemoSelectMultiple />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Selection slot -->
|
||||
<AppCardCode
|
||||
title="Selection slot"
|
||||
:code="demoCode.selectionSlot"
|
||||
>
|
||||
<p>The <code>selection</code> slot can be used to customize the way selected values are shown in the input.</p>
|
||||
|
||||
<DemoSelectSelectionSlot />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
204
resources/js/pages/forms/slider.vue
Normal file
204
resources/js/pages/forms/slider.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-elements/slider/demoCodeSlider'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<p>The <code>v-slider</code> component is a better visualization of the number input.</p>
|
||||
|
||||
<DemoSliderBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Disabled and Readonly -->
|
||||
<AppCardCode
|
||||
title="Disabled and Readonly"
|
||||
:code="demoCode.disabledAndReadonly"
|
||||
>
|
||||
<p>You cannot interact with <code>disabled</code> and <code>readonly</code> sliders.</p>
|
||||
|
||||
<DemoSliderDisabledAndReadonly />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Colors -->
|
||||
<AppCardCode
|
||||
title="Colors"
|
||||
:code="demoCode.colors"
|
||||
>
|
||||
<p>You can set the colors of the slider using the props <code>color</code>, <code>track-color</code> and <code>thumb-color</code>.</p>
|
||||
|
||||
<DemoSliderColors />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Icons -->
|
||||
<AppCardCode
|
||||
title="Icons"
|
||||
:code="demoCode.icons"
|
||||
>
|
||||
<p>You can add icons to the slider with the <code>append-icon</code> and <code>prepend-icon</code> props.</p>
|
||||
|
||||
<DemoSliderIcons />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Step -->
|
||||
<AppCardCode
|
||||
title="Step"
|
||||
:code="demoCode.step"
|
||||
>
|
||||
<p>Using the <code>step</code> prop you can control the precision of the slider, and how much it should move each step.</p>
|
||||
|
||||
<DemoSliderStep />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Validation -->
|
||||
<AppCardCode
|
||||
title="Validation"
|
||||
:code="demoCode.validation"
|
||||
>
|
||||
<p>Vuetify includes simple validation through the <code>rules</code> prop.</p>
|
||||
|
||||
<DemoSliderValidation />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Min and Max -->
|
||||
<AppCardCode
|
||||
title="Min and Max"
|
||||
:code="demoCode.minAndMax"
|
||||
>
|
||||
<p>You can set <code>min</code> and <code>max</code> values of sliders.</p>
|
||||
|
||||
<DemoSliderMinAndMax />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Size -->
|
||||
<AppCardCode
|
||||
title="Size"
|
||||
:code="demoCode.size"
|
||||
>
|
||||
<p>Use <code>thumb-size</code>, <code>tick-size</code>, and <code>track-size</code> prop to increase and decrease the size of thumb, tick and track. </p>
|
||||
|
||||
<DemoSliderSize />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Thumb -->
|
||||
<AppCardCode
|
||||
title="Thumb"
|
||||
:code="demoCode.thumb"
|
||||
>
|
||||
<p>You can display a thumb label while sliding or always with the <code>thumb-label</code> prop.</p>
|
||||
|
||||
<DemoSliderThumb />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Ticks -->
|
||||
<AppCardCode
|
||||
title="Ticks"
|
||||
:code="demoCode.ticks"
|
||||
>
|
||||
<p>Tick marks represent predetermined values to which the user can move the slider.</p>
|
||||
|
||||
<DemoSliderTicks />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Vertical -->
|
||||
<AppCardCode
|
||||
title="Vertical"
|
||||
:code="demoCode.vertical"
|
||||
>
|
||||
<p>
|
||||
You can use the <code>vertical</code> prop to switch sliders to a vertical orientation.
|
||||
</p>
|
||||
|
||||
<DemoSliderVertical />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Append text field -->
|
||||
<AppCardCode
|
||||
title="Append text field"
|
||||
:code="demoCode.appendTextField"
|
||||
>
|
||||
<p>Sliders can be combined with other components in its <code>append</code> slot, such as <code>v-text-field</code>, to add additional functionality to the component.</p>
|
||||
|
||||
<DemoSliderAppendTextField />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Append and prepend -->
|
||||
<AppCardCode
|
||||
title="Append and prepend"
|
||||
:code="demoCode.appendAndPrepend"
|
||||
>
|
||||
<p>Use slots such as <code>append</code> and <code>prepend</code> to easily customize the <code>v-slider</code> to fit any situation.</p>
|
||||
|
||||
<DemoSliderAppendAndPrepend />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
114
resources/js/pages/forms/switch.vue
Normal file
114
resources/js/pages/forms/switch.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-elements/switch/demoCodeSwitch'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<p>A <code>v-switch</code> in its simplest form provides a toggle between 2 values.</p>
|
||||
|
||||
<DemoSwitchBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Inset -->
|
||||
<AppCardCode
|
||||
title="Inset"
|
||||
:code="demoCode.inset"
|
||||
>
|
||||
<p>To change the default <code>inset</code> switch, simply modify the inset prop to a <code>false</code> value.</p>
|
||||
|
||||
<DemoSwitchInset />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Colors -->
|
||||
<AppCardCode
|
||||
title="Colors"
|
||||
:code="demoCode.colors"
|
||||
>
|
||||
<p>Switches can be colored by using any of the builtin colors and contextual names using the <code>color</code> prop.</p>
|
||||
|
||||
<DemoSwitchColors />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Model as array -->
|
||||
<AppCardCode
|
||||
title="Model as array"
|
||||
:code="demoCode.modelAsArray"
|
||||
>
|
||||
<p>Multiple <code>v-switch</code>'s can share the same <code>v-model</code> by using an array.</p>
|
||||
|
||||
<DemoSwitchModelAsArray />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Label slot -->
|
||||
<AppCardCode
|
||||
title="Label slot"
|
||||
:code="demoCode.labelSlot"
|
||||
>
|
||||
<p>Switch labels can be defined in <code>label</code> slot - that will allow to use HTML content.</p>
|
||||
|
||||
<DemoSwitchLabelSlot />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 True and False Value -->
|
||||
<AppCardCode
|
||||
title="True and False Value"
|
||||
:code="demoCode.trueAndFalseValue"
|
||||
>
|
||||
<p>
|
||||
Use <code>false-value</code> and <code>true-value</code> prop to sets value for truthy and falsy state
|
||||
</p>
|
||||
|
||||
<DemoSwitchTrueAndFalseValue />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 States -->
|
||||
<AppCardCode
|
||||
title="States"
|
||||
:code="demoCode.states"
|
||||
>
|
||||
<p><code>v-switch</code> can have different states such as <code>default</code>, <code>disabled</code>, and <code>loading</code>.</p>
|
||||
|
||||
<DemoSwitchStates />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
175
resources/js/pages/forms/textarea.vue
Normal file
175
resources/js/pages/forms/textarea.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-elements/textarea/demoCodeTextarea'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<p>
|
||||
v-textarea in its simplest form is a multi-line text-field, useful for larger amounts of text.
|
||||
</p>
|
||||
|
||||
<DemoTextareaBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Auto Grow -->
|
||||
<AppCardCode
|
||||
title="Auto Grow"
|
||||
:code="demoCode.autoGrow"
|
||||
>
|
||||
<p>When using the <code>auto-grow</code> prop, textarea's will automatically increase in size when the contained text exceeds its size.</p>
|
||||
|
||||
<DemoTextareaAutoGrow />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Variant -->
|
||||
<AppCardCode
|
||||
title="Variant"
|
||||
:code="demoCode.variant"
|
||||
>
|
||||
<p>Use <code>filled</code>, <code>plain</code>, <code>outlined</code>, <code>solo</code> and <code>underlined</code> option of <code>variant</code> prop to change the look of file input.</p>
|
||||
|
||||
<DemoTextareaVariant />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 States -->
|
||||
<AppCardCode
|
||||
title="States"
|
||||
:code="demoCode.states"
|
||||
>
|
||||
<p>Use <code>disabled</code> and <code>readonly</code> prop to change the state of textarea.</p>
|
||||
|
||||
<DemoTextareaStates />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Browser autocomplete -->
|
||||
<AppCardCode
|
||||
title="Browser autocomplete"
|
||||
:code="demoCode.browserAutocomplete"
|
||||
>
|
||||
<p>
|
||||
The <code>autocomplete</code> prop gives you the option to enable the browser to predict user input.
|
||||
</p>
|
||||
|
||||
<DemoTextareaBrowserAutocomplete />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Clearable -->
|
||||
<AppCardCode
|
||||
title="Clearable"
|
||||
:code="demoCode.clearable"
|
||||
>
|
||||
<p>You can clear the text from a <code>v-textarea</code> by using the <code>clearable</code> prop, and customize the icon used with the <code>clearable-icon</code> prop.</p>
|
||||
|
||||
<DemoTextareaClearable />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Counter -->
|
||||
<AppCardCode
|
||||
title="Counter"
|
||||
:code="demoCode.counter"
|
||||
>
|
||||
<p>
|
||||
The <code>counter</code> prop informs the user of a character limit for the <code>v-textarea</code>.
|
||||
</p>
|
||||
|
||||
<DemoTextareaCounter />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Icons -->
|
||||
<AppCardCode
|
||||
title="Icons"
|
||||
:code="demoCode.icons"
|
||||
>
|
||||
<p>The <code>append-icon</code>, <code>prepend-icon</code>, <code>append-inner-icon</code> and <code>prepend-inner-icon</code> props help add context to v-textarea.</p>
|
||||
|
||||
<DemoTextareaIcons />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Rows -->
|
||||
<AppCardCode
|
||||
title="Rows"
|
||||
:code="demoCode.rows"
|
||||
>
|
||||
<p>The <code>rows</code> prop allows you to define how many rows the textarea has, when combined with the <code>row-height</code> prop you can further customize your rows by defining their height.</p>
|
||||
|
||||
<DemoTextareaRows />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 No resize -->
|
||||
<AppCardCode
|
||||
title="No resize"
|
||||
:code="demoCode.noResize"
|
||||
>
|
||||
<p><code>v-textarea</code>'s have the option to remain the same size regardless of their content's size, using the <code>no-resize</code> prop.</p>
|
||||
|
||||
<DemoTextareaNoResize />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Validation -->
|
||||
<AppCardCode
|
||||
title="Validation"
|
||||
:code="demoCode.validation"
|
||||
>
|
||||
<p>Use <code>rules</code> prop to validate the textarea.</p>
|
||||
|
||||
<DemoTextareaValidation />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
229
resources/js/pages/forms/textfield.vue
Normal file
229
resources/js/pages/forms/textfield.vue
Normal file
@@ -0,0 +1,229 @@
|
||||
<script setup>
|
||||
import * as demoCode from '@/views/demos/forms/form-elements/textfield/demoCodeTextfield'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow class="match-height">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Basic -->
|
||||
<AppCardCode
|
||||
title="Basic"
|
||||
:code="demoCode.basic"
|
||||
>
|
||||
<p>Text fields components are used for collecting user provided information.</p>
|
||||
|
||||
<DemoTextfieldBasic />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Density -->
|
||||
<AppCardCode
|
||||
title="Density"
|
||||
:code="demoCode.density"
|
||||
>
|
||||
<p>The <code>density</code> prop decreases the height of the text field based upon levels of density; <code>default</code>,<code>comfortable</code>, and <code>compact</code>.</p>
|
||||
|
||||
<DemoTextfieldDensity />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<!-- 👉 Variant -->
|
||||
<AppCardCode
|
||||
title="Variant"
|
||||
:code="demoCode.variant"
|
||||
>
|
||||
<p>Use <code>solo</code>, <code>filled</code>, <code>outlined</code>, <code>plain</code> and <code>underlined</code> option of <code>variant</code> prop to change the look of the textfield. </p>
|
||||
|
||||
<DemoTextfieldVariant />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 State -->
|
||||
<AppCardCode
|
||||
title="State"
|
||||
:code="demoCode.state"
|
||||
>
|
||||
<p>Text fields can be disabled or readonly.</p>
|
||||
|
||||
<DemoTextfieldState />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Counter -->
|
||||
<AppCardCode
|
||||
title="Counter"
|
||||
:code="demoCode.counter"
|
||||
>
|
||||
<p>Use a <code>counter</code> prop to inform a user of the character limit.</p>
|
||||
|
||||
<DemoTextfieldCounter />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Clearable -->
|
||||
<AppCardCode
|
||||
title="Clearable"
|
||||
:code="demoCode.clearable"
|
||||
>
|
||||
<p>When clearable, you can customize the clear icon with clear-icon.</p>
|
||||
|
||||
<DemoTextfieldClearable />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Custom Colors -->
|
||||
<AppCardCode
|
||||
title="Custom Colors"
|
||||
:code="demoCode.customColors"
|
||||
>
|
||||
<p>Use <code>color</code> prop to change the input border color.</p>
|
||||
|
||||
<DemoTextfieldCustomColors />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Icons -->
|
||||
<AppCardCode
|
||||
title="Icons"
|
||||
:code="demoCode.icons"
|
||||
>
|
||||
<p>You can add icons to the text field with <code>prepend-icon</code>, <code>append-icon</code> and <code>append-inner-icon</code> and <code>prepend-inner-icon</code> props.</p>
|
||||
|
||||
<DemoTextfieldIcons />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Prefixes and suffixes -->
|
||||
<AppCardCode
|
||||
title="Prefixes and suffixes"
|
||||
:code="demoCode.prefixesAndSuffixes"
|
||||
>
|
||||
<p>The <code>prefix</code> and <code>suffix</code> properties allows you to prepend and append inline non-modifiable text next to the text field.</p>
|
||||
|
||||
<DemoTextfieldPrefixesAndSuffixes />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Single line -->
|
||||
<AppCardCode
|
||||
title="Single line"
|
||||
:code="demoCode.singleLine"
|
||||
>
|
||||
<p><code>single-line</code> text fields do not float their label on focus or with data.</p>
|
||||
|
||||
<DemoTextfieldSingleLine />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Validation -->
|
||||
<AppCardCode
|
||||
title="Validation"
|
||||
:code="demoCode.validation"
|
||||
>
|
||||
<p>Vuetify includes simple validation through the <code>rules</code> prop.</p>
|
||||
|
||||
<DemoTextfieldValidation />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Icon events -->
|
||||
<AppCardCode
|
||||
title="Icon events"
|
||||
:code="demoCode.iconEvents"
|
||||
>
|
||||
<p><code>click:prepend</code>, <code>click:append</code>, <code>click:append-inner</code>, and <code>click:clear</code> will be emitted when you click on the respective icon</p>
|
||||
|
||||
<DemoTextfieldIconEvents />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Label Slot -->
|
||||
<AppCardCode
|
||||
title="Label Slot"
|
||||
:code="demoCode.labelSlot"
|
||||
>
|
||||
<p>Text field label can be defined in <code>label</code> slot - that will allow to use HTML content.</p>
|
||||
|
||||
<DemoTextfieldLabelSlot />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Password input -->
|
||||
<AppCardCode
|
||||
title="Password input"
|
||||
:code="demoCode.passwordInput"
|
||||
>
|
||||
<p>Using the HTML input <code>type</code> password can be used with an appended icon and callback to control the visibility.</p>
|
||||
|
||||
<DemoTextfieldPasswordInput />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<!-- 👉 Icon slots -->
|
||||
<AppCardCode
|
||||
title="Icon slots"
|
||||
:code="demoCode.iconSlots"
|
||||
>
|
||||
<p>Instead of using <code>prepend</code>/<code>append</code>/<code>append-inner</code> icons you can use slots to extend input's functionality.</p>
|
||||
|
||||
<DemoTextfieldIconSlots />
|
||||
</AppCardCode>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
169
resources/js/pages/front-pages/checkout.vue
Normal file
169
resources/js/pages/front-pages/checkout.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<script setup>
|
||||
import Footer from '@/views/front-pages/front-page-footer.vue'
|
||||
import Navbar from '@/views/front-pages/front-page-navbar.vue'
|
||||
import AddressContent from '@/views/wizard-examples/checkout/Address.vue'
|
||||
import CartContent from '@/views/wizard-examples/checkout/Cart.vue'
|
||||
import ConfirmationContent from '@/views/wizard-examples/checkout/Confirmation.vue'
|
||||
import PaymentContent from '@/views/wizard-examples/checkout/Payment.vue'
|
||||
import googleHome from '@images/pages/google-home.png'
|
||||
import iphone11 from '@images/pages/iphone-11.png'
|
||||
import customAddress from '@images/svg/address.svg'
|
||||
import customCart from '@images/svg/cart.svg'
|
||||
import customPayment from '@images/svg/payment.svg'
|
||||
import customTrending from '@images/svg/trending.svg'
|
||||
import { useConfigStore } from '@core/stores/config'
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
public: true,
|
||||
},
|
||||
})
|
||||
|
||||
const store = useConfigStore()
|
||||
|
||||
store.skin = 'default'
|
||||
|
||||
const checkoutSteps = [
|
||||
{
|
||||
title: 'Cart',
|
||||
icon: customCart,
|
||||
},
|
||||
{
|
||||
title: 'Address',
|
||||
icon: customAddress,
|
||||
},
|
||||
{
|
||||
title: 'Payment',
|
||||
icon: customPayment,
|
||||
},
|
||||
{
|
||||
title: 'Confirmation',
|
||||
icon: customTrending,
|
||||
},
|
||||
]
|
||||
|
||||
const checkoutData = ref({
|
||||
cartItems: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Google - Google Home - White',
|
||||
seller: 'Google',
|
||||
inStock: true,
|
||||
rating: 4,
|
||||
price: 299,
|
||||
discountPrice: 359,
|
||||
image: googleHome,
|
||||
quantity: 1,
|
||||
estimatedDelivery: '18th Nov 2021',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Apple iPhone 11 (64GB, Black)',
|
||||
seller: 'Apple',
|
||||
inStock: true,
|
||||
rating: 4,
|
||||
price: 899,
|
||||
discountPrice: 999,
|
||||
image: iphone11,
|
||||
quantity: 1,
|
||||
estimatedDelivery: '20th Nov 2021',
|
||||
},
|
||||
],
|
||||
promoCode: '',
|
||||
orderAmount: 1198,
|
||||
deliveryAddress: 'home',
|
||||
deliverySpeed: 'free',
|
||||
deliveryCharges: 0,
|
||||
addresses: [
|
||||
{
|
||||
title: 'John Doe (Default)',
|
||||
desc: '4135 Parkway Street, Los Angeles, CA, 90017',
|
||||
subtitle: '1234567890',
|
||||
value: 'home',
|
||||
},
|
||||
{
|
||||
title: 'ACME Inc.',
|
||||
desc: '87 Hoffman Avenue, New York, NY, 10016',
|
||||
subtitle: '1234567890',
|
||||
value: 'office',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const currentStep = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="checkout-page">
|
||||
<Navbar />
|
||||
<VContainer>
|
||||
<div class="checkout-card">
|
||||
<VCard>
|
||||
<VCardText>
|
||||
<!-- 👉 Stepper -->
|
||||
<AppStepper
|
||||
v-model:current-step="currentStep"
|
||||
class="checkout-stepper"
|
||||
:items="checkoutSteps"
|
||||
:direction="$vuetify.display.mdAndUp ? 'horizontal' : 'vertical'"
|
||||
align="center"
|
||||
/>
|
||||
</VCardText>
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<!-- 👉 stepper content -->
|
||||
<VWindow
|
||||
v-model="currentStep"
|
||||
class="disable-tab-transition"
|
||||
:touch="false"
|
||||
>
|
||||
<VWindowItem>
|
||||
<CartContent
|
||||
v-model:current-step="currentStep"
|
||||
v-model:checkout-data="checkoutData"
|
||||
/>
|
||||
</VWindowItem>
|
||||
<VWindowItem>
|
||||
<AddressContent
|
||||
v-model:current-step="currentStep"
|
||||
v-model:checkout-data="checkoutData"
|
||||
/>
|
||||
</VWindowItem>
|
||||
<VWindowItem>
|
||||
<PaymentContent
|
||||
v-model:current-step="currentStep"
|
||||
v-model:checkout-data="checkoutData"
|
||||
/>
|
||||
</VWindowItem>
|
||||
<VWindowItem>
|
||||
<ConfirmationContent v-model:checkout-data="checkoutData" />
|
||||
</VWindowItem>
|
||||
</VWindow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</div>
|
||||
</VContainer>
|
||||
<Footer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.checkout-card {
|
||||
margin-block: 10.5rem 5.25rem;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) and (min-width: 600px) {
|
||||
.checkout-page {
|
||||
.v-container {
|
||||
padding-inline: 2rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.checkout-card {
|
||||
margin-block-start: 6rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
156
resources/js/pages/front-pages/help-center/article/[title].vue
Normal file
156
resources/js/pages/front-pages/help-center/article/[title].vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<script setup>
|
||||
import Footer from '@/views/front-pages/front-page-footer.vue'
|
||||
import Navbar from '@/views/front-pages/front-page-navbar.vue'
|
||||
import { useConfigStore } from '@core/stores/config'
|
||||
|
||||
const store = useConfigStore()
|
||||
|
||||
store.skin = 'default'
|
||||
definePage({
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
public: true,
|
||||
},
|
||||
})
|
||||
|
||||
const articleData = ref()
|
||||
|
||||
setTimeout(async () => {
|
||||
const { data, error } = await useApi('/pages/help-center/article')
|
||||
if (error.value)
|
||||
console.log(error.value)
|
||||
else
|
||||
articleData.value = data.value
|
||||
}, 1000)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div class="bg-surface help-center-article">
|
||||
<!-- 👉 Navbar -->
|
||||
<Navbar />
|
||||
|
||||
<!-- 👉 Content -->
|
||||
<VContainer>
|
||||
<div
|
||||
v-if="articleData && articleData?.title"
|
||||
class="article-section"
|
||||
>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="8"
|
||||
>
|
||||
<div>
|
||||
<VBreadcrumbs
|
||||
class="px-0 pb-2 pt-0 help-center-breadcrumbs"
|
||||
:items="[{ title: 'Help Center', to: { name: 'front-pages-help-center' }, class: 'text-primary' }, { title: 'how to add product in cart' }]"
|
||||
/>
|
||||
<h4 class="text-h4 mb-2">
|
||||
{{ articleData?.title }}
|
||||
</h4>
|
||||
<div class="text-body-1">
|
||||
{{ articleData?.lastUpdated }}
|
||||
</div>
|
||||
</div>
|
||||
<VDivider class="my-6" />
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div
|
||||
class="mb-6 text-body-1"
|
||||
v-html="articleData?.productContent"
|
||||
/>
|
||||
<VImg
|
||||
class="rounded-lg"
|
||||
:src="articleData?.productImg"
|
||||
/>
|
||||
<p class="my-6 text-body-1">
|
||||
{{ articleData?.checkoutContent }}
|
||||
</p>
|
||||
<VImg
|
||||
class="rounded-lg"
|
||||
:src="articleData?.checkoutImg"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<VTextField
|
||||
prepend-inner-icon="tabler-search"
|
||||
placeholder="Search..."
|
||||
class="mb-6"
|
||||
/>
|
||||
<div>
|
||||
<!-- 👉 Article List -->
|
||||
<h5
|
||||
class="text-h5 px-6 py-2 mb-4 rounded"
|
||||
style="background: rgba(var(--v-theme-on-surface), var(--v-hover-opacity));"
|
||||
>
|
||||
Articles in this section
|
||||
</h5>
|
||||
<VList class="card-list">
|
||||
<VListItem
|
||||
v-for="(item, index) in articleData?.articleList"
|
||||
:key="index"
|
||||
link
|
||||
class="text-disabled"
|
||||
>
|
||||
<template #append>
|
||||
<VIcon
|
||||
:icon="$vuetify.locale.isRtl ? 'tabler-chevron-left' : 'tabler-chevron-right'"
|
||||
size="20"
|
||||
/>
|
||||
</template>
|
||||
<div class="text-body-1 text-high-emphasis">
|
||||
{{ item }}
|
||||
</div>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</div>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
</VContainer>
|
||||
|
||||
<!-- 👉 Footer -->
|
||||
<Footer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.article-section {
|
||||
margin-block: 10.5rem 5.25rem;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.article-section {
|
||||
margin-block-start: 6rem;
|
||||
}
|
||||
}
|
||||
|
||||
.card-list {
|
||||
--v-card-list-gap: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
@media (max-width: 960px) and (min-width: 600px) {
|
||||
.help-center-article {
|
||||
.v-container {
|
||||
padding-inline: 2rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.help-center-breadcrumbs {
|
||||
&.v-breadcrumbs {
|
||||
.v-breadcrumbs-item {
|
||||
padding: 0 !important;
|
||||
|
||||
&.v-breadcrumbs-item--disabled {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
123
resources/js/pages/front-pages/help-center/index.vue
Normal file
123
resources/js/pages/front-pages/help-center/index.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<script setup>
|
||||
import Footer from '@/views/front-pages/front-page-footer.vue'
|
||||
import Navbar from '@/views/front-pages/front-page-navbar.vue'
|
||||
import HelpCenterLandingArticlesOverview from '@/views/pages/help-center/HelpCenterLandingArticlesOverview.vue'
|
||||
import HelpCenterLandingFooter from '@/views/pages/help-center/HelpCenterLandingFooter.vue'
|
||||
import HelpCenterLandingKnowledgeBase from '@/views/pages/help-center/HelpCenterLandingKnowledgeBase.vue'
|
||||
import { useConfigStore } from '@core/stores/config'
|
||||
|
||||
// fetching data from fake-api
|
||||
const store = useConfigStore()
|
||||
|
||||
store.skin = 'default'
|
||||
definePage({
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
public: true,
|
||||
},
|
||||
})
|
||||
|
||||
const apiData = ref()
|
||||
|
||||
// // ℹ️ Check if MSW service worker is registered and ready to intercept requests
|
||||
setTimeout(async () => {
|
||||
const faqData = await $api('/pages/help-center')
|
||||
|
||||
apiData.value = faqData
|
||||
}, 1000)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="help-center-page">
|
||||
<Navbar />
|
||||
<div v-if="apiData && apiData.allArticles.length">
|
||||
<AppSearchHeader
|
||||
subtitle="Common troubleshooting topics: eCommerce, Blogging to payment"
|
||||
custom-class="rounded-0"
|
||||
placeholder="Search"
|
||||
>
|
||||
<template #title>
|
||||
<h4
|
||||
class="text-h4 font-weight-medium"
|
||||
style="color: rgba(var(--v-theme-primary), 1);"
|
||||
>
|
||||
Hello, how can we help?
|
||||
</h4>
|
||||
</template>
|
||||
</AppSearchHeader>
|
||||
|
||||
<!-- 👉 Popular Articles -->
|
||||
<div class="help-center-section bg-surface">
|
||||
<VContainer>
|
||||
<h4 class="text-h4 text-center mb-6">
|
||||
Popular Articles
|
||||
</h4>
|
||||
<HelpCenterLandingArticlesOverview :articles="apiData.popularArticles" />
|
||||
</VContainer>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Knowledge Base -->
|
||||
<div class="help-center-section">
|
||||
<VContainer>
|
||||
<h4 class="text-h4 text-center mb-6">
|
||||
Knowledge Base
|
||||
</h4>
|
||||
<HelpCenterLandingKnowledgeBase :categories="apiData.allArticles" />
|
||||
</VContainer>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Keep Learning -->
|
||||
<div class="help-center-section bg-surface">
|
||||
<VContainer>
|
||||
<h4 class="text-h4 text-center mb-6">
|
||||
Keep Learning
|
||||
</h4>
|
||||
<HelpCenterLandingArticlesOverview :articles="apiData.keepLearning" />
|
||||
</VContainer>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Still need help? -->
|
||||
<div class="help-center-section">
|
||||
<HelpCenterLandingFooter />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.help-center-page {
|
||||
.search-header {
|
||||
background-size: cover !important;
|
||||
padding-block: 9.25rem 4.75rem !important;
|
||||
}
|
||||
|
||||
.help-center-section {
|
||||
padding-block: 5.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 960px) and (min-width: 600px) {
|
||||
.help-center-page {
|
||||
.v-container {
|
||||
padding-inline: 2rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 599px) {
|
||||
.help-center-page {
|
||||
.search-header {
|
||||
padding-block: 7rem 2rem !important;
|
||||
padding-inline: 2rem !important;
|
||||
}
|
||||
|
||||
.help-center-section {
|
||||
padding-block: 3.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
98
resources/js/pages/front-pages/landing-page/index.vue
Normal file
98
resources/js/pages/front-pages/landing-page/index.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<script setup>
|
||||
import Footer from '@/views/front-pages/front-page-footer.vue'
|
||||
import Navbar from '@/views/front-pages/front-page-navbar.vue'
|
||||
import Banner from '@/views/front-pages/landing-page/banner.vue'
|
||||
import ContactUs from '@/views/front-pages/landing-page/contact-us.vue'
|
||||
import CustomersReview from '@/views/front-pages/landing-page/customers-review.vue'
|
||||
import FaqSection from '@/views/front-pages/landing-page/faq-section.vue'
|
||||
import Features from '@/views/front-pages/landing-page/features.vue'
|
||||
import HeroSection from '@/views/front-pages/landing-page/hero-section.vue'
|
||||
import OurTeam from '@/views/front-pages/landing-page/our-team.vue'
|
||||
import PricingPlans from '@/views/front-pages/landing-page/pricing-plans.vue'
|
||||
import ProductStats from '@/views/front-pages/landing-page/product-stats.vue'
|
||||
import { useConfigStore } from '@core/stores/config'
|
||||
|
||||
const store = useConfigStore()
|
||||
|
||||
store.skin = 'default'
|
||||
definePage({
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
public: true,
|
||||
},
|
||||
})
|
||||
|
||||
const activeSectionId = ref()
|
||||
const refHome = ref()
|
||||
const refFeatures = ref()
|
||||
const refTeam = ref()
|
||||
const refContact = ref()
|
||||
const refFaq = ref()
|
||||
|
||||
useIntersectionObserver([
|
||||
refHome,
|
||||
refFeatures,
|
||||
refTeam,
|
||||
refContact,
|
||||
refFaq,
|
||||
], ([{ isIntersecting, target }]) => {
|
||||
if (isIntersecting)
|
||||
activeSectionId.value = target.id
|
||||
}, { threshold: 0.25 })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="landing-page-wrapper">
|
||||
<Navbar :active-id="activeSectionId" />
|
||||
|
||||
<!-- 👉 Hero Section -->
|
||||
<HeroSection ref="refHome" />
|
||||
|
||||
<!-- 👉 Useful features -->
|
||||
<div :style="{ 'background-color': 'rgb(var(--v-theme-surface))' }">
|
||||
<Features ref="refFeatures" />
|
||||
</div>
|
||||
|
||||
<!-- 👉 Customer Review -->
|
||||
<div :style="{ 'background-color': 'rgb(var(--v-theme-surface))' }">
|
||||
<CustomersReview />
|
||||
</div>
|
||||
|
||||
<!-- 👉 Our Team -->
|
||||
<div :style="{ 'background-color': 'rgb(var(--v-theme-surface))' }">
|
||||
<OurTeam ref="refTeam" />
|
||||
</div>
|
||||
|
||||
<!-- 👉 Pricing Plans -->
|
||||
<div :style="{ 'background-color': 'rgb(var(--v-theme-surface))' }">
|
||||
<PricingPlans />
|
||||
</div>
|
||||
|
||||
<!-- 👉 Product stats -->
|
||||
<ProductStats />
|
||||
|
||||
<!-- 👉 FAQ Section -->
|
||||
<div :style="{ 'background-color': 'rgb(var(--v-theme-surface))' }">
|
||||
<FaqSection ref="refFaq" />
|
||||
</div>
|
||||
|
||||
<!-- 👉 Banner -->
|
||||
<Banner />
|
||||
|
||||
<!-- 👉 Contact Us -->
|
||||
<ContactUs ref="refContact" />
|
||||
|
||||
<!-- 👉 Footer -->
|
||||
<Footer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@media (max-width: 960px) and (min-width: 600px) {
|
||||
.landing-page-wrapper {
|
||||
.v-container {
|
||||
padding-inline: 2rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
308
resources/js/pages/front-pages/payment.vue
Normal file
308
resources/js/pages/front-pages/payment.vue
Normal file
@@ -0,0 +1,308 @@
|
||||
<script setup>
|
||||
import Footer from '@/views/front-pages/front-page-footer.vue'
|
||||
import Navbar from '@/views/front-pages/front-page-navbar.vue'
|
||||
import paypalDark from '@images/icons/payments/img/paypal-dark.png'
|
||||
import paypalLight from '@images/icons/payments/img/paypal-light.png'
|
||||
import visaDark from '@images/icons/payments/img/visa-dark.png'
|
||||
import visaLight from '@images/icons/payments/img/visa-light.png'
|
||||
import { useConfigStore } from '@core/stores/config'
|
||||
|
||||
const visa = useGenerateImageVariant(visaLight, visaDark)
|
||||
const paypal = useGenerateImageVariant(paypalLight, paypalDark)
|
||||
const store = useConfigStore()
|
||||
|
||||
store.skin = 'default'
|
||||
definePage({
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
public: true,
|
||||
},
|
||||
})
|
||||
|
||||
const radioContent = [
|
||||
{
|
||||
title: 'Credit Card',
|
||||
value: 'credit card',
|
||||
images: visa.value,
|
||||
},
|
||||
{
|
||||
title: 'PayPal',
|
||||
value: 'paypal',
|
||||
images: paypal.value,
|
||||
},
|
||||
]
|
||||
|
||||
const selectedRadio = ref('credit card')
|
||||
const selectedCountry = ref('USA')
|
||||
const isPricingPlanDialogVisible = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- eslint-disable vue/attribute-hyphenation -->
|
||||
|
||||
<div class="payment-page">
|
||||
<!-- 👉 Navbar -->
|
||||
<Navbar />
|
||||
|
||||
<!-- 👉 Payment card -->
|
||||
<VContainer>
|
||||
<div class="d-flex justify-center align-center payment-card">
|
||||
<VCard width="100%">
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="7"
|
||||
:class="$vuetify.display.mdAndUp ? 'border-e' : 'border-b'"
|
||||
>
|
||||
<VCardText class="pa-8 pe-5">
|
||||
<!-- Checkout header -->
|
||||
<div>
|
||||
<h4 class="text-h4 mb-2">
|
||||
Checkout
|
||||
</h4>
|
||||
<div class="text-body-1">
|
||||
All plans include 40+ advanced tools and features to boost your product. Choose the best plan to fit your needs.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CustomRadios
|
||||
v-model:selected-radio="selectedRadio"
|
||||
:radio-content="radioContent"
|
||||
:grid-column="{ cols: '12', sm: '6' }"
|
||||
class="my-8"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<div class="d-flex align-center gap-x-4 ms-3">
|
||||
<img
|
||||
:src="item.images"
|
||||
height="34"
|
||||
>
|
||||
<h6 class="text-h6">
|
||||
{{ item.title }}
|
||||
</h6>
|
||||
</div>
|
||||
</template>
|
||||
</CustomRadios>
|
||||
|
||||
<!-- billing Details -->
|
||||
<div class="mb-8">
|
||||
<h4 class="text-h4 mb-6">
|
||||
Billing Details
|
||||
</h4>
|
||||
<VForm>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
label="Email Address"
|
||||
type="email"
|
||||
placeholder="johndoe@email.com"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
label="Password"
|
||||
type="password"
|
||||
placeholder="············"
|
||||
autocomplete="on"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppSelect
|
||||
v-model="selectedCountry"
|
||||
label="Billing Country"
|
||||
:items="['USA', 'Canada', 'UK', 'AUS']"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
label="Billing Zip/Postal Code"
|
||||
type="number"
|
||||
placeholder="129211"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</div>
|
||||
|
||||
<!-- Credit card info -->
|
||||
<div
|
||||
class="mb-8"
|
||||
:class="selectedRadio === 'paypal' ? 'd-none' : 'd-block'"
|
||||
>
|
||||
<h4 class="text-h4 mb-6">
|
||||
Credit Card Info
|
||||
</h4>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
label="Card Number"
|
||||
placeholder="8787 2345 3458"
|
||||
type="number"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<AppTextField
|
||||
label="Card Holder"
|
||||
placeholder="John Doe"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<AppTextField
|
||||
label="Exp. date"
|
||||
placeholder="05/2020"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<AppTextField
|
||||
label="CVV"
|
||||
type="number"
|
||||
placeholder="784"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="5"
|
||||
>
|
||||
<VCardText class="pa-8 ps-5">
|
||||
<!-- order summary -->
|
||||
<div class="mb-8">
|
||||
<h4 class="text-h4 mb-2">
|
||||
Order Summary
|
||||
</h4>
|
||||
<div class="text-body-1">
|
||||
It can help you manage and service orders before, during, and after fulfillment.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<VCard
|
||||
flat
|
||||
color="rgba(var(--v-theme-on-surface), var(--v-hover-opacity))"
|
||||
>
|
||||
<VCardText>
|
||||
<div class="text-body-1">
|
||||
A simple start for everyone
|
||||
</div>
|
||||
<h1 class="text-h1 my-4">
|
||||
$59.99<span class="text-body-1 font-weight-medium">/month</span>
|
||||
</h1>
|
||||
<VBtn
|
||||
variant="tonal"
|
||||
block
|
||||
@click="isPricingPlanDialogVisible = !isPricingPlanDialogVisible"
|
||||
>
|
||||
Change Plan
|
||||
</VBtn>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<div class="my-5">
|
||||
<div class="d-flex justify-space-between mb-2">
|
||||
<span>Subscription</span>
|
||||
<h6 class="text-h6">
|
||||
$85.99
|
||||
</h6>
|
||||
</div>
|
||||
<div class="d-flex justify-space-between">
|
||||
<span>Tax</span>
|
||||
<h6 class="text-h6">
|
||||
$4.99
|
||||
</h6>
|
||||
</div>
|
||||
<VDivider class="my-4" />
|
||||
<div class="d-flex justify-space-between">
|
||||
<span>Total</span>
|
||||
<h6 class="text-h6">
|
||||
$90.98
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<VBtn
|
||||
block
|
||||
color="success"
|
||||
class="mb-8"
|
||||
>
|
||||
<template #append>
|
||||
<VIcon
|
||||
icon="tabler-arrow-right"
|
||||
class="flip-in-rtl"
|
||||
/>
|
||||
</template>
|
||||
Proceed With Payment
|
||||
</VBtn>
|
||||
|
||||
<div class="text-body-1">
|
||||
By continuing, you accept to our Terms of Services and Privacy Policy. Please note that payments are non-refundable.
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCard>
|
||||
</div>
|
||||
</VContainer>
|
||||
|
||||
<!-- 👉 Footer -->
|
||||
<Footer />
|
||||
|
||||
<PricingPlanDialog v-model:is-dialog-visible="isPricingPlanDialogVisible" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.footer {
|
||||
position: static !important;
|
||||
inline-size: 100%;
|
||||
inset-block-end: 0;
|
||||
}
|
||||
|
||||
.payment-card {
|
||||
margin-block: 10.5rem 5.25rem;
|
||||
}
|
||||
|
||||
.payment-page {
|
||||
@media (min-width: 600px) and (max-width: 960px) {
|
||||
.v-container {
|
||||
padding-inline: 2rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.payment-card {
|
||||
.custom-radio {
|
||||
.v-radio {
|
||||
margin-block-start: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
406
resources/js/pages/front-pages/pricing.vue
Normal file
406
resources/js/pages/front-pages/pricing.vue
Normal file
@@ -0,0 +1,406 @@
|
||||
<script setup>
|
||||
import Footer from '@/views/front-pages/front-page-footer.vue'
|
||||
import Navbar from '@/views/front-pages/front-page-navbar.vue'
|
||||
import { useConfigStore } from '@core/stores/config'
|
||||
import laptopGirl from '@images/illustrations/laptop-girl.png'
|
||||
|
||||
const store = useConfigStore()
|
||||
|
||||
store.skin = 'default'
|
||||
definePage({
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
public: true,
|
||||
},
|
||||
})
|
||||
|
||||
const features = [
|
||||
{
|
||||
feature: '14-days free trial',
|
||||
starter: true,
|
||||
pro: true,
|
||||
enterprise: true,
|
||||
addOnAvailable: {
|
||||
starter: false,
|
||||
pro: false,
|
||||
enterprise: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
feature: 'No user limit',
|
||||
starter: false,
|
||||
pro: false,
|
||||
enterprise: true,
|
||||
addOnAvailable: {
|
||||
starter: false,
|
||||
pro: false,
|
||||
enterprise: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
feature: 'Product Support',
|
||||
starter: false,
|
||||
pro: true,
|
||||
enterprise: true,
|
||||
addOnAvailable: {
|
||||
starter: false,
|
||||
pro: false,
|
||||
enterprise: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
feature: 'Email Support',
|
||||
starter: false,
|
||||
pro: false,
|
||||
enterprise: true,
|
||||
addOnAvailable: {
|
||||
starter: false,
|
||||
pro: true,
|
||||
enterprise: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
feature: 'Integrations',
|
||||
starter: false,
|
||||
pro: true,
|
||||
enterprise: true,
|
||||
addOnAvailable: {
|
||||
starter: false,
|
||||
pro: false,
|
||||
enterprise: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
feature: 'Removal of Front branding',
|
||||
starter: false,
|
||||
pro: false,
|
||||
enterprise: true,
|
||||
addOnAvailable: {
|
||||
starter: false,
|
||||
pro: true,
|
||||
enterprise: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
feature: 'Active maintenance & support',
|
||||
starter: false,
|
||||
pro: false,
|
||||
enterprise: true,
|
||||
addOnAvailable: {
|
||||
starter: false,
|
||||
pro: false,
|
||||
enterprise: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
feature: 'Data storage for 365 days',
|
||||
starter: false,
|
||||
pro: false,
|
||||
enterprise: true,
|
||||
addOnAvailable: {
|
||||
starter: false,
|
||||
pro: false,
|
||||
enterprise: false,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: 'How do you process payments?',
|
||||
answer: 'We accept Visa®, MasterCard®, American Express®, and PayPal®. So you can be confident that your credit card information will be kept safe and secure.',
|
||||
},
|
||||
{
|
||||
question: 'What counts towards the 100 responses limit?',
|
||||
answer: 'We count all responses submitted through all forms in a month.If you already received 100 responses this month, you won\'t be able to receive any more of them until next month when the counter resets.',
|
||||
},
|
||||
{
|
||||
question: 'What payment methods do you accept?',
|
||||
answer: 'Checkout accepts all type of credit and debit cards.',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pricing-page">
|
||||
<Navbar />
|
||||
|
||||
<VCard class="pricing-card">
|
||||
<!-- 👉 App Pricing components -->
|
||||
<VContainer>
|
||||
<AppPricing md="4" />
|
||||
</VContainer>
|
||||
|
||||
<!-- 👉 Free trial Banner -->
|
||||
|
||||
<div class="page-pricing-free-trial-banner-bg">
|
||||
<VContainer>
|
||||
<div class="d-flex align-center flex-md-row flex-column position-relative">
|
||||
<div class="text-center text-md-start pb-5 px-10 px-sm-0 pt-8">
|
||||
<h4 class="text-h4 text-primary mb-2">
|
||||
Still not convinced? Start with a 14-day FREE trial!
|
||||
</h4>
|
||||
<p class="text-body-1 mb-11">
|
||||
You will get full access to all the features for 14 days.
|
||||
</p>
|
||||
<VBtn :to="{ name: 'front-pages-payment' }">
|
||||
Start 14-day FREE trial
|
||||
</VBtn>
|
||||
</div>
|
||||
<div class="free-trial-illustrator">
|
||||
<VImg
|
||||
:src="laptopGirl"
|
||||
:width="238"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</VContainer>
|
||||
</div>
|
||||
|
||||
<!-- 👉 Plans -->
|
||||
<VContainer>
|
||||
<VCardText class="text-center py-16 pricing-section">
|
||||
<h3 class="text-h3 mb-2">
|
||||
Pick a plan that works best for you
|
||||
</h3>
|
||||
<p class="text-body-1">
|
||||
Stay cool, we have a 48-hour money back guarantee!
|
||||
</p>
|
||||
<!-- 👉 Features & Tables -->
|
||||
<VTable class="text-no-wrap border rounded pricing-table">
|
||||
<!-- 👉 Table head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
class="py-4"
|
||||
>
|
||||
<div>
|
||||
Features
|
||||
</div>
|
||||
<div class="text-body-2">
|
||||
Native Font Features
|
||||
</div>
|
||||
</th>
|
||||
<th
|
||||
v-for="{ plan, price } in [
|
||||
{ plan: 'Starter', price: 'Free' },
|
||||
{ plan: 'Pro', price: '$7.5/Month' },
|
||||
{ plan: 'Enterprise', price: '$16/Month' },
|
||||
]"
|
||||
:key="plan"
|
||||
scope="col"
|
||||
class="text-center py-4"
|
||||
>
|
||||
<div class="position-relative">
|
||||
{{ plan }}
|
||||
<VAvatar
|
||||
v-if="plan === 'Pro'"
|
||||
size="20"
|
||||
class="ms-2 position-absolute"
|
||||
variant="elevated"
|
||||
color="primary"
|
||||
style="inset-block-end: 7px;"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-star"
|
||||
size="14"
|
||||
color="white"
|
||||
/>
|
||||
</VAvatar>
|
||||
</div>
|
||||
<div class="text-body-2">
|
||||
{{ price }}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<!-- 👉 Table Body -->
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="feature in features"
|
||||
:key="feature.feature"
|
||||
>
|
||||
<td class="text-start text-body-1 text-high-emphasis">
|
||||
{{ feature.feature }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<VAvatar
|
||||
variant="tonal"
|
||||
size="20"
|
||||
:color="feature.starter ? 'primary' : 'secondary'"
|
||||
>
|
||||
<VIcon
|
||||
v-if="!feature.addOnAvailable.starter"
|
||||
:color="feature.starter ? 'primary' : 'secondary'"
|
||||
size="14"
|
||||
:icon="feature.starter ? 'tabler-check' : 'tabler-x'"
|
||||
/>
|
||||
</VAvatar>
|
||||
<VChip
|
||||
v-if="feature.addOnAvailable.starter"
|
||||
color="primary"
|
||||
size="small"
|
||||
label
|
||||
>
|
||||
Add-On Available
|
||||
</VChip>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<VChip
|
||||
v-if="feature.addOnAvailable.pro"
|
||||
color="primary"
|
||||
size="small"
|
||||
label
|
||||
>
|
||||
Add-On Available
|
||||
</VChip>
|
||||
<VAvatar
|
||||
v-else
|
||||
size="20"
|
||||
variant="tonal"
|
||||
:color="feature.pro ? 'primary' : 'secondary'"
|
||||
>
|
||||
<VIcon
|
||||
:color="feature.pro ? 'primary' : 'secondary'"
|
||||
size="14"
|
||||
:icon="feature.pro ? 'tabler-check' : 'tabler-x'"
|
||||
/>
|
||||
</VAvatar>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<VChip
|
||||
v-if="feature.addOnAvailable.enterprise"
|
||||
label
|
||||
color="primary"
|
||||
size="small"
|
||||
>
|
||||
Add-On Available
|
||||
</VChip>
|
||||
<VAvatar
|
||||
v-else
|
||||
size="20"
|
||||
variant="tonal"
|
||||
:color="feature.enterprise ? 'primary' : 'disabled'"
|
||||
>
|
||||
<VIcon
|
||||
:color="feature.enterprise ? 'primary' : 'disabled'"
|
||||
size="14"
|
||||
:icon="feature.enterprise ? 'tabler-check' : 'tabler-x'"
|
||||
/>
|
||||
</VAvatar>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<!-- 👉 Table footer -->
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td class="py-2" />
|
||||
<td class="text-center py-2">
|
||||
<VBtn
|
||||
variant="tonal"
|
||||
:to="{ name: 'front-pages-payment' }"
|
||||
>
|
||||
Choose Plan
|
||||
</VBtn>
|
||||
</td>
|
||||
<td class="text-center py-2">
|
||||
<VBtn :to="{ name: 'front-pages-payment' }">
|
||||
Choose Plan
|
||||
</VBtn>
|
||||
</td>
|
||||
<td class="text-center py-2">
|
||||
<VBtn
|
||||
variant="tonal"
|
||||
:to="{ name: 'front-pages-payment' }"
|
||||
>
|
||||
Choose Plan
|
||||
</VBtn>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</VTable>
|
||||
</VCardText>
|
||||
</VContainer>
|
||||
|
||||
<!-- 👉 FAQ -->
|
||||
<div style="background-color: rgba(var(--v-theme-on-surface), var(--v-hover-opacity));">
|
||||
<VContainer>
|
||||
<VCardText class="py-10 py-sm-16 pricing-section">
|
||||
<div class="text-center">
|
||||
<h4 class="text-h4 mb-2">
|
||||
FAQ's
|
||||
</h4>
|
||||
<p class="text-body-1 mb-6">
|
||||
Let us help answer the most common questions.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<VExpansionPanels>
|
||||
<VExpansionPanel
|
||||
v-for="(faq, index) in faqs"
|
||||
:key="faq.question"
|
||||
:title="faq.question"
|
||||
:text="faq.answer"
|
||||
:value="index"
|
||||
/>
|
||||
</VExpansionPanels>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VContainer>
|
||||
</div>
|
||||
|
||||
<div style="background-color: rgba(var(--v-theme-on-surface), var(--v-hover-opacity));">
|
||||
<Footer />
|
||||
</div>
|
||||
</VCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pricing-section {
|
||||
padding-block: 5.25rem !important;
|
||||
padding-inline: 0 !important;
|
||||
}
|
||||
|
||||
.page-pricing-free-trial-banner-bg {
|
||||
/* stylelint-disable-next-line color-function-notation */
|
||||
background-color: rgba(var(--v-theme-primary), var(--v-activated-opacity));
|
||||
margin-block-start: 8.9375rem !important;
|
||||
}
|
||||
|
||||
.pricing-card {
|
||||
padding-block-start: 10.5rem !important;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 960px) {
|
||||
.free-trial-illustrator {
|
||||
position: absolute;
|
||||
inset-block-end: -1rem !important;
|
||||
inset-inline-end: 0%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 959px) {
|
||||
.free-trial-illustrator {
|
||||
position: relative;
|
||||
inset-block-end: -1rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.pricing-table {
|
||||
tr:nth-child(even) {
|
||||
background: rgba(var(--v-theme-on-surface), var(--v-hover-opacity));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.pricing-page {
|
||||
@media (min-width: 600px) and (max-width: 960px) {
|
||||
.v-container {
|
||||
padding-inline: 2rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
243
resources/js/pages/login.vue
Normal file
243
resources/js/pages/login.vue
Normal file
@@ -0,0 +1,243 @@
|
||||
<!-- ❗Errors in the form are set on line 60 -->
|
||||
<script setup>
|
||||
import { VForm } from 'vuetify/components/VForm'
|
||||
import AuthProvider from '@/views/pages/authentication/AuthProvider.vue'
|
||||
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
|
||||
import authV2LoginIllustrationBorderedDark from '@images/pages/auth-v2-login-illustration-bordered-dark.png'
|
||||
import authV2LoginIllustrationBorderedLight from '@images/pages/auth-v2-login-illustration-bordered-light.png'
|
||||
import authV2LoginIllustrationDark from '@images/pages/auth-v2-login-illustration-dark.png'
|
||||
import authV2LoginIllustrationLight from '@images/pages/auth-v2-login-illustration-light.png'
|
||||
import authV2MaskDark from '@images/pages/misc-mask-dark.png'
|
||||
import authV2MaskLight from '@images/pages/misc-mask-light.png'
|
||||
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
|
||||
import { themeConfig } from '@themeConfig'
|
||||
|
||||
const authThemeImg = useGenerateImageVariant(authV2LoginIllustrationLight, authV2LoginIllustrationDark, authV2LoginIllustrationBorderedLight, authV2LoginIllustrationBorderedDark, true)
|
||||
const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
unauthenticatedOnly: true,
|
||||
},
|
||||
})
|
||||
|
||||
const isPasswordVisible = ref(false)
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const ability = useAbility()
|
||||
|
||||
const errors = ref({
|
||||
email: undefined,
|
||||
password: undefined,
|
||||
})
|
||||
|
||||
const refVForm = ref()
|
||||
|
||||
const credentials = ref({
|
||||
email: 'admin@demo.com',
|
||||
password: 'admin',
|
||||
})
|
||||
|
||||
const rememberMe = ref(false)
|
||||
|
||||
const login = async () => {
|
||||
try {
|
||||
const res = await $api('/auth/login', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
email: credentials.value.email,
|
||||
password: credentials.value.password,
|
||||
},
|
||||
onResponseError({ response }) {
|
||||
errors.value = response._data.errors
|
||||
},
|
||||
})
|
||||
|
||||
const { accessToken, userData, userAbilityRules } = res
|
||||
|
||||
useCookie('userAbilityRules').value = userAbilityRules
|
||||
ability.update(userAbilityRules)
|
||||
useCookie('userData').value = userData
|
||||
useCookie('accessToken').value = accessToken
|
||||
await nextTick(() => {
|
||||
router.replace(route.query.to ? String(route.query.to) : '/')
|
||||
})
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
refVForm.value?.validate().then(({ valid: isValid }) => {
|
||||
if (isValid)
|
||||
login()
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterLink to="/">
|
||||
<div class="auth-logo d-flex align-center gap-x-3">
|
||||
<VNodeRenderer :nodes="themeConfig.app.logo" />
|
||||
<h1 class="auth-title">
|
||||
{{ themeConfig.app.title }}
|
||||
</h1>
|
||||
</div>
|
||||
</RouterLink>
|
||||
|
||||
<VRow
|
||||
no-gutters
|
||||
class="auth-wrapper bg-surface"
|
||||
>
|
||||
<VCol
|
||||
md="8"
|
||||
class="d-none d-md-flex"
|
||||
>
|
||||
<div class="position-relative bg-background w-100 me-0">
|
||||
<div
|
||||
class="d-flex align-center justify-center w-100 h-100"
|
||||
style="padding-inline: 6.25rem;"
|
||||
>
|
||||
<VImg
|
||||
max-width="613"
|
||||
:src="authThemeImg"
|
||||
class="auth-illustration mt-16 mb-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<img
|
||||
class="auth-footer-mask"
|
||||
:src="authThemeMask"
|
||||
alt="auth-footer-mask"
|
||||
height="280"
|
||||
width="100"
|
||||
>
|
||||
</div>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
class="auth-card-v2 d-flex align-center justify-center"
|
||||
>
|
||||
<VCard
|
||||
flat
|
||||
:max-width="500"
|
||||
class="mt-12 mt-sm-0 pa-4"
|
||||
>
|
||||
<VCardText>
|
||||
<h4 class="text-h4 mb-1">
|
||||
Welcome to <span class="text-capitalize"> {{ themeConfig.app.title }} </span>! 👋🏻
|
||||
</h4>
|
||||
<p class="mb-0">
|
||||
Please sign-in to your account and start the adventure
|
||||
</p>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VAlert
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
>
|
||||
<p class="text-sm mb-2">
|
||||
Admin Email: <strong>admin@demo.com</strong> / Pass: <strong>admin</strong>
|
||||
</p>
|
||||
<p class="text-sm mb-0">
|
||||
Client Email: <strong>client@demo.com</strong> / Pass: <strong>client</strong>
|
||||
</p>
|
||||
</VAlert>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm
|
||||
ref="refVForm"
|
||||
@submit.prevent="onSubmit"
|
||||
>
|
||||
<VRow>
|
||||
<!-- email -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="credentials.email"
|
||||
label="Email"
|
||||
placeholder="johndoe@email.com"
|
||||
type="email"
|
||||
autofocus
|
||||
:rules="[requiredValidator, emailValidator]"
|
||||
:error-messages="errors.email"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- password -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="credentials.password"
|
||||
label="Password"
|
||||
placeholder="············"
|
||||
:rules="[requiredValidator]"
|
||||
:type="isPasswordVisible ? 'text' : 'password'"
|
||||
autocomplete="password"
|
||||
:error-messages="errors.password"
|
||||
:append-inner-icon="isPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
|
||||
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
||||
/>
|
||||
|
||||
<div class="d-flex align-center flex-wrap justify-space-between my-6">
|
||||
<VCheckbox
|
||||
v-model="rememberMe"
|
||||
label="Remember me"
|
||||
/>
|
||||
<RouterLink
|
||||
class="text-primary ms-2 mb-1"
|
||||
:to="{ name: 'forgot-password' }"
|
||||
>
|
||||
Forgot Password?
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<VBtn
|
||||
block
|
||||
type="submit"
|
||||
>
|
||||
Login
|
||||
</VBtn>
|
||||
</VCol>
|
||||
|
||||
<!-- create account -->
|
||||
<VCol
|
||||
cols="12"
|
||||
class="text-center"
|
||||
>
|
||||
<span>New on our platform?</span>
|
||||
<RouterLink
|
||||
class="text-primary ms-1"
|
||||
:to="{ name: 'register' }"
|
||||
>
|
||||
Create an account
|
||||
</RouterLink>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
class="d-flex align-center"
|
||||
>
|
||||
<VDivider />
|
||||
<span class="mx-4">or</span>
|
||||
<VDivider />
|
||||
</VCol>
|
||||
|
||||
<!-- auth providers -->
|
||||
<VCol
|
||||
cols="12"
|
||||
class="text-center"
|
||||
>
|
||||
<AuthProvider />
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/pages/page-auth";
|
||||
</style>
|
||||
54
resources/js/pages/not-authorized.vue
Normal file
54
resources/js/pages/not-authorized.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<script setup>
|
||||
import pages401 from '@images/pages/401.png'
|
||||
import miscMaskDark from '@images/pages/misc-mask-dark.png'
|
||||
import miscMaskLight from '@images/pages/misc-mask-light.png'
|
||||
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
|
||||
|
||||
definePage({
|
||||
alias: '/pages/misc/not-authorized',
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
public: true,
|
||||
},
|
||||
})
|
||||
|
||||
const authThemeMask = useGenerateImageVariant(miscMaskLight, miscMaskDark)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="misc-wrapper">
|
||||
<ErrorHeader
|
||||
status-code="401"
|
||||
title="You are not authorized! 🔐"
|
||||
description="You don’t have permission to access this page. Go Home!."
|
||||
/>
|
||||
|
||||
<VBtn
|
||||
class="mb-11"
|
||||
to="/"
|
||||
>
|
||||
Back To Home
|
||||
</VBtn>
|
||||
|
||||
<!-- 👉 Image -->
|
||||
<div class="misc-avatar w-100 text-center">
|
||||
<VImg
|
||||
:src="pages401"
|
||||
alt="not autorized"
|
||||
:max-height="$vuetify.display.smAndDown ? 350 : 500"
|
||||
class="mx-auto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<img
|
||||
class="misc-footer-img d-none d-md-block"
|
||||
:src="authThemeMask"
|
||||
alt="misc-footer-img"
|
||||
height="320"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/pages/misc.scss";
|
||||
</style>
|
||||
99
resources/js/pages/pages/account-settings/[tab].vue
Normal file
99
resources/js/pages/pages/account-settings/[tab].vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<script setup>
|
||||
import AccountSettingsAccount from '@/views/pages/account-settings/AccountSettingsAccount.vue'
|
||||
import AccountSettingsBillingAndPlans from '@/views/pages/account-settings/AccountSettingsBillingAndPlans.vue'
|
||||
import AccountSettingsConnections from '@/views/pages/account-settings/AccountSettingsConnections.vue'
|
||||
import AccountSettingsNotification from '@/views/pages/account-settings/AccountSettingsNotification.vue'
|
||||
import AccountSettingsSecurity from '@/views/pages/account-settings/AccountSettingsSecurity.vue'
|
||||
|
||||
const route = useRoute('pages-account-settings-tab')
|
||||
|
||||
const activeTab = computed({
|
||||
get: () => route.params.tab,
|
||||
set: () => route.params.tab,
|
||||
})
|
||||
|
||||
// tabs
|
||||
const tabs = [
|
||||
{
|
||||
title: 'Account',
|
||||
icon: 'tabler-users',
|
||||
tab: 'account',
|
||||
},
|
||||
{
|
||||
title: 'Security',
|
||||
icon: 'tabler-lock',
|
||||
tab: 'security',
|
||||
},
|
||||
{
|
||||
title: 'Billing & Plans',
|
||||
icon: 'tabler-file-text',
|
||||
tab: 'billing-plans',
|
||||
},
|
||||
{
|
||||
title: 'Notifications',
|
||||
icon: 'tabler-bell',
|
||||
tab: 'notification',
|
||||
},
|
||||
{
|
||||
title: 'Connections',
|
||||
icon: 'tabler-link',
|
||||
tab: 'connection',
|
||||
},
|
||||
]
|
||||
|
||||
definePage({ meta: { navActiveLink: 'pages-account-settings-tab' } })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<VTabs
|
||||
v-model="activeTab"
|
||||
class="v-tabs-pill"
|
||||
>
|
||||
<VTab
|
||||
v-for="item in tabs"
|
||||
:key="item.icon"
|
||||
:value="item.tab"
|
||||
:to="{ name: 'pages-account-settings-tab', params: { tab: item.tab } }"
|
||||
>
|
||||
<VIcon
|
||||
size="20"
|
||||
start
|
||||
:icon="item.icon"
|
||||
/>
|
||||
{{ item.title }}
|
||||
</VTab>
|
||||
</VTabs>
|
||||
|
||||
<VWindow
|
||||
v-model="activeTab"
|
||||
class="mt-6 disable-tab-transition"
|
||||
:touch="false"
|
||||
>
|
||||
<!-- Account -->
|
||||
<VWindowItem value="account">
|
||||
<AccountSettingsAccount />
|
||||
</VWindowItem>
|
||||
|
||||
<!-- Security -->
|
||||
<VWindowItem value="security">
|
||||
<AccountSettingsSecurity />
|
||||
</VWindowItem>
|
||||
|
||||
<!-- Billing -->
|
||||
<VWindowItem value="billing-plans">
|
||||
<AccountSettingsBillingAndPlans />
|
||||
</VWindowItem>
|
||||
|
||||
<!-- Notification -->
|
||||
<VWindowItem value="notification">
|
||||
<AccountSettingsNotification />
|
||||
</VWindowItem>
|
||||
|
||||
<!-- Connections -->
|
||||
<VWindowItem value="connection">
|
||||
<AccountSettingsConnections />
|
||||
</VWindowItem>
|
||||
</VWindow>
|
||||
</div>
|
||||
</template>
|
||||
108
resources/js/pages/pages/authentication/forgot-password-v1.vue
Normal file
108
resources/js/pages/pages/authentication/forgot-password-v1.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<script setup>
|
||||
import authV1BottomShape from '@images/svg/auth-v1-bottom-shape.svg?raw'
|
||||
import authV1TopShape from '@images/svg/auth-v1-top-shape.svg?raw'
|
||||
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
|
||||
import { themeConfig } from '@themeConfig'
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
public: true,
|
||||
},
|
||||
})
|
||||
|
||||
const form = ref({ email: '' })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="auth-wrapper d-flex align-center justify-center pa-4">
|
||||
<div class="position-relative my-sm-16">
|
||||
<!-- 👉 Top shape -->
|
||||
<VNodeRenderer
|
||||
:nodes="h('div', { innerHTML: authV1TopShape })"
|
||||
class="text-primary auth-v1-top-shape d-none d-sm-block"
|
||||
/>
|
||||
|
||||
<!-- 👉 Bottom shape -->
|
||||
<VNodeRenderer
|
||||
:nodes="h('div', { innerHTML: authV1BottomShape })"
|
||||
class="text-primary auth-v1-bottom-shape d-none d-sm-block"
|
||||
/>
|
||||
|
||||
<!-- 👉 Auth card -->
|
||||
<VCard
|
||||
class="auth-card"
|
||||
max-width="460"
|
||||
:class="$vuetify.display.smAndUp ? 'pa-6' : 'pa-0'"
|
||||
>
|
||||
<VCardItem class="justify-center">
|
||||
<VCardTitle>
|
||||
<RouterLink to="/">
|
||||
<div class="app-logo">
|
||||
<VNodeRenderer :nodes="themeConfig.app.logo" />
|
||||
<h1 class="app-logo-title">
|
||||
{{ themeConfig.app.title }}
|
||||
</h1>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</VCardTitle>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<h4 class="text-h4 mb-1">
|
||||
Forgot Password? 🔒
|
||||
</h4>
|
||||
<p class="mb-0">
|
||||
Enter your email and we'll send you instructions to reset your password
|
||||
</p>
|
||||
</VCardText>
|
||||
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<VRow>
|
||||
<!-- email -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.email"
|
||||
autofocus
|
||||
label="Email"
|
||||
type="email"
|
||||
placeholder="johndoe@email.com"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- reset password -->
|
||||
<VCol cols="12">
|
||||
<VBtn
|
||||
block
|
||||
type="submit"
|
||||
>
|
||||
Send Reset Link
|
||||
</VBtn>
|
||||
</VCol>
|
||||
|
||||
<!-- back to login -->
|
||||
<VCol cols="12">
|
||||
<RouterLink
|
||||
class="d-flex align-center justify-center"
|
||||
:to="{ name: 'pages-authentication-login-v1' }"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-chevron-left"
|
||||
size="20"
|
||||
class="me-1 flip-in-rtl"
|
||||
/>
|
||||
<span>Back to login</span>
|
||||
</RouterLink>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/pages/page-auth";
|
||||
</style>
|
||||
129
resources/js/pages/pages/authentication/forgot-password-v2.vue
Normal file
129
resources/js/pages/pages/authentication/forgot-password-v2.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<script setup>
|
||||
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
|
||||
import authV2ForgotPasswordIllustrationDark from '@images/pages/auth-v2-forgot-password-illustration-dark.png'
|
||||
import authV2ForgotPasswordIllustrationLight from '@images/pages/auth-v2-forgot-password-illustration-light.png'
|
||||
import authV2MaskDark from '@images/pages/misc-mask-dark.png'
|
||||
import authV2MaskLight from '@images/pages/misc-mask-light.png'
|
||||
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
|
||||
import { themeConfig } from '@themeConfig'
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
public: true,
|
||||
},
|
||||
})
|
||||
|
||||
const email = ref('')
|
||||
const authThemeImg = useGenerateImageVariant(authV2ForgotPasswordIllustrationLight, authV2ForgotPasswordIllustrationDark)
|
||||
const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterLink to="/">
|
||||
<div class="auth-logo d-flex align-center gap-x-3">
|
||||
<VNodeRenderer :nodes="themeConfig.app.logo" />
|
||||
<h1 class="auth-title">
|
||||
{{ themeConfig.app.title }}
|
||||
</h1>
|
||||
</div>
|
||||
</RouterLink>
|
||||
|
||||
<VRow
|
||||
class="auth-wrapper bg-surface"
|
||||
no-gutters
|
||||
>
|
||||
<VCol
|
||||
md="8"
|
||||
class="d-none d-md-flex"
|
||||
>
|
||||
<div class="position-relative bg-background w-100 me-0">
|
||||
<div
|
||||
class="d-flex align-center justify-center w-100 h-100"
|
||||
style="padding-inline: 150px;"
|
||||
>
|
||||
<VImg
|
||||
max-width="468"
|
||||
:src="authThemeImg"
|
||||
class="auth-illustration mt-16 mb-2 flip-in-rtl"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<img
|
||||
class="auth-footer-mask flip-in-rtl"
|
||||
:src="authThemeMask"
|
||||
alt="auth-footer-mask"
|
||||
height="280"
|
||||
width="100"
|
||||
>
|
||||
</div>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
class="auth-card-v2 d-flex align-center justify-center"
|
||||
>
|
||||
<VCard
|
||||
flat
|
||||
:max-width="500"
|
||||
class="mt-12 mt-sm-0 pa-6"
|
||||
>
|
||||
<VCardText>
|
||||
<h4 class="text-h4 mb-1">
|
||||
Forgot Password? 🔒
|
||||
</h4>
|
||||
<p class="mb-0">
|
||||
Enter your email and we'll send you instructions to reset your password
|
||||
</p>
|
||||
</VCardText>
|
||||
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<VRow>
|
||||
<!-- email -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="email"
|
||||
autofocus
|
||||
label="Email"
|
||||
placeholder="johndoe@email.com"
|
||||
type="email"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- Reset link -->
|
||||
<VCol cols="12">
|
||||
<VBtn
|
||||
block
|
||||
type="submit"
|
||||
>
|
||||
Send Reset Link
|
||||
</VBtn>
|
||||
</VCol>
|
||||
|
||||
<!-- back to login -->
|
||||
<VCol cols="12">
|
||||
<RouterLink
|
||||
class="d-flex align-center justify-center"
|
||||
:to="{ name: 'pages-authentication-login-v2' }"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-chevron-left"
|
||||
size="20"
|
||||
class="me-1 flip-in-rtl"
|
||||
/>
|
||||
<span>Back to login</span>
|
||||
</RouterLink>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/pages/page-auth.scss";
|
||||
</style>
|
||||
159
resources/js/pages/pages/authentication/login-v1.vue
Normal file
159
resources/js/pages/pages/authentication/login-v1.vue
Normal file
@@ -0,0 +1,159 @@
|
||||
<script setup>
|
||||
import AuthProvider from '@/views/pages/authentication/AuthProvider.vue'
|
||||
import authV1BottomShape from '@images/svg/auth-v1-bottom-shape.svg?raw'
|
||||
import authV1TopShape from '@images/svg/auth-v1-top-shape.svg?raw'
|
||||
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
|
||||
import { themeConfig } from '@themeConfig'
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
public: true,
|
||||
},
|
||||
})
|
||||
|
||||
const form = ref({
|
||||
email: '',
|
||||
password: '',
|
||||
remember: false,
|
||||
})
|
||||
|
||||
const isPasswordVisible = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="auth-wrapper d-flex align-center justify-center pa-4">
|
||||
<div class="position-relative my-sm-16">
|
||||
<!-- 👉 Top shape -->
|
||||
<VNodeRenderer
|
||||
:nodes="h('div', { innerHTML: authV1TopShape })"
|
||||
class="text-primary auth-v1-top-shape d-none d-sm-block"
|
||||
/>
|
||||
|
||||
<!-- 👉 Bottom shape -->
|
||||
<VNodeRenderer
|
||||
:nodes="h('div', { innerHTML: authV1BottomShape })"
|
||||
class="text-primary auth-v1-bottom-shape d-none d-sm-block"
|
||||
/>
|
||||
|
||||
<!-- 👉 Auth Card -->
|
||||
<VCard
|
||||
class="auth-card"
|
||||
max-width="460"
|
||||
:class="$vuetify.display.smAndUp ? 'pa-6' : 'pa-0'"
|
||||
>
|
||||
<VCardItem class="justify-center">
|
||||
<VCardTitle>
|
||||
<RouterLink to="/">
|
||||
<div class="app-logo">
|
||||
<VNodeRenderer :nodes="themeConfig.app.logo" />
|
||||
<h1 class="app-logo-title">
|
||||
{{ themeConfig.app.title }}
|
||||
</h1>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</VCardTitle>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<h4 class="text-h4 mb-1">
|
||||
Welcome to <span class="text-capitalize">{{ themeConfig.app.title }}</span>! 👋🏻
|
||||
</h4>
|
||||
<p class="mb-0">
|
||||
Please sign-in to your account and start the adventure
|
||||
</p>
|
||||
</VCardText>
|
||||
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<VRow>
|
||||
<!-- email -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.email"
|
||||
autofocus
|
||||
label="Email or Username"
|
||||
type="email"
|
||||
placeholder="johndoe@email.com"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- password -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.password"
|
||||
label="Password"
|
||||
placeholder="············"
|
||||
:type="isPasswordVisible ? 'text' : 'password'"
|
||||
autocomplete="password"
|
||||
:append-inner-icon="isPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
|
||||
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
||||
/>
|
||||
|
||||
<!-- remember me checkbox -->
|
||||
<div class="d-flex align-center justify-space-between flex-wrap my-6">
|
||||
<VCheckbox
|
||||
v-model="form.remember"
|
||||
label="Remember me"
|
||||
/>
|
||||
|
||||
<RouterLink
|
||||
class="text-primary"
|
||||
:to="{ name: 'pages-authentication-forgot-password-v1' }"
|
||||
>
|
||||
Forgot Password?
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<!-- login button -->
|
||||
<VBtn
|
||||
block
|
||||
type="submit"
|
||||
>
|
||||
Login
|
||||
</VBtn>
|
||||
</VCol>
|
||||
|
||||
<!-- create account -->
|
||||
<VCol
|
||||
cols="12"
|
||||
class="text-body-1 text-center"
|
||||
>
|
||||
<span class="d-inline-block">
|
||||
New on our platform?
|
||||
</span>
|
||||
<RouterLink
|
||||
class="text-primary ms-1 d-inline-block text-body-1"
|
||||
:to="{ name: 'pages-authentication-register-v1' }"
|
||||
>
|
||||
Create an account
|
||||
</RouterLink>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
class="d-flex align-center"
|
||||
>
|
||||
<VDivider />
|
||||
<span class="mx-4 text-high-emphasis">or</span>
|
||||
<VDivider />
|
||||
</VCol>
|
||||
|
||||
<!-- auth providers -->
|
||||
<VCol
|
||||
cols="12"
|
||||
class="text-center"
|
||||
>
|
||||
<AuthProvider />
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/pages/page-auth";
|
||||
</style>
|
||||
178
resources/js/pages/pages/authentication/login-v2.vue
Normal file
178
resources/js/pages/pages/authentication/login-v2.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<script setup>
|
||||
import AuthProvider from '@/views/pages/authentication/AuthProvider.vue'
|
||||
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
|
||||
import authV2LoginIllustrationBorderedDark from '@images/pages/auth-v2-login-illustration-bordered-dark.png'
|
||||
import authV2LoginIllustrationBorderedLight from '@images/pages/auth-v2-login-illustration-bordered-light.png'
|
||||
import authV2LoginIllustrationDark from '@images/pages/auth-v2-login-illustration-dark.png'
|
||||
import authV2LoginIllustrationLight from '@images/pages/auth-v2-login-illustration-light.png'
|
||||
import authV2MaskDark from '@images/pages/misc-mask-dark.png'
|
||||
import authV2MaskLight from '@images/pages/misc-mask-light.png'
|
||||
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
|
||||
import { themeConfig } from '@themeConfig'
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
public: true,
|
||||
},
|
||||
})
|
||||
|
||||
const form = ref({
|
||||
email: '',
|
||||
password: '',
|
||||
remember: false,
|
||||
})
|
||||
|
||||
const isPasswordVisible = ref(false)
|
||||
const authThemeImg = useGenerateImageVariant(authV2LoginIllustrationLight, authV2LoginIllustrationDark, authV2LoginIllustrationBorderedLight, authV2LoginIllustrationBorderedDark, true)
|
||||
const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterLink to="/">
|
||||
<div class="auth-logo d-flex align-center gap-x-3">
|
||||
<VNodeRenderer :nodes="themeConfig.app.logo" />
|
||||
<h1 class="auth-title">
|
||||
{{ themeConfig.app.title }}
|
||||
</h1>
|
||||
</div>
|
||||
</RouterLink>
|
||||
|
||||
<VRow
|
||||
no-gutters
|
||||
class="auth-wrapper bg-surface"
|
||||
>
|
||||
<VCol
|
||||
md="8"
|
||||
class="d-none d-md-flex"
|
||||
>
|
||||
<div class="position-relative bg-background w-100 me-0">
|
||||
<div
|
||||
class="d-flex align-center justify-center w-100 h-100"
|
||||
style="padding-inline: 6.25rem;"
|
||||
>
|
||||
<VImg
|
||||
max-width="613"
|
||||
:src="authThemeImg"
|
||||
class="auth-illustration mt-16 mb-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<img
|
||||
class="auth-footer-mask flip-in-rtl"
|
||||
:src="authThemeMask"
|
||||
alt="auth-footer-mask"
|
||||
height="280"
|
||||
width="100"
|
||||
>
|
||||
</div>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
class="auth-card-v2 d-flex align-center justify-center"
|
||||
>
|
||||
<VCard
|
||||
flat
|
||||
:max-width="500"
|
||||
class="mt-12 mt-sm-0 pa-6"
|
||||
>
|
||||
<VCardText>
|
||||
<h4 class="text-h4 mb-1">
|
||||
Welcome to <span class="text-capitalize">{{ themeConfig.app.title }}</span>! 👋🏻
|
||||
</h4>
|
||||
<p class="mb-0">
|
||||
Please sign-in to your account and start the adventure
|
||||
</p>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<VRow>
|
||||
<!-- email -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.email"
|
||||
autofocus
|
||||
label="Email or Username"
|
||||
type="email"
|
||||
placeholder="johndoe@email.com"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- password -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.password"
|
||||
label="Password"
|
||||
placeholder="············"
|
||||
:type="isPasswordVisible ? 'text' : 'password'"
|
||||
autocomplete="password"
|
||||
:append-inner-icon="isPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
|
||||
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
||||
/>
|
||||
|
||||
<div class="d-flex align-center flex-wrap justify-space-between my-6">
|
||||
<VCheckbox
|
||||
v-model="form.remember"
|
||||
label="Remember me"
|
||||
/>
|
||||
<RouterLink
|
||||
class="text-primary"
|
||||
:to="{ name: 'pages-authentication-forgot-password-v2' }"
|
||||
>
|
||||
Forgot Password?
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<VBtn
|
||||
block
|
||||
type="submit"
|
||||
>
|
||||
Login
|
||||
</VBtn>
|
||||
</VCol>
|
||||
|
||||
<!-- create account -->
|
||||
<VCol
|
||||
cols="12"
|
||||
class="text-body-1 text-center"
|
||||
>
|
||||
<span class="d-inline-block">
|
||||
New on our platform?
|
||||
</span>
|
||||
<RouterLink
|
||||
class="text-primary ms-1 d-inline-block text-body-1"
|
||||
:to="{ name: 'pages-authentication-register-v2' }"
|
||||
>
|
||||
Create an account
|
||||
</RouterLink>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
class="d-flex align-center"
|
||||
>
|
||||
<VDivider />
|
||||
<span class="mx-4">or</span>
|
||||
<VDivider />
|
||||
</VCol>
|
||||
|
||||
<!-- auth providers -->
|
||||
<VCol
|
||||
cols="12"
|
||||
class="text-center"
|
||||
>
|
||||
<AuthProvider />
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/pages/page-auth";
|
||||
</style>
|
||||
454
resources/js/pages/pages/authentication/register-multi-steps.vue
Normal file
454
resources/js/pages/pages/authentication/register-multi-steps.vue
Normal file
@@ -0,0 +1,454 @@
|
||||
<script setup>
|
||||
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
|
||||
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
|
||||
import { themeConfig } from '@themeConfig'
|
||||
import registerMultiStepIllustrationDark from '@images/illustrations/register-multi-step-illustration-dark.png'
|
||||
import registerMultiStepIllustrationLight from '@images/illustrations/register-multi-step-illustration-light.png'
|
||||
import registerMultiStepBgDark from '@images/pages/register-multi-step-bg-dark.png'
|
||||
import registerMultiStepBgLight from '@images/pages/register-multi-step-bg-light.png'
|
||||
|
||||
const registerMultiStepBg = useGenerateImageVariant(registerMultiStepBgLight, registerMultiStepBgDark)
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
public: true,
|
||||
},
|
||||
})
|
||||
|
||||
const currentStep = ref(0)
|
||||
const isPasswordVisible = ref(false)
|
||||
const isConfirmPasswordVisible = ref(false)
|
||||
const registerMultiStepIllustration = useGenerateImageVariant(registerMultiStepIllustrationLight, registerMultiStepIllustrationDark)
|
||||
|
||||
const radioContent = [
|
||||
{
|
||||
title: 'Starter',
|
||||
desc: 'A simple start for everyone.',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
title: 'Standard',
|
||||
desc: 'For small to medium businesses.',
|
||||
value: '99',
|
||||
},
|
||||
{
|
||||
title: 'Enterprise',
|
||||
desc: 'Solution for big organizations.',
|
||||
value: '499',
|
||||
},
|
||||
]
|
||||
|
||||
const items = [
|
||||
{
|
||||
title: 'Account',
|
||||
subtitle: 'Account Details',
|
||||
icon: 'tabler-file-analytics',
|
||||
},
|
||||
{
|
||||
title: 'Personal',
|
||||
subtitle: 'Enter Information',
|
||||
icon: 'tabler-user',
|
||||
},
|
||||
{
|
||||
title: 'Billing',
|
||||
subtitle: 'Payment Details',
|
||||
icon: 'tabler-credit-card',
|
||||
},
|
||||
]
|
||||
|
||||
const form = ref({
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
link: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
mobile: '',
|
||||
pincode: '',
|
||||
address: '',
|
||||
landmark: '',
|
||||
city: '',
|
||||
state: null,
|
||||
selectedPlan: '0',
|
||||
cardNumber: '',
|
||||
cardName: '',
|
||||
expiryDate: '',
|
||||
cvv: '',
|
||||
})
|
||||
|
||||
const onSubmit = () => {
|
||||
|
||||
// eslint-disable-next-line no-alert
|
||||
alert('Submitted..!!')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterLink to="/">
|
||||
<div class="auth-logo d-flex align-center gap-x-3">
|
||||
<VNodeRenderer :nodes="themeConfig.app.logo" />
|
||||
<h1 class="auth-title">
|
||||
{{ themeConfig.app.title }}
|
||||
</h1>
|
||||
</div>
|
||||
</RouterLink>
|
||||
|
||||
<VRow
|
||||
no-gutters
|
||||
class="auth-wrapper"
|
||||
>
|
||||
<VCol
|
||||
md="4"
|
||||
class="d-none d-md-flex"
|
||||
>
|
||||
<!-- here your illustration -->
|
||||
<div class="d-flex justify-center align-center w-100 position-relative">
|
||||
<VImg
|
||||
:src="registerMultiStepIllustration"
|
||||
class="illustration-image flip-in-rtl"
|
||||
/>
|
||||
|
||||
<img
|
||||
class="bg-image position-absolute w-100 flip-in-rtl"
|
||||
:src="registerMultiStepBg"
|
||||
alt="register-multi-step-bg"
|
||||
height="340"
|
||||
>
|
||||
</div>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="8"
|
||||
class="auth-card-v2 d-flex align-center justify-center pa-10"
|
||||
style="background-color: rgb(var(--v-theme-surface));"
|
||||
>
|
||||
<VCard
|
||||
flat
|
||||
class="mt-12 mt-sm-10"
|
||||
>
|
||||
<AppStepper
|
||||
v-model:current-step="currentStep"
|
||||
:items="items"
|
||||
:direction="$vuetify.display.smAndUp ? 'horizontal' : 'vertical'"
|
||||
icon-size="22"
|
||||
class="stepper-icon-step-bg mb-12"
|
||||
/>
|
||||
|
||||
<VWindow
|
||||
v-model="currentStep"
|
||||
class="disable-tab-transition"
|
||||
style="max-inline-size: 681px;"
|
||||
>
|
||||
<VForm>
|
||||
<VWindowItem>
|
||||
<h4 class="text-h4">
|
||||
Account Information
|
||||
</h4>
|
||||
<p class="text-body-1 mb-6">
|
||||
Enter Your Account Details
|
||||
</p>
|
||||
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.username"
|
||||
label="Username"
|
||||
placeholder="Johndoe"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.email"
|
||||
label="Email"
|
||||
placeholder="johndoe@email.com"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.password"
|
||||
label="Password"
|
||||
placeholder="············"
|
||||
:type="isPasswordVisible ? 'text' : 'password'"
|
||||
autocomplete="password"
|
||||
:append-inner-icon="isPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
|
||||
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.confirmPassword"
|
||||
label="Confirm Password"
|
||||
autocomplete="confirm-password"
|
||||
placeholder="············"
|
||||
:type="isConfirmPasswordVisible ? 'text' : 'password'"
|
||||
:append-inner-icon="isConfirmPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
|
||||
@click:append-inner="isConfirmPasswordVisible = !isConfirmPasswordVisible"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.link"
|
||||
label="Profile Link"
|
||||
placeholder="https://profile.com/johndoe"
|
||||
type="url"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VWindowItem>
|
||||
|
||||
<VWindowItem>
|
||||
<h4 class="text-h4">
|
||||
Personal Information
|
||||
</h4>
|
||||
<p>
|
||||
Enter Your Personal Information
|
||||
</p>
|
||||
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.firstName"
|
||||
label="First Name"
|
||||
placeholder="John"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.lastName"
|
||||
label="Last Name"
|
||||
placeholder="Doe"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.mobile"
|
||||
type="number"
|
||||
label="Mobile"
|
||||
placeholder="+1 123 456 7890"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.pincode"
|
||||
type="number"
|
||||
label="Pincode"
|
||||
placeholder="123456"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.address"
|
||||
label="Address"
|
||||
placeholder="1234 Main St, New York, NY 10001, USA"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.landmark"
|
||||
label="Landmark"
|
||||
placeholder="Near Central Park"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.city"
|
||||
label="City"
|
||||
placeholder="New York"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppSelect
|
||||
v-model="form.state"
|
||||
label="State"
|
||||
placeholder="Select State"
|
||||
:items="['New York', 'California', 'Florida', 'Washington', 'Texas']"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VWindowItem>
|
||||
|
||||
<VWindowItem>
|
||||
<h4 class="text-h4">
|
||||
Select Plan
|
||||
</h4>
|
||||
<p class="text-body-1 mb-5">
|
||||
Select plan as per your requirement
|
||||
</p>
|
||||
|
||||
<CustomRadiosWithIcon
|
||||
v-model:selected-radio="form.selectedPlan"
|
||||
:radio-content="radioContent"
|
||||
:grid-column="{ sm: '4', cols: '12' }"
|
||||
>
|
||||
<template #default="{ item }">
|
||||
<div class="text-center">
|
||||
<h5 class="text-h5 mb-2">
|
||||
{{ item.title }}
|
||||
</h5>
|
||||
<p class="clamp-text mb-2">
|
||||
{{ item.desc }}
|
||||
</p>
|
||||
|
||||
<div class="d-flex align-center justify-center">
|
||||
<span class="text-primary mb-2">$</span>
|
||||
<span class="text-h4 text-primary">
|
||||
{{ item.value }}
|
||||
</span>
|
||||
<span class="mt-2">/month</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</CustomRadiosWithIcon>
|
||||
|
||||
<h4 class="text-h4 mt-12">
|
||||
Payment Information
|
||||
</h4>
|
||||
<p class="text-body-1 mb-6">
|
||||
Enter your card information
|
||||
</p>
|
||||
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.cardNumber"
|
||||
type="number"
|
||||
label="Card Number"
|
||||
placeholder="1234 1234 1234 1234"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.cardName"
|
||||
label="Name on Card"
|
||||
placeholder="John Doe"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="6"
|
||||
md="3"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.expiryDate"
|
||||
label="Expiry"
|
||||
placeholder="MM/YY"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="6"
|
||||
md="3"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.cvv"
|
||||
type="number"
|
||||
label="CVV"
|
||||
placeholder="123"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VWindowItem>
|
||||
</VForm>
|
||||
</VWindow>
|
||||
|
||||
<div class="d-flex flex-wrap justify-space-between gap-x-4 mt-6">
|
||||
<VBtn
|
||||
color="secondary"
|
||||
:disabled="currentStep === 0"
|
||||
variant="tonal"
|
||||
@click="currentStep--"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-arrow-left"
|
||||
start
|
||||
class="flip-in-rtl"
|
||||
/>
|
||||
Previous
|
||||
</VBtn>
|
||||
|
||||
<VBtn
|
||||
v-if="items.length - 1 === currentStep"
|
||||
color="success"
|
||||
@click="onSubmit"
|
||||
>
|
||||
submit
|
||||
</VBtn>
|
||||
|
||||
<VBtn
|
||||
v-else
|
||||
@click="currentStep++"
|
||||
>
|
||||
Next
|
||||
|
||||
<VIcon
|
||||
icon="tabler-arrow-right"
|
||||
end
|
||||
class="flip-in-rtl"
|
||||
/>
|
||||
</VBtn>
|
||||
</div>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/pages/page-auth.scss";
|
||||
|
||||
.illustration-image {
|
||||
block-size: 550px;
|
||||
inline-size: 248px;
|
||||
}
|
||||
|
||||
.bg-image {
|
||||
inset-block-end: 0;
|
||||
}
|
||||
</style>
|
||||
168
resources/js/pages/pages/authentication/register-v1.vue
Normal file
168
resources/js/pages/pages/authentication/register-v1.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<script setup>
|
||||
import AuthProvider from '@/views/pages/authentication/AuthProvider.vue'
|
||||
import authV1BottomShape from '@images/svg/auth-v1-bottom-shape.svg?raw'
|
||||
import authV1TopShape from '@images/svg/auth-v1-top-shape.svg?raw'
|
||||
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
|
||||
import { themeConfig } from '@themeConfig'
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
public: true,
|
||||
},
|
||||
})
|
||||
|
||||
const form = ref({
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
privacyPolicies: false,
|
||||
})
|
||||
|
||||
const isPasswordVisible = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="auth-wrapper d-flex align-center justify-center pa-4">
|
||||
<div class="position-relative my-sm-16">
|
||||
<!-- 👉 Top shape -->
|
||||
<VNodeRenderer
|
||||
:nodes="h('div', { innerHTML: authV1TopShape })"
|
||||
class="text-primary auth-v1-top-shape d-none d-sm-block"
|
||||
/>
|
||||
|
||||
<!-- 👉 Bottom shape -->
|
||||
<VNodeRenderer
|
||||
:nodes="h('div', { innerHTML: authV1BottomShape })"
|
||||
class="text-primary auth-v1-bottom-shape d-none d-sm-block"
|
||||
/>
|
||||
|
||||
<!-- 👉 Auth card -->
|
||||
<VCard
|
||||
class="auth-card"
|
||||
max-width="460"
|
||||
:class="$vuetify.display.smAndUp ? 'pa-6' : 'pa-0'"
|
||||
>
|
||||
<VCardItem class="justify-center">
|
||||
<VCardTitle>
|
||||
<RouterLink to="/">
|
||||
<div class="app-logo">
|
||||
<VNodeRenderer :nodes="themeConfig.app.logo" />
|
||||
<h1 class="app-logo-title">
|
||||
{{ themeConfig.app.title }}
|
||||
</h1>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</VCardTitle>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<h4 class="text-h4 mb-1">
|
||||
Adventure starts here 🚀
|
||||
</h4>
|
||||
<p class="mb-0">
|
||||
Make your app management easy and fun!
|
||||
</p>
|
||||
</VCardText>
|
||||
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<VRow>
|
||||
<!-- Username -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.username"
|
||||
autofocus
|
||||
label="Username"
|
||||
placeholder="Johndoe"
|
||||
/>
|
||||
</VCol>
|
||||
<!-- email -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.email"
|
||||
label="Email"
|
||||
type="email"
|
||||
placeholder="johndoe@email.com"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- password -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.password"
|
||||
label="Password"
|
||||
placeholder="············"
|
||||
:type="isPasswordVisible ? 'text' : 'password'"
|
||||
autocomplete="password"
|
||||
:append-inner-icon="isPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
|
||||
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
||||
/>
|
||||
|
||||
<div class="d-flex align-center my-6">
|
||||
<VCheckbox
|
||||
id="privacy-policy"
|
||||
v-model="form.privacyPolicies"
|
||||
inline
|
||||
/>
|
||||
<VLabel
|
||||
for="privacy-policy"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<span class="me-1 text-high-emphasis">I agree to</span>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="text-primary"
|
||||
>privacy policy & terms</a>
|
||||
</VLabel>
|
||||
</div>
|
||||
|
||||
<VBtn
|
||||
block
|
||||
type="submit"
|
||||
>
|
||||
Sign up
|
||||
</VBtn>
|
||||
</VCol>
|
||||
|
||||
<!-- login instead -->
|
||||
<VCol
|
||||
cols="12"
|
||||
class="text-center text-base"
|
||||
>
|
||||
<span>Already have an account?</span>
|
||||
<RouterLink
|
||||
class="text-primary ms-1"
|
||||
:to="{ name: 'pages-authentication-login-v1' }"
|
||||
>
|
||||
Sign in instead
|
||||
</RouterLink>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
class="d-flex align-center"
|
||||
>
|
||||
<VDivider />
|
||||
<span class="mx-4">or</span>
|
||||
<VDivider />
|
||||
</VCol>
|
||||
|
||||
<!-- auth providers -->
|
||||
<VCol
|
||||
cols="12"
|
||||
class="text-center"
|
||||
>
|
||||
<AuthProvider />
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/pages/page-auth";
|
||||
</style>
|
||||
192
resources/js/pages/pages/authentication/register-v2.vue
Normal file
192
resources/js/pages/pages/authentication/register-v2.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<script setup>
|
||||
import { VNodeRenderer } from '@/@layouts/components/VNodeRenderer'
|
||||
import AuthProvider from '@/views/pages/authentication/AuthProvider.vue'
|
||||
import { useGenerateImageVariant } from '@core/composable/useGenerateImageVariant'
|
||||
import { themeConfig } from '@themeConfig'
|
||||
import authV2RegisterIllustrationBorderedDark from '@images/pages/auth-v2-register-illustration-bordered-dark.png'
|
||||
import authV2RegisterIllustrationBorderedLight from '@images/pages/auth-v2-register-illustration-bordered-light.png'
|
||||
import authV2RegisterIllustrationDark from '@images/pages/auth-v2-register-illustration-dark.png'
|
||||
import authV2RegisterIllustrationLight from '@images/pages/auth-v2-register-illustration-light.png'
|
||||
import authV2MaskDark from '@images/pages/misc-mask-dark.png'
|
||||
import authV2MaskLight from '@images/pages/misc-mask-light.png'
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
public: true,
|
||||
},
|
||||
})
|
||||
|
||||
const form = ref({
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
privacyPolicies: false,
|
||||
})
|
||||
|
||||
const imageVariant = useGenerateImageVariant(authV2RegisterIllustrationLight, authV2RegisterIllustrationDark, authV2RegisterIllustrationBorderedLight, authV2RegisterIllustrationBorderedDark, true)
|
||||
const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
|
||||
const isPasswordVisible = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterLink to="/">
|
||||
<div class="auth-logo d-flex align-center gap-x-3">
|
||||
<VNodeRenderer :nodes="themeConfig.app.logo" />
|
||||
<h1 class="auth-title">
|
||||
{{ themeConfig.app.title }}
|
||||
</h1>
|
||||
</div>
|
||||
</RouterLink>
|
||||
|
||||
<VRow
|
||||
no-gutters
|
||||
class="auth-wrapper bg-surface"
|
||||
>
|
||||
<VCol
|
||||
md="8"
|
||||
class="d-none d-md-flex"
|
||||
>
|
||||
<div class="position-relative bg-background w-100 me-0">
|
||||
<div
|
||||
class="d-flex align-center justify-center w-100 h-100"
|
||||
style="padding-inline: 100px;"
|
||||
>
|
||||
<VImg
|
||||
max-width="500"
|
||||
:src="imageVariant"
|
||||
class="auth-illustration mt-16 mb-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<img
|
||||
class="auth-footer-mask flip-in-rtl"
|
||||
:src="authThemeMask"
|
||||
alt="auth-footer-mask"
|
||||
height="280"
|
||||
width="100"
|
||||
>
|
||||
</div>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
class="auth-card-v2 d-flex align-center justify-center"
|
||||
>
|
||||
<VCard
|
||||
flat
|
||||
:max-width="500"
|
||||
class="mt-12 pa-6"
|
||||
>
|
||||
<VCardText>
|
||||
<h4 class="text-h4 mb-1">
|
||||
Adventure starts here 🚀
|
||||
</h4>
|
||||
<p class="mb-0">
|
||||
Make your app management easy and fun!
|
||||
</p>
|
||||
</VCardText>
|
||||
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<VRow>
|
||||
<!-- Username -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.username"
|
||||
autofocus
|
||||
label="Username"
|
||||
placeholder="Johndoe"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- email -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.email"
|
||||
label="Email"
|
||||
type="email"
|
||||
placeholder="johndoe@email.com"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- password -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.password"
|
||||
label="Password"
|
||||
placeholder="············"
|
||||
:type="isPasswordVisible ? 'text' : 'password'"
|
||||
autocomplete="password"
|
||||
:append-inner-icon="isPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
|
||||
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
||||
/>
|
||||
|
||||
<div class="d-flex align-center my-6">
|
||||
<VCheckbox
|
||||
id="privacy-policy"
|
||||
v-model="form.privacyPolicies"
|
||||
inline
|
||||
/>
|
||||
<VLabel
|
||||
for="privacy-policy"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<span class="me-1 text-high-emphasis">I agree to</span>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="text-primary"
|
||||
>privacy policy & terms</a>
|
||||
</VLabel>
|
||||
</div>
|
||||
|
||||
<VBtn
|
||||
block
|
||||
type="submit"
|
||||
>
|
||||
Sign up
|
||||
</VBtn>
|
||||
</VCol>
|
||||
|
||||
<!-- create account -->
|
||||
<VCol
|
||||
cols="12"
|
||||
class="text-center text-base"
|
||||
>
|
||||
<span class="d-inline-block">Already have an account?</span>
|
||||
<RouterLink
|
||||
class="text-primary ms-1 d-inline-block"
|
||||
:to="{ name: 'pages-authentication-login-v2' }"
|
||||
>
|
||||
Sign in instead
|
||||
</RouterLink>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
cols="12"
|
||||
class="d-flex align-center"
|
||||
>
|
||||
<VDivider />
|
||||
<span class="mx-4">or</span>
|
||||
<VDivider />
|
||||
</VCol>
|
||||
|
||||
<!-- auth providers -->
|
||||
<VCol
|
||||
cols="12"
|
||||
class="text-center"
|
||||
>
|
||||
<AuthProvider />
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/pages/page-auth";
|
||||
</style>
|
||||
130
resources/js/pages/pages/authentication/reset-password-v1.vue
Normal file
130
resources/js/pages/pages/authentication/reset-password-v1.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<script setup>
|
||||
import authV1BottomShape from '@images/svg/auth-v1-bottom-shape.svg?raw'
|
||||
import authV1TopShape from '@images/svg/auth-v1-top-shape.svg?raw'
|
||||
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
|
||||
import { themeConfig } from '@themeConfig'
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
public: true,
|
||||
},
|
||||
})
|
||||
|
||||
const form = ref({
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
})
|
||||
|
||||
const isPasswordVisible = ref(false)
|
||||
const isConfirmPasswordVisible = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="auth-wrapper d-flex align-center justify-center pa-4">
|
||||
<div class="position-relative my-sm-16">
|
||||
<!-- 👉 Top shape -->
|
||||
<VNodeRenderer
|
||||
:nodes="h('div', { innerHTML: authV1TopShape })"
|
||||
class="text-primary auth-v1-top-shape d-none d-sm-block"
|
||||
/>
|
||||
|
||||
<!-- 👉 Bottom shape -->
|
||||
<VNodeRenderer
|
||||
:nodes="h('div', { innerHTML: authV1BottomShape })"
|
||||
class="text-primary auth-v1-bottom-shape d-none d-sm-block"
|
||||
/>
|
||||
|
||||
<!-- 👉 Auth Card -->
|
||||
<VCard
|
||||
class="auth-card"
|
||||
max-width="460"
|
||||
:class="$vuetify.display.smAndUp ? 'pa-6' : 'pa-2'"
|
||||
>
|
||||
<VCardItem class="justify-center">
|
||||
<VCardTitle>
|
||||
<RouterLink to="/">
|
||||
<div class="app-logo">
|
||||
<VNodeRenderer :nodes="themeConfig.app.logo" />
|
||||
<h1 class="app-logo-title">
|
||||
{{ themeConfig.app.title }}
|
||||
</h1>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</VCardTitle>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<h4 class="text-h4 mb-1">
|
||||
Reset Password 🔒
|
||||
</h4>
|
||||
<p class="mb-0">
|
||||
Your new password must be different from previously used passwords
|
||||
</p>
|
||||
</VCardText>
|
||||
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<VRow>
|
||||
<!-- password -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.newPassword"
|
||||
autofocus
|
||||
label="New Password"
|
||||
placeholder="············"
|
||||
:type="isPasswordVisible ? 'text' : 'password'"
|
||||
autocomplete="password"
|
||||
:append-inner-icon="isPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
|
||||
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- Confirm Password -->
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.confirmPassword"
|
||||
label="Confirm Password"
|
||||
autocomplete="confirm-password"
|
||||
placeholder="············"
|
||||
:type="isConfirmPasswordVisible ? 'text' : 'password'"
|
||||
:append-inner-icon="isConfirmPasswordVisible ? 'tabler-eye-off' : 'tabler-eye'"
|
||||
@click:append-inner="isConfirmPasswordVisible = !isConfirmPasswordVisible"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- reset password -->
|
||||
<VCol cols="12">
|
||||
<VBtn
|
||||
block
|
||||
type="submit"
|
||||
>
|
||||
Set New Password
|
||||
</VBtn>
|
||||
</VCol>
|
||||
|
||||
<!-- back to login -->
|
||||
<VCol cols="12">
|
||||
<RouterLink
|
||||
class="d-flex align-center justify-center"
|
||||
:to="{ name: 'pages-authentication-login-v1' }"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-chevron-left"
|
||||
size="20"
|
||||
class="me-1 flip-in-rtl"
|
||||
/>
|
||||
<span>Back to login</span>
|
||||
</RouterLink>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@core-scss/template/pages/page-auth";
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user