Select class before user when creating bills, fix national ID and registered classes display, remove course ratings, fix notification badge, and show Persian datetime in the topbar.
210 lines
5.7 KiB
Vue
210 lines
5.7 KiB
Vue
<!-- /src/components/layout/AppTopbar.vue -->
|
|
<template>
|
|
<header class="app-topbar flex align-items-center justify-content-between px-4 py-2 surface-card border-bottom-1 border-color">
|
|
<div class="flex align-items-center gap-3">
|
|
<Button
|
|
icon="pi pi-bars"
|
|
text
|
|
rounded
|
|
class="lg:hidden"
|
|
@click="uiStore.toggleMobileSidebar"
|
|
/>
|
|
<Button
|
|
icon="pi pi-bars"
|
|
text
|
|
rounded
|
|
class="hidden lg:flex"
|
|
@click="uiStore.toggleSidebar"
|
|
/>
|
|
<div class="flex align-items-center gap-2 font-bold text-xl text-primary">
|
|
<i class="pi pi-building text-2xl"></i>
|
|
<span>{{ $t('app.title') }}</span>
|
|
</div>
|
|
<div class="persian-datetime hidden md:flex flex-column align-items-start line-height-2 mr-2">
|
|
<span class="text-sm font-semibold text-color">{{ persianDate }}</span>
|
|
<span class="text-xs text-color-secondary" dir="ltr">{{ persianTime }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex align-items-center gap-3">
|
|
<!-- Dark/Light Theme Toggle -->
|
|
<Button
|
|
:icon="themeStore.isDarkMode ? 'pi pi-sun' : 'pi pi-moon'"
|
|
text
|
|
rounded
|
|
:aria-label="themeStore.isDarkMode ? $t('app.lightMode') : $t('app.darkMode')"
|
|
v-tooltip.bottom="themeStore.isDarkMode ? $t('app.lightMode') : $t('app.darkMode')"
|
|
@click="themeStore.toggleTheme"
|
|
/>
|
|
|
|
<!-- Notifications Icon -->
|
|
<button
|
|
type="button"
|
|
class="notification-trigger"
|
|
:aria-label="$t('navigation.notifications')"
|
|
v-tooltip.bottom="$t('navigation.notifications')"
|
|
@click="$router.push('/notifications')"
|
|
>
|
|
<i class="pi pi-bell"></i>
|
|
<span v-if="notificationCount > 0" class="notification-badge">{{ notificationCount }}</span>
|
|
</button>
|
|
|
|
<!-- User Menu -->
|
|
<div class="flex align-items-center gap-2">
|
|
<Avatar
|
|
icon="pi pi-user"
|
|
shape="circle"
|
|
class="bg-primary text-white cursor-pointer"
|
|
@click="toggleUserMenu"
|
|
/>
|
|
<span class="font-medium hidden sm:inline text-color cursor-pointer" @click="toggleUserMenu">
|
|
{{ authStore.user?.name || authStore.user?.username || 'کاربر سیستم' }}
|
|
</span>
|
|
<Menu ref="userMenu" :model="menuItems" :popup="true" />
|
|
</div>
|
|
</div>
|
|
</header>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, onUnmounted } from 'vue';
|
|
import { useRouter } from 'vue-router';
|
|
import moment from 'jalali-moment';
|
|
import { useAuthStore } from '@/stores/authStore';
|
|
import { useThemeStore } from '@/stores/themeStore';
|
|
import { useUiStore } from '@/stores/uiStore';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { usePersianDate } from '@/composables/usePersianDate';
|
|
import { notificationApi } from '@/api/notificationApi';
|
|
import Button from 'primevue/button';
|
|
import Avatar from 'primevue/avatar';
|
|
import Menu from 'primevue/menu';
|
|
|
|
const authStore = useAuthStore();
|
|
const themeStore = useThemeStore();
|
|
const uiStore = useUiStore();
|
|
const router = useRouter();
|
|
const { t } = useI18n();
|
|
const { toPersianDigits } = usePersianDate();
|
|
|
|
const userMenu = ref(null);
|
|
const notificationCount = ref(0);
|
|
const persianDate = ref('');
|
|
const persianTime = ref('');
|
|
let clockTimer = null;
|
|
|
|
const updateClock = () => {
|
|
const now = moment().locale('fa');
|
|
persianDate.value = toPersianDigits(now.format('dddd jD jMMMM jYYYY'));
|
|
persianTime.value = toPersianDigits(now.format('HH:mm:ss'));
|
|
};
|
|
|
|
const fetchNotificationCount = async () => {
|
|
try {
|
|
const res = await notificationApi.getAll({ limit: 1, page: 1 });
|
|
// axios interceptor returns body: { success, data, meta }
|
|
const total = res?.meta?.totalCount ?? 0;
|
|
notificationCount.value = Number(total) || 0;
|
|
} catch {
|
|
notificationCount.value = 0;
|
|
}
|
|
};
|
|
|
|
const toggleUserMenu = (event) => {
|
|
userMenu.value.toggle(event);
|
|
};
|
|
|
|
const menuItems = ref([
|
|
{
|
|
label: authStore.user?.name || authStore.user?.username || 'کاربر',
|
|
items: [
|
|
{
|
|
label: t('app.profile'),
|
|
icon: 'pi pi-user',
|
|
command: () => {
|
|
const id = authStore.user?._id || authStore.user?.id;
|
|
if (id) {
|
|
router.push(`/users/edit/${id}`);
|
|
}
|
|
}
|
|
},
|
|
{
|
|
label: t('app.logout'),
|
|
icon: 'pi pi-sign-out',
|
|
command: async () => {
|
|
await authStore.logout();
|
|
router.push('/login');
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]);
|
|
|
|
onMounted(() => {
|
|
updateClock();
|
|
clockTimer = setInterval(updateClock, 1000);
|
|
fetchNotificationCount();
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
if (clockTimer) clearInterval(clockTimer);
|
|
});
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.app-topbar {
|
|
height: 60px;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 999;
|
|
}
|
|
|
|
.persian-datetime {
|
|
padding-inline-start: 0.75rem;
|
|
border-inline-start: 1px solid var(--surface-border, #e2e8f0);
|
|
}
|
|
|
|
.notification-trigger {
|
|
position: relative;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 2.5rem;
|
|
height: 2.5rem;
|
|
padding: 0;
|
|
border: none;
|
|
border-radius: 50%;
|
|
background: transparent;
|
|
color: var(--primary-color);
|
|
cursor: pointer;
|
|
transition: background-color 0.15s ease;
|
|
|
|
&:hover {
|
|
background-color: var(--primary-color-light);
|
|
}
|
|
|
|
.pi-bell {
|
|
font-size: 1.15rem;
|
|
line-height: 1;
|
|
}
|
|
}
|
|
|
|
.notification-badge {
|
|
position: absolute;
|
|
top: 0.2rem;
|
|
inset-inline-end: 0.15rem;
|
|
min-width: 1.05rem;
|
|
height: 1.05rem;
|
|
padding: 0 0.25rem;
|
|
border-radius: 999px;
|
|
background: #ef4444;
|
|
color: #fff;
|
|
font-size: 0.65rem;
|
|
font-weight: 700;
|
|
line-height: 1.05rem;
|
|
text-align: center;
|
|
box-shadow: 0 0 0 2px var(--surface-card, #1e293b);
|
|
pointer-events: none;
|
|
}
|
|
</style>
|