fix: resolve course/class fields, surname i18n, optional nationalId, auto class end date, login styling, and mobile sidebar navigation

This commit is contained in:
2026-08-21 15:03:28 +03:30
parent b1c3dd6218
commit 86f1f9bab0
13 changed files with 348 additions and 117 deletions
+157 -21
View File
@@ -1,39 +1,109 @@
<!-- /src/components/layout/AppSidebar.vue -->
<template>
<div class="app-sidebar p-3 surface-card border-left-1 border-color flex flex-column h-full flex-shrink-0">
<div class="logo flex align-items-center gap-3 px-2 py-3 mb-3 border-bottom-1 border-color">
<i class="pi pi-graduation-cap text-primary text-3xl"></i>
<span class="font-bold text-xl text-color">گام نو</span>
</div>
<!-- Desktop Sidebar -->
<aside
class="app-sidebar desktop-sidebar surface-card border-left-1 border-color flex flex-column h-full flex-shrink-0"
:class="{ 'sidebar-collapsed': !uiStore.isSidebarOpen }"
>
<div class="sidebar-inner flex flex-column h-full p-3">
<div class="logo flex align-items-center gap-3 px-2 py-3 mb-3 border-bottom-1 border-color flex-shrink-0">
<i class="pi pi-graduation-cap text-primary text-3xl"></i>
<span class="font-bold text-xl text-color">گام نو</span>
</div>
<div class="menu-list flex-grow-1 overflow-y-auto">
<div v-for="group in menuGroups" :key="group.key" class="menu-group mb-3">
<div class="menu-group-label px-3 py-2 text-xs font-bold text-muted uppercase">
{{ group.label }}
<div class="menu-list flex-grow-1 overflow-y-auto">
<div v-for="group in menuGroups" :key="group.key" class="menu-group mb-3">
<div class="menu-group-label px-3 py-2 text-xs font-bold text-muted uppercase">
{{ group.label }}
</div>
<router-link
v-for="item in group.items"
:key="item.to"
:to="item.to"
class="menu-item flex align-items-center gap-3 px-3 py-2 border-round text-color mb-1 transition-colors"
:class="{ 'active-item': isMenuActive(item.to) }"
>
<i :class="item.icon" class="text-lg"></i>
<span class="font-medium text-sm">{{ item.label }}</span>
</router-link>
</div>
<router-link
v-for="item in group.items"
:key="item.to"
:to="item.to"
class="menu-item flex align-items-center gap-3 px-3 py-2 border-round text-color mb-1 transition-colors"
:class="{ 'active-item': isMenuActive(item.to) }"
>
<i :class="item.icon" class="text-lg"></i>
<span class="font-medium text-sm">{{ item.label }}</span>
</router-link>
</div>
</div>
</div>
</aside>
<!-- Mobile Drawer & Backdrop -->
<Teleport to="body">
<Transition name="sidebar-fade">
<div
v-if="uiStore.isMobileSidebarOpen"
class="mobile-sidebar-backdrop"
@click="uiStore.closeMobileSidebar"
/>
</Transition>
<Transition name="sidebar-slide">
<div
v-if="uiStore.isMobileSidebarOpen"
class="mobile-sidebar-drawer surface-card flex flex-column p-3 shadow-8 border-left-1 border-color"
dir="rtl"
>
<div class="flex align-items-center justify-content-between px-2 py-3 mb-3 border-bottom-1 border-color flex-shrink-0">
<div class="logo flex align-items-center gap-2">
<i class="pi pi-graduation-cap text-primary text-2xl"></i>
<span class="font-bold text-lg text-color">گام نو</span>
</div>
<Button
icon="pi pi-times"
text
rounded
severity="secondary"
size="small"
@click="uiStore.closeMobileSidebar"
/>
</div>
<div class="menu-list flex-grow-1 overflow-y-auto">
<div v-for="group in menuGroups" :key="group.key" class="menu-group mb-3">
<div class="menu-group-label px-3 py-2 text-xs font-bold text-muted uppercase">
{{ group.label }}
</div>
<router-link
v-for="item in group.items"
:key="item.to"
:to="item.to"
class="menu-item flex align-items-center gap-3 px-3 py-2 border-round text-color mb-1 transition-colors"
:class="{ 'active-item': isMenuActive(item.to) }"
@click="uiStore.closeMobileSidebar"
>
<i :class="item.icon" class="text-lg"></i>
<span class="font-medium text-sm">{{ item.label }}</span>
</router-link>
</div>
</div>
</div>
</Transition>
</Teleport>
</template>
<script setup>
import { computed } from 'vue';
import { computed, watch } from 'vue';
import { useRoute } from 'vue-router';
import { usePermissionStore } from '@/stores/permissionStore';
import { useUiStore } from '@/stores/uiStore';
import { PERMISSIONS } from '@/constants/permissions';
import Button from 'primevue/button';
const route = useRoute();
const permissionStore = usePermissionStore();
const uiStore = useUiStore();
// Automatically close mobile sidebar on navigation
watch(
() => route.path,
() => {
uiStore.closeMobileSidebar();
}
);
const menuGroups = computed(() => {
const groups = [
@@ -124,6 +194,72 @@ function isMenuActive(to) {
max-width: 240px;
flex-shrink: 0;
min-height: 100vh;
transition: width 0.25s ease, min-width 0.25s ease, max-width 0.25s ease, opacity 0.2s ease, padding 0.25s ease;
overflow: hidden;
.sidebar-inner {
width: 240px;
min-width: 240px;
}
&.sidebar-collapsed {
width: 0 !important;
min-width: 0 !important;
max-width: 0 !important;
padding: 0 !important;
border-left: none !important;
border-right: none !important;
opacity: 0;
pointer-events: none;
}
}
@media (max-width: 991px) {
.desktop-sidebar {
display: none !important;
}
}
.mobile-sidebar-backdrop {
position: fixed;
inset: 0;
background: rgba(15, 23, 42, 0.65);
backdrop-filter: blur(3px);
z-index: 1100;
}
.mobile-sidebar-drawer {
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: 280px;
max-width: 85vw;
height: 100vh;
height: 100dvh;
z-index: 1101;
background: var(--surface-card);
box-sizing: border-box;
}
.sidebar-slide-enter-active,
.sidebar-slide-leave-active {
transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
.sidebar-slide-enter-from,
.sidebar-slide-leave-to {
transform: translateX(100%);
}
.sidebar-fade-enter-active,
.sidebar-fade-leave-active {
transition: opacity 0.2s ease;
}
.sidebar-fade-enter-from,
.sidebar-fade-leave-to {
opacity: 0;
}
.menu-group-label {