mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-15 17:24:10 +00:00
Personalize welcome heading with user name, add descriptive subtitle, improve invitation card styling, remove redundant logout button. Fix hasCreateAbilities check in header to actually call the function. Widen company switcher dropdown and improve invitation row layout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
277 lines
8.0 KiB
Vue
277 lines
8.0 KiB
Vue
<template>
|
|
<div ref="companySwitchBar" class="relative rounded">
|
|
<CompanyModal />
|
|
|
|
<div
|
|
class="
|
|
flex
|
|
items-center
|
|
justify-center
|
|
px-3
|
|
h-8
|
|
md:h-9
|
|
ml-2
|
|
text-sm text-white
|
|
bg-white/20
|
|
rounded
|
|
cursor-pointer
|
|
"
|
|
@click="isShow = !isShow"
|
|
>
|
|
<span
|
|
v-if="companyStore.selectedCompany"
|
|
class="w-16 text-sm font-medium truncate sm:w-auto"
|
|
>
|
|
{{ companyStore.selectedCompany.name }}
|
|
</span>
|
|
<BaseIcon name="ChevronDownIcon" class="h-5 ml-1 text-white" />
|
|
</div>
|
|
|
|
<transition
|
|
enter-active-class="transition duration-200 ease-out"
|
|
enter-from-class="translate-y-1 opacity-0"
|
|
enter-to-class="translate-y-0 opacity-100"
|
|
leave-active-class="transition duration-150 ease-in"
|
|
leave-from-class="translate-y-0 opacity-100"
|
|
leave-to-class="translate-y-1 opacity-0"
|
|
>
|
|
<div
|
|
v-if="isShow"
|
|
class="absolute right-0 mt-2 bg-white rounded-md shadow-lg"
|
|
>
|
|
<div
|
|
class="
|
|
overflow-y-auto
|
|
scrollbar-thin scrollbar-thumb-rounded-full
|
|
w-[300px]
|
|
max-h-[350px]
|
|
scrollbar-thumb-gray-300 scrollbar-track-gray-10
|
|
pb-4
|
|
"
|
|
>
|
|
<label
|
|
class="
|
|
px-3
|
|
py-2
|
|
text-xs
|
|
font-semibold
|
|
text-gray-400
|
|
mb-0.5
|
|
block
|
|
uppercase
|
|
"
|
|
>
|
|
{{ $t('company_switcher.label') }}
|
|
</label>
|
|
|
|
<div
|
|
v-if="companyStore.companies.length < 1"
|
|
class="
|
|
flex flex-col
|
|
items-center
|
|
justify-center
|
|
p-2
|
|
px-3
|
|
mt-4
|
|
text-base text-gray-400
|
|
"
|
|
>
|
|
<BaseIcon name="ExclamationCircleIcon" class="h-5 text-gray-400" />
|
|
{{ $t('company_switcher.no_results_found') }}
|
|
</div>
|
|
<div v-else>
|
|
<div v-if="companyStore.companies.length > 0">
|
|
<div
|
|
v-for="(company, index) in companyStore.companies"
|
|
:key="index"
|
|
class="
|
|
p-2
|
|
px-3
|
|
rounded-md
|
|
cursor-pointer
|
|
hover:bg-gray-100 hover:text-primary-500
|
|
"
|
|
:class="{
|
|
'bg-gray-100 text-primary-500':
|
|
companyStore.selectedCompany && companyStore.selectedCompany.id === company.id,
|
|
}"
|
|
@click="changeCompany(company)"
|
|
>
|
|
<div class="flex items-center">
|
|
<span
|
|
class="
|
|
flex
|
|
items-center
|
|
justify-center
|
|
mr-3
|
|
overflow-hidden
|
|
text-base
|
|
font-semibold
|
|
bg-gray-200
|
|
rounded-md
|
|
w-9
|
|
h-9
|
|
text-primary-500
|
|
"
|
|
>
|
|
<span v-if="!company.logo">
|
|
{{ initGenerator(company.name) }}
|
|
</span>
|
|
<img
|
|
v-else
|
|
:src="company.logo"
|
|
alt="Company logo"
|
|
class="w-full h-full object-contain"
|
|
/>
|
|
</span>
|
|
<div class="flex flex-col">
|
|
<span class="text-sm">{{ company.name }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Pending Invitations -->
|
|
<div
|
|
v-if="invitationStore.pendingInvitations.length > 0"
|
|
class="border-t border-gray-100 p-2"
|
|
>
|
|
<label
|
|
class="
|
|
block px-1 pt-1 pb-2 text-xs font-semibold
|
|
leading-tight text-gray-400 uppercase
|
|
"
|
|
>
|
|
{{ $t('members.pending_invitations') }}
|
|
</label>
|
|
<div
|
|
v-for="invitation in invitationStore.pendingInvitations"
|
|
:key="invitation.id"
|
|
class="p-2 px-3 rounded-md"
|
|
>
|
|
<div class="flex items-center mb-2">
|
|
<span
|
|
class="
|
|
flex items-center justify-center mr-3
|
|
overflow-hidden text-xs font-semibold
|
|
bg-gray-200 rounded-md w-9 h-9 shrink-0 text-gray-400
|
|
"
|
|
>
|
|
{{ initGenerator(invitation.company?.name || '?') }}
|
|
</span>
|
|
<div class="flex flex-col min-w-0">
|
|
<span class="text-sm text-gray-700 truncate">{{ invitation.company?.name }}</span>
|
|
<span class="text-xs text-gray-400">{{ invitation.role?.title }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="flex space-x-1 pl-12">
|
|
<button
|
|
class="text-xs px-2 py-1 rounded bg-primary-500 text-white hover:bg-primary-600"
|
|
@click.stop="acceptInvitation(invitation.token)"
|
|
>
|
|
{{ $t('general.accept') }}
|
|
</button>
|
|
<button
|
|
class="text-xs px-2 py-1 rounded bg-gray-200 text-gray-600 hover:bg-gray-300"
|
|
@click.stop="declineInvitation(invitation.token)"
|
|
>
|
|
{{ $t('general.decline') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="userStore.currentUser.is_owner"
|
|
class="
|
|
flex
|
|
items-center
|
|
justify-center
|
|
p-4
|
|
pl-3
|
|
border-t-2 border-gray-100
|
|
cursor-pointer
|
|
text-primary-400
|
|
hover:text-primary-500
|
|
"
|
|
@click="addNewCompany"
|
|
>
|
|
<BaseIcon name="PlusIcon" class="h-5 mr-2" />
|
|
|
|
<span class="font-medium">
|
|
{{ $t('company_switcher.add_new_company') }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</transition>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, watch } from 'vue'
|
|
import { useCompanyStore } from '@/scripts/admin/stores/company'
|
|
import { onClickOutside } from '@vueuse/core'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { useModalStore } from '../stores/modal'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { useGlobalStore } from '@/scripts/admin//stores/global'
|
|
import { useUserStore } from '@/scripts/admin/stores/user'
|
|
import { useInvitationStore } from '@/scripts/admin/stores/invitation'
|
|
|
|
import CompanyModal from '@/scripts/admin/components/modal-components/CompanyModal.vue'
|
|
import abilities from '@/scripts/admin/stub/abilities'
|
|
|
|
const companyStore = useCompanyStore()
|
|
const modalStore = useModalStore()
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const globalStore = useGlobalStore()
|
|
const { t } = useI18n()
|
|
const userStore = useUserStore()
|
|
const invitationStore = useInvitationStore()
|
|
const isShow = ref(false)
|
|
const name = ref('')
|
|
const companySwitchBar = ref(null)
|
|
|
|
watch(route, () => {
|
|
isShow.value = false
|
|
name.value = ''
|
|
})
|
|
|
|
onClickOutside(companySwitchBar, () => {
|
|
isShow.value = false
|
|
})
|
|
|
|
function initGenerator(name) {
|
|
if (name) {
|
|
const nameSplit = name.split(' ')
|
|
const initials = nameSplit[0].charAt(0).toUpperCase()
|
|
return initials
|
|
}
|
|
}
|
|
|
|
function addNewCompany() {
|
|
modalStore.openModal({
|
|
title: t('company_switcher.new_company'),
|
|
componentName: 'CompanyModal',
|
|
size: 'sm',
|
|
})
|
|
}
|
|
|
|
async function changeCompany(company) {
|
|
await companyStore.setSelectedCompany(company)
|
|
router.push('/admin/dashboard')
|
|
await globalStore.setIsAppLoaded(false)
|
|
await globalStore.bootstrap()
|
|
}
|
|
|
|
async function acceptInvitation(token) {
|
|
await invitationStore.accept(token)
|
|
}
|
|
|
|
async function declineInvitation(token) {
|
|
await invitationStore.decline(token)
|
|
}
|
|
</script>
|