initial commit

This commit is contained in:
2026-06-23 13:28:38 +07:00
commit 966ed115b2
590 changed files with 111412 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
<script>
import { useSkins } from '@core/composable/useSkins'
export default defineComponent({
setup() {
const routerView = resolveComponent('router-view')
const { injectSkinClasses } = useSkins()
// ️ This will inject classes in body tag for accurate styling
injectSkinClasses()
return () => h('div', { class: 'layout-wrapper layout-blank' }, h(routerView))
},
})
</script>
<style>
.layout-wrapper.layout-blank {
flex-direction: column;
}
</style>
@@ -0,0 +1,54 @@
<script setup>
import navItems from '@/navigation/horizontal'
import { useThemeConfig } from '@core/composable/useThemeConfig'
import { themeConfig } from '@themeConfig'
// Components
import Footer from '@/layouts/components/Footer.vue'
import NavbarThemeSwitcher from '@/layouts/components/NavbarThemeSwitcher.vue'
import UserProfile from '@/layouts/components/UserProfile.vue'
import { HorizontalNavLayout } from '@layouts'
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
const { appRouteTransition } = useThemeConfig()
</script>
<template>
<HorizontalNavLayout :nav-items="navItems">
<!-- 👉 navbar -->
<template #navbar>
<a
href="/"
class="app-logo d-flex align-center gap-x-3"
>
<VNodeRenderer :nodes="themeConfig.app.logo" />
<h1 class="app-title font-weight-bold leading-normal text-xl text-capitalize">
{{ themeConfig.app.title }}
</h1>
</a>
<VSpacer />
<NavbarThemeSwitcher class="me-2" />
<UserProfile />
</template>
<!-- 👉 Pages -->
<RouterView v-slot="{ Component }">
<Transition
:name="appRouteTransition"
mode="out-in"
>
<Component :is="Component" />
</Transition>
</RouterView>
<!-- 👉 Footer -->
<template #footer>
<Footer />
</template>
<!-- 👉 Customizer -->
<!-- <TheCustomizer /> -->
</HorizontalNavLayout>
</template>
@@ -0,0 +1,119 @@
<script setup>
import { useAuthStore } from '@/stores/auth'
import { useThemeConfig } from '@core/composable/useThemeConfig'
import { computed } from 'vue'
// Components
import Footer from '@/layouts/components/Footer.vue'
import UserProfile from '@/layouts/components/UserProfile.vue'
// @layouts plugin
import { VerticalNavLayout } from '@layouts'
const { appRouteTransition, isLessThanOverlayNavBreakpoint } = useThemeConfig()
const { width: windowWidth } = useWindowSize()
const authStore = useAuthStore()
// DYNAMIC SIDEBAR NAV ITEMS
const navItems = computed(()=>{
const items = [
{ heading: 'Dashboard' },
{
title: 'Home',
to: { name: 'dashboard' },
icon: { icon: 'tabler-smart-home' },
},
]
if (authStore.role == 'admin') {
items.push(...[
{ heading: 'User Management' },
{
title: 'Patient',
to: { name: 'admin-patient' },
icon: { icon: 'tabler-user-filled' },
},
{
title: 'Doctor',
to: { name: 'admin-doctor' },
icon: { icon: 'tabler-stethoscope' },
},
])
} else if (authStore.role == 'doctor') {
items.push(...[
{ heading: 'Patients' },
{
title: 'My Patients',
to: { name: 'doctor-patient' },
icon: { icon: 'tabler-user-filled' },
},
])
} else if (authStore.role == 'patient') {
items.push(...[
{ heading: 'Diagnoses' },
{
title: 'My Diagnoses',
to: { name: 'patient-diagnoses' },
icon: { icon: 'tabler-clipboard-check' },
},
])
}
items.push(...[
{ heading: 'Account' },
{
title: 'Profile',
to: { name: 'profile' },
icon: { icon: 'tabler-user' },
},
])
return items
})
</script>
<template>
<VerticalNavLayout :nav-items="navItems">
<!-- 👉 navbar -->
<template #navbar="{ toggleVerticalOverlayNavActive }">
<div class="d-flex h-100 align-center">
<IconBtn
v-if="isLessThanOverlayNavBreakpoint(windowWidth)"
id="vertical-nav-toggle-btn"
class="ms-n3"
@click="toggleVerticalOverlayNavActive(true)"
>
<VIcon
size="26"
icon="tabler-menu-2"
/>
</IconBtn>
<!-- <NavbarThemeSwitcher /> -->
<VSpacer />
<UserProfile />
</div>
</template>
<!-- 👉 Pages -->
<RouterView v-slot="{ Component }">
<Transition
:name="appRouteTransition"
mode="out-in"
>
<Component :is="Component" />
</Transition>
</RouterView>
<!-- 👉 Footer -->
<template #footer>
<Footer />
</template>
<!-- 👉 Customizer -->
<!-- <TheCustomizer /> -->
</VerticalNavLayout>
</template>
@@ -0,0 +1,3 @@
<template>
<div />
</template>
@@ -0,0 +1,49 @@
<script setup>
import { useThemeConfig } from '@core/composable/useThemeConfig'
import logo from '@images/logo.png'
// @layouts plugin
import { TopNavLayout } from '@layouts'
const { appRouteTransition } = useThemeConfig()
</script>
<template>
<TopNavLayout>
<!-- 👉 navbar -->
<template #navbar>
<div class="d-flex h-100 align-center">
<img
:src="logo"
alt="MyoScope"
height="32"
>
<h1 class="text-h2 ms-2">
MyoScope
</h1>
<VSpacer />
<VBtn
color="primary"
outlined
to="/login"
prepend-icon="mdi-login"
>
Login
</VBtn>
</div>
</template>
<!-- 👉 Pages -->
<RouterView v-slot="{ Component }">
<Transition
:name="appRouteTransition"
mode="out-in"
>
<Component :is="Component" />
</Transition>
</RouterView>
</TopNavLayout>
</template>
@@ -0,0 +1,32 @@
<script setup>
import NavBarI18n from '@core/components/I18n.vue'
import { useThemeConfig } from '@core/composable/useThemeConfig'
const { isAppRtl } = useThemeConfig()
const i18nCompLanguages = [
{
label: 'English',
i18nLang: 'en',
},
{
label: 'French',
i18nLang: 'fr',
},
{
label: 'Arabic',
i18nLang: 'ar',
},
]
const handleLangChange = lang => {
isAppRtl.value = lang === 'ar'
}
</script>
<template>
<NavBarI18n
:languages="i18nCompLanguages"
@change="handleLangChange"
/>
</template>
@@ -0,0 +1,90 @@
<script setup>
import avatar3 from '@images/avatars/avatar-3.png'
import avatar4 from '@images/avatars/avatar-4.png'
import avatar5 from '@images/avatars/avatar-5.png'
import paypal from '@images/svg/paypal.svg'
const notifications = ref([
{
id: 1,
img: avatar4,
title: 'Congratulation Flora! 🎉',
subtitle: 'Won the monthly best seller badge',
time: 'Today',
isSeen: true,
},
{
id: 2,
text: 'Tom Holland',
title: 'New user registered.',
subtitle: '5 hours ago',
time: 'Yesterday',
isSeen: false,
},
{
id: 3,
img: avatar5,
title: 'New message received 👋🏻',
subtitle: 'You have 10 unread messages',
time: '11 Aug',
isSeen: true,
},
{
id: 4,
img: paypal,
title: 'Paypal',
subtitle: 'Received Payment',
time: '25 May',
isSeen: false,
color: 'error',
},
{
id: 5,
img: avatar3,
title: 'Received Order 📦',
subtitle: 'New order received from john',
time: '19 Mar',
isSeen: true,
},
])
const removeNotification = notificationId => {
notifications.value.forEach((item, index) => {
if (notificationId === item.id)
notifications.value.splice(index, 1)
})
}
const markRead = notificationId => {
notifications.value.forEach(item => {
notificationId.forEach(id => {
if (id === item.id)
item.isSeen = true
})
})
}
const markUnRead = notificationId => {
notifications.value.forEach(item => {
notificationId.forEach(id => {
if (id === item.id)
item.isSeen = false
})
})
}
const handleNotificationClick = notification => {
if (!notification.isSeen)
markRead([notification.id])
}
</script>
<template>
<Notifications
:notifications="notifications"
@remove="removeNotification"
@read="markRead"
@unread="markUnRead"
@click:notification="handleNotificationClick"
/>
</template>
@@ -0,0 +1,231 @@
<script setup>
import Shepherd from 'shepherd.js'
import axios from '@axios'
import { useThemeConfig } from '@core/composable/useThemeConfig'
const { appContentLayoutNav } = useThemeConfig()
defineOptions({ inheritAttrs: false })
// 👉 Is App Search Bar Visible
const isAppSearchBarVisible = ref(false)
// 👉 Default suggestions
const suggestionGroups = [
{
title: 'Popular Searches',
content: [
{
icon: 'tabler-chart-donut',
title: 'Analytics',
url: { name: 'dashboards-analytics' },
},
{
icon: 'tabler-chart-bubble',
title: 'CRM',
url: { name: 'dashboards-crm' },
},
{
icon: 'tabler-file',
title: 'Invoice List',
url: { name: 'apps-invoice-list' },
},
{
icon: 'tabler-users',
title: 'User List',
url: { name: 'apps-user-list' },
},
],
},
{
title: 'Apps & Pages',
content: [
{
icon: 'tabler-calendar',
title: 'Calendar',
url: { name: 'apps-calendar' },
},
{
icon: 'tabler-file-plus',
title: 'Invoice Add',
url: { name: 'apps-invoice-add' },
},
{
icon: 'tabler-currency-dollar',
title: 'Pricing',
url: { name: 'pages-pricing' },
},
{
icon: 'tabler-user',
title: 'Account Settings',
url: {
name: 'pages-account-settings-tab',
params: { tab: 'account' },
},
},
],
},
{
title: 'User Interface',
content: [
{
icon: 'tabler-letter-a',
title: 'Typography',
url: { name: 'pages-typography' },
},
{
icon: 'tabler-square',
title: 'Tabs',
url: { name: 'components-tabs' },
},
{
icon: 'tabler-hand-click',
title: 'Buttons',
url: { name: 'components-button' },
},
{
icon: 'tabler-keyboard',
title: 'Statistics',
url: { name: 'pages-cards-card-statistics' },
},
],
},
{
title: 'Popular Searches',
content: [
{
icon: 'tabler-list',
title: 'Select',
url: { name: 'forms-select' },
},
{
icon: 'tabler-space',
title: 'Combobox',
url: { name: 'forms-combobox' },
},
{
icon: 'tabler-calendar',
title: 'Date & Time Picker',
url: { name: 'forms-date-time-picker' },
},
{
icon: 'tabler-hexagon',
title: 'Rating',
url: { name: 'forms-rating' },
},
],
},
]
// 👉 No Data suggestion
const noDataSuggestions = [
{
title: 'Analytics Dashboard',
icon: 'tabler-shopping-cart',
url: { name: 'dashboards-analytics' },
},
{
title: 'Account Settings',
icon: 'tabler-user',
url: {
name: 'pages-account-settings-tab',
params: { tab: 'account' },
},
},
{
title: 'Pricing Page',
icon: 'tabler-cash',
url: { name: 'pages-pricing' },
},
]
const searchQuery = ref('')
const searchResult = ref([])
const router = useRouter()
// 👉 fetch search result API
watchEffect(() => {
axios.get('/app-bar/search', { params: { q: searchQuery.value } }).then(response => {
searchResult.value = response.data
})
})
const redirectToSuggestedOrSearchedPage = selected => {
router.push(selected.url)
isAppSearchBarVisible.value = false
searchQuery.value = ''
}
const LazyAppBarSearch = defineAsyncComponent(() => import('@core/components/AppBarSearch.vue'))
</script>
<template>
<div
class="d-flex align-center cursor-pointer"
v-bind="$attrs"
style="user-select: none;"
@click="isAppSearchBarVisible = !isAppSearchBarVisible"
>
<!-- 👉 Search Trigger button -->
<!-- close active tour while opening search bar using icon -->
<IconBtn
class="me-1"
@click="Shepherd.activeTour?.cancel()"
>
<VIcon
size="26"
icon="tabler-search"
/>
</IconBtn>
<span
v-if="appContentLayoutNav === 'vertical'"
class="d-none d-md-flex align-center text-disabled"
@click="Shepherd.activeTour?.cancel()"
>
<span class="me-3">Search</span>
<span class="meta-key">&#8984;K</span>
</span>
</div>
<!-- 👉 App Bar Search -->
<LazyAppBarSearch
v-model:isDialogVisible="isAppSearchBarVisible"
v-model:search-query="searchQuery"
:search-results="searchResult"
:suggestions="suggestionGroups"
:no-data-suggestion="noDataSuggestions"
@item-selected="redirectToSuggestedOrSearchedPage"
>
<!--
<template #suggestions>
use this slot if you want to override default suggestions
</template>
-->
<!--
<template #noData>
use this slot to change the view of no data section
</template>
-->
<!--
<template #searchResult="{ item }">
use this slot to change the search item
</template>
-->
</LazyAppBarSearch>
</template>
<style lang="scss" scoped>
@use "@styles/variables/_vuetify.scss";
.meta-key {
border: thin solid rgba(var(--v-border-color), var(--v-border-opacity));
border-radius: 6px;
block-size: 1.5625rem;
line-height: 1.3125rem;
padding-block: 0.125rem;
padding-inline: 0.25rem;
}
</style>
@@ -0,0 +1,47 @@
<script setup>
const shortcuts = [
{
icon: 'tabler-calendar',
title: 'Calendar',
subtitle: 'Appointments',
to: { name: 'apps-calendar' },
},
{
icon: 'tabler-file',
title: 'Invoice App',
subtitle: 'Manage Accounts',
to: { name: 'apps-invoice-list' },
},
{
icon: 'tabler-user',
title: 'Users',
subtitle: 'Manage Users',
to: { name: 'apps-user-list' },
},
{
icon: 'tabler-layout',
title: 'Dashboard',
subtitle: 'Dashboard Analytics',
to: { name: 'dashboards-analytics' },
},
{
icon: 'tabler-settings',
title: 'Settings',
subtitle: 'Account Settings',
to: {
name: 'pages-account-settings-tab',
params: { tab: 'account' },
},
},
{
icon: 'tabler-help',
title: 'Help Center',
subtitle: 'FAQs & Articles',
to: { name: 'pages-help-center' },
},
]
</script>
<template>
<Shortcuts :shortcuts="shortcuts" />
</template>
@@ -0,0 +1,20 @@
<script setup>
const themes = [
{
name: 'system',
icon: 'tabler-device-laptop',
},
{
name: 'light',
icon: 'tabler-sun-high',
},
{
name: 'dark',
icon: 'tabler-moon',
},
]
</script>
<template>
<ThemeSwitcher :themes="themes" />
</template>
@@ -0,0 +1,100 @@
<script setup>
import axiosIns from '@/plugins/axios'
import { showToast } from '@/plugins/toast'
import { useAuthStore } from '@/stores/auth'
import { useRouter } from 'vue-router'
const authStore = useAuthStore()
const router = useRouter()
const icon = computed(() => {
switch (authStore?.user?.role) {
case 'admin':
return 'tabler-shield-check'
case 'doctor':
return 'tabler-stethoscope'
case 'patient':
return 'tabler-user'
}
return 'tabler-user'
})
async function logout() {
axiosIns.get('/api/v1/account/logout')
.then(response => {
authStore.deleteData()
showToast('Logout successfully', 'success')
router.push('/login')
})
}
</script>
<template>
<VCard
class="cursor-pointer"
color="primary"
variant="outlined"
:loading="!authStore?.user?.email"
>
<VCardItem class="py-0 px-2">
<template #prepend>
<VAvatar
color="primary"
variant="tonal"
>
<VIcon :icon="icon" size="32" color="primary" />
</VAvatar>
</template>
<VCardTitle class="text-body-1 text-primary pa-0">
{{ authStore?.user?.email }}
</VCardTitle>
<VCardSubtitle class="text-body-2 text-primary pa-0">
{{ authStore?.role }}
</VCardSubtitle>
</VCardItem>
<!-- SECTION Menu -->
<VMenu
activator="parent"
width="230"
location="bottom end"
offset="14px"
>
<VList>
<!-- 👉 Profile -->
<VListItem
link
to="/profile"
>
<template #prepend>
<VIcon
class="me-2"
icon="tabler-user"
size="22"
/>
</template>
<VListItemTitle>Profile</VListItemTitle>
</VListItem>
<!-- Divider -->
<VDivider class="my-2" />
<!-- 👉 Logout -->
<VListItem @click.prevent="logout">
<template #prepend>
<VIcon
class="me-2"
icon="tabler-logout"
size="22"
/>
</template>
<VListItemTitle>Logout</VListItemTitle>
</VListItem>
</VList>
</VMenu>
<!-- !SECTION -->
</VCard>
</template>
+33
View File
@@ -0,0 +1,33 @@
<script setup>
import { useSkins } from '@core/composable/useSkins'
import { useThemeConfig } from '@core/composable/useThemeConfig'
// @layouts plugin
import { AppContentLayoutNav } from '@layouts/enums'
const DefaultLayoutWithHorizontalNav = defineAsyncComponent(() => import('./components/DefaultLayoutWithHorizontalNav.vue'))
const DefaultLayoutWithVerticalNav = defineAsyncComponent(() => import('./components/DefaultLayoutWithVerticalNav.vue'))
const { width: windowWidth } = useWindowSize()
const { appContentLayoutNav, switchToVerticalNavOnLtOverlayNavBreakpoint } = useThemeConfig()
// Remove below composable usage if you are not using horizontal nav layout in your app
switchToVerticalNavOnLtOverlayNavBreakpoint(windowWidth)
const { layoutAttrs, injectSkinClasses } = useSkins()
injectSkinClasses()
</script>
<template>
<template v-if="appContentLayoutNav === AppContentLayoutNav.Vertical">
<DefaultLayoutWithVerticalNav v-bind="layoutAttrs" />
</template>
<template v-else>
<DefaultLayoutWithHorizontalNav v-bind="layoutAttrs" />
</template>
</template>
<style lang="scss">
// As we are using `layouts` plugin we need its styles to be imported
@use "@layouts/styles/default-layout";
</style>
+18
View File
@@ -0,0 +1,18 @@
<script setup>
import { useSkins } from '@core/composable/useSkins'
const LandingNav = defineAsyncComponent(() => import('./components/LandingNav.vue'))
const { layoutAttrs, injectSkinClasses } = useSkins()
injectSkinClasses()
</script>
<template>
<LandingNav v-bind="layoutAttrs" />
</template>
<style lang="scss">
// As we are using `layouts` plugin we need its styles to be imported
@use "@layouts/styles/default-layout";
</style>