mirror of
https://github.com/crater-invoice/crater.git
synced 2025-12-17 10:52:55 -05:00
v5.0.0 update
This commit is contained in:
170
resources/scripts/layouts/partials/TheSiteHeader.vue
Normal file
170
resources/scripts/layouts/partials/TheSiteHeader.vue
Normal file
@@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<header
|
||||
class="fixed top-0 left-0 z-20 flex items-center justify-between w-full px-4 py-3 md:h-16 md:px-8 bg-gradient-to-r from-primary-500 to-primary-400"
|
||||
>
|
||||
<router-link
|
||||
to="/admin/dashboard"
|
||||
class="float-none text-lg not-italic font-black tracking-wider text-white brand-main md:float-left font-base hidden md:block"
|
||||
>
|
||||
<img
|
||||
id="logo-white"
|
||||
src="/img/logo-white.png"
|
||||
alt="Crater Logo"
|
||||
class="h-6"
|
||||
/>
|
||||
</router-link>
|
||||
|
||||
<!-- toggle button-->
|
||||
<div
|
||||
:class="{ 'is-active': globalStore.isSidebarOpen }"
|
||||
class="flex float-left p-1 overflow-visible text-sm ease-linear bg-white border-0 rounded cursor-pointer md:hidden md:ml-0 hover:bg-gray-100"
|
||||
@click.prevent="onToggle"
|
||||
>
|
||||
<BaseIcon name="MenuIcon" class="!w-6 !h-6 text-gray-500" />
|
||||
</div>
|
||||
|
||||
<ul class="flex float-right h-8 m-0 list-none md:h-9">
|
||||
<li
|
||||
v-if="hasCreateAbilities"
|
||||
class="relative hidden float-left m-0 md:block"
|
||||
>
|
||||
<BaseDropdown width-class="w-48">
|
||||
<template #activator>
|
||||
<div
|
||||
class="flex items-center justify-center w-8 h-8 ml-2 text-sm text-black bg-white rounded md:h-9 md:w-9"
|
||||
>
|
||||
<BaseIcon name="PlusIcon" class="w-5 h-5 text-gray-600" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<router-link to="/admin/invoices/create">
|
||||
<BaseDropdownItem
|
||||
v-if="userStore.hasAbilities(abilities.CREATE_INVOICE)"
|
||||
>
|
||||
<BaseIcon
|
||||
name="DocumentTextIcon"
|
||||
class="w-5 h-5 mr-3 text-gray-400 group-hover:text-gray-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{ $t('invoices.new_invoice') }}
|
||||
</BaseDropdownItem>
|
||||
</router-link>
|
||||
<router-link to="/admin/estimates/create">
|
||||
<BaseDropdownItem
|
||||
v-if="userStore.hasAbilities(abilities.CREATE_ESTIMATE)"
|
||||
>
|
||||
<BaseIcon
|
||||
name="DocumentIcon"
|
||||
class="w-5 h-5 mr-3 text-gray-400 group-hover:text-gray-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{ $t('estimates.new_estimate') }}
|
||||
</BaseDropdownItem>
|
||||
</router-link>
|
||||
|
||||
<router-link to="/admin/customers/create">
|
||||
<BaseDropdownItem
|
||||
v-if="userStore.hasAbilities(abilities.CREATE_CUSTOMER)"
|
||||
>
|
||||
<BaseIcon
|
||||
name="UserIcon"
|
||||
class="w-5 h-5 mr-3 text-gray-400 group-hover:text-gray-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{ $t('customers.new_customer') }}
|
||||
</BaseDropdownItem>
|
||||
</router-link>
|
||||
</BaseDropdown>
|
||||
</li>
|
||||
|
||||
<li class="ml-2">
|
||||
<GlobalSearchBar
|
||||
v-if="
|
||||
userStore.currentUser.is_owner ||
|
||||
userStore.hasAbilities(abilities.VIEW_CUSTOMER)
|
||||
"
|
||||
/>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<CompanySwitcher />
|
||||
</li>
|
||||
|
||||
<!-- User Dropdown-->
|
||||
<li class="relative block float-left ml-2">
|
||||
<BaseDropdown width-class="w-48">
|
||||
<template #activator>
|
||||
<img
|
||||
:src="previewAvatar"
|
||||
class="block w-8 h-8 rounded md:h-9 md:w-9"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<router-link to="/admin/settings/account-settings">
|
||||
<BaseDropdownItem>
|
||||
<BaseIcon
|
||||
name="CogIcon"
|
||||
class="w-5 h-5 mr-3 text-gray-400 group-hover:text-gray-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{ $t('navigation.settings') }}
|
||||
</BaseDropdownItem>
|
||||
</router-link>
|
||||
|
||||
<BaseDropdownItem @click="logout">
|
||||
<BaseIcon
|
||||
name="LogoutIcon"
|
||||
class="w-5 h-5 mr-3 text-gray-400 group-hover:text-gray-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Logout
|
||||
</BaseDropdownItem>
|
||||
</BaseDropdown>
|
||||
</li>
|
||||
</ul>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useAuthStore } from '@/scripts/stores/auth'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { computed } from 'vue'
|
||||
import { useUserStore } from '@/scripts/stores/user'
|
||||
import { useGlobalStore } from '@/scripts/stores/global'
|
||||
import CompanySwitcher from '@/scripts/components/CompanySwitcher.vue'
|
||||
import GlobalSearchBar from '@/scripts/components/GlobalSearchBar.vue'
|
||||
import abilities from '@/scripts/stub/abilities'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const userStore = useUserStore()
|
||||
const globalStore = useGlobalStore()
|
||||
const router = useRouter()
|
||||
|
||||
const previewAvatar = computed(() => {
|
||||
return userStore.currentUser && userStore.currentUser.avatar !== 0
|
||||
? userStore.currentUser.avatar
|
||||
: getDefaultAvatar()
|
||||
})
|
||||
|
||||
function getDefaultAvatar() {
|
||||
const imgUrl = new URL('/img/default-avatar.jpg', import.meta.url)
|
||||
return imgUrl
|
||||
}
|
||||
|
||||
function hasCreateAbilities() {
|
||||
return userStore.hasAbilities([
|
||||
abilities.CREATE_INVOICE,
|
||||
abilities.CREATE_ESTIMATE,
|
||||
abilities.CREATE_CUSTOMER,
|
||||
])
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
await authStore.logout()
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
function onToggle() {
|
||||
globalStore.setSidebarVisibility(true)
|
||||
}
|
||||
</script>
|
||||
178
resources/scripts/layouts/partials/TheSiteSidebar.vue
Normal file
178
resources/scripts/layouts/partials/TheSiteSidebar.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<!-- MOBILE MENU -->
|
||||
<TransitionRoot as="template" :show="globalStore.isSidebarOpen">
|
||||
<Dialog
|
||||
as="div"
|
||||
class="fixed inset-0 z-40 flex md:hidden"
|
||||
@close="globalStore.setSidebarVisibility(false)"
|
||||
>
|
||||
<TransitionChild
|
||||
as="template"
|
||||
enter="transition-opacity ease-linear duration-300"
|
||||
enter-from="opacity-0"
|
||||
enter-to="opacity-100"
|
||||
leave="transition-opacity ease-linear duration-300"
|
||||
leave-from="opacity-100"
|
||||
leave-to="opacity-0"
|
||||
>
|
||||
<DialogOverlay class="fixed inset-0 bg-gray-600 bg-opacity-75" />
|
||||
</TransitionChild>
|
||||
|
||||
<TransitionChild
|
||||
as="template"
|
||||
enter="transition ease-in-out duration-300 transform"
|
||||
enter-from="-translate-x-full"
|
||||
enter-to="translate-x-0"
|
||||
leave="transition ease-in-out duration-300 transform"
|
||||
leave-from="translate-x-0"
|
||||
leave-to="-translate-x-full"
|
||||
>
|
||||
<div class="relative flex flex-col flex-1 w-full max-w-xs bg-white">
|
||||
<TransitionChild
|
||||
as="template"
|
||||
enter="ease-in-out duration-300"
|
||||
enter-from="opacity-0"
|
||||
enter-to="opacity-100"
|
||||
leave="ease-in-out duration-300"
|
||||
leave-from="opacity-100"
|
||||
leave-to="opacity-0"
|
||||
>
|
||||
<div class="absolute top-0 right-0 pt-2 -mr-12">
|
||||
<button
|
||||
class="
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
w-10
|
||||
h-10
|
||||
ml-1
|
||||
rounded-full
|
||||
focus:outline-none
|
||||
focus:ring-2
|
||||
focus:ring-inset
|
||||
focus:ring-white
|
||||
"
|
||||
@click="globalStore.setSidebarVisibility(false)"
|
||||
>
|
||||
<span class="sr-only">Close sidebar</span>
|
||||
<BaseIcon
|
||||
name="XIcon"
|
||||
class="w-6 h-6 text-white"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</TransitionChild>
|
||||
<div class="flex-1 h-0 pt-5 pb-4 overflow-y-auto">
|
||||
<div class="flex items-center flex-shrink-0 px-4 mb-10">
|
||||
<img
|
||||
src="/img/crater-logo.png"
|
||||
class="block h-auto max-w-full w-36 text-primary-400"
|
||||
alt="Crater Logo"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<nav
|
||||
v-for="menu in globalStore.menuGroups"
|
||||
:key="menu"
|
||||
class="mt-5 space-y-1"
|
||||
>
|
||||
<router-link
|
||||
v-for="item in menu"
|
||||
:key="item.name"
|
||||
:to="item.link"
|
||||
:class="[
|
||||
hasActiveUrl(item.link)
|
||||
? 'text-primary-500 border-primary-500 bg-gray-100 '
|
||||
: 'text-black',
|
||||
'cursor-pointer px-0 pl-4 py-3 border-transparent flex items-center border-l-4 border-solid text-sm not-italic font-medium',
|
||||
]"
|
||||
@click="globalStore.setSidebarVisibility(false)"
|
||||
>
|
||||
<BaseIcon
|
||||
:name="item.icon"
|
||||
:class="[
|
||||
hasActiveUrl(item.link)
|
||||
? 'text-primary-500 '
|
||||
: 'text-gray-400',
|
||||
'mr-4 flex-shrink-0 h-5 w-5',
|
||||
]"
|
||||
@click="globalStore.setSidebarVisibility(false)"
|
||||
/>
|
||||
{{ $t(item.title) }}
|
||||
</router-link>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionChild>
|
||||
<div class="flex-shrink-0 w-14">
|
||||
<!-- Force sidebar to shrink to fit close icon -->
|
||||
</div>
|
||||
</Dialog>
|
||||
</TransitionRoot>
|
||||
|
||||
<!-- DESKTOP MENU -->
|
||||
<div
|
||||
class="
|
||||
hidden
|
||||
w-56
|
||||
h-screen h-screen-ios
|
||||
pb-32
|
||||
overflow-y-auto
|
||||
bg-white
|
||||
border-r border-gray-200 border-solid
|
||||
xl:w-64
|
||||
md:fixed md:flex md:flex-col md:inset-y-0
|
||||
pt-16
|
||||
"
|
||||
>
|
||||
<div
|
||||
v-for="menu in globalStore.menuGroups"
|
||||
:key="menu"
|
||||
class="p-0 m-0 mt-6 list-none"
|
||||
>
|
||||
<router-link
|
||||
v-for="item in menu"
|
||||
:key="item"
|
||||
:to="item.link"
|
||||
:class="[
|
||||
hasActiveUrl(item.link)
|
||||
? 'text-primary-500 border-primary-500 bg-gray-100 '
|
||||
: 'text-black',
|
||||
'cursor-pointer px-0 pl-6 hover:bg-gray-50 py-3 group flex items-center border-l-4 border-solid border-transparent text-sm not-italic font-medium',
|
||||
]"
|
||||
>
|
||||
<BaseIcon
|
||||
:name="item.icon"
|
||||
:class="[
|
||||
hasActiveUrl(item.link)
|
||||
? 'text-primary-500 group-hover:text-primary-500 '
|
||||
: 'text-gray-400 group-hover:text-black',
|
||||
'mr-4 flex-shrink-0 h-5 w-5 ',
|
||||
]"
|
||||
/>
|
||||
|
||||
{{ $t(item.title) }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
Dialog,
|
||||
DialogOverlay,
|
||||
TransitionChild,
|
||||
TransitionRoot,
|
||||
} from '@headlessui/vue'
|
||||
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useGlobalStore } from '@/scripts/stores/global'
|
||||
|
||||
const route = useRoute()
|
||||
const globalStore = useGlobalStore()
|
||||
|
||||
function hasActiveUrl(url) {
|
||||
return route.path.indexOf(url) > -1
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user