fix: resolve education dashboard interface issues
This commit is contained in:
@@ -9,10 +9,7 @@ export const courseSchema = object().shape({
|
||||
.integer('ظرفیت باید عدد صحیح باشد')
|
||||
.min(1, 'ظرفیت باید حداقل ۱ نفر باشد'),
|
||||
prerequisiteCourseIds: array().nullable().default([]),
|
||||
contentMediaId: number()
|
||||
.typeError('شناسه فایل محتوای دوره باید عدد باشد')
|
||||
.nullable()
|
||||
.notRequired(),
|
||||
coverMediaId: number().typeError('شناسه تصویر دوره باید عدد باشد').nullable().notRequired(),
|
||||
description: string().nullable().notRequired(),
|
||||
termId: mixed().required(),
|
||||
isActive: boolean().nullable().notRequired(),
|
||||
|
||||
@@ -91,11 +91,11 @@ const performanceStats = computed(() => {
|
||||
const stats = dashboard.value.stats ?? {}
|
||||
return [
|
||||
{
|
||||
icon: 'user-sound',
|
||||
label: 'تعداد جلسات اعزام شده',
|
||||
icon: 'video',
|
||||
label: 'تعداد جلسات آموزشی گذراندهشده',
|
||||
value: toFaDigits(stats.attendedSessions ?? 0),
|
||||
unit: 'جلسه',
|
||||
subtitle: 'اعزام شده است',
|
||||
subtitle: 'با موفقیت گذرانده شده',
|
||||
to: { name: 'student-terms' },
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,27 +5,47 @@
|
||||
</div>
|
||||
<nav class="admin-sidebar__nav">
|
||||
<ul>
|
||||
<li v-for="item in menuItems" :key="item.title" class="admin-sidebar__item">
|
||||
<component
|
||||
:is="item.to ? 'RouterLink' : 'div'"
|
||||
<li v-for="(item, index) in menuItems" :key="item.title" class="admin-sidebar__item">
|
||||
<button
|
||||
v-if="item.children?.length"
|
||||
type="button"
|
||||
class="admin-sidebar__link"
|
||||
:class="{ 'admin-sidebar__link--active': item.active }"
|
||||
:aria-expanded="isExpanded(item)"
|
||||
:aria-controls="submenuId(index)"
|
||||
@click="toggle(item)"
|
||||
>
|
||||
<SvgIcon v-if="item.icon" :name="item.icon" :size="22" />
|
||||
<span>{{ item.title }}</span>
|
||||
<SvgIcon
|
||||
name="caret-down"
|
||||
:size="16"
|
||||
class="admin-sidebar__chevron"
|
||||
:class="{ 'admin-sidebar__chevron--expanded': isExpanded(item) }"
|
||||
/>
|
||||
</button>
|
||||
<RouterLink
|
||||
v-else
|
||||
:to="item.to"
|
||||
class="admin-sidebar__link"
|
||||
:class="{ 'admin-sidebar__link--active': item.active }"
|
||||
>
|
||||
<SvgIcon v-if="item.icon" :name="item.icon" :size="22" />
|
||||
<span>{{ item.title }}</span>
|
||||
</component>
|
||||
<ul v-if="item.children?.length" class="admin-sidebar__submenu">
|
||||
<li v-for="child in item.children" :key="child.title">
|
||||
<RouterLink
|
||||
:to="child.to"
|
||||
class="admin-sidebar__sublink"
|
||||
:class="{ 'admin-sidebar__sublink--active': child.active }"
|
||||
>
|
||||
{{ child.title }}
|
||||
</RouterLink>
|
||||
</li>
|
||||
</ul>
|
||||
</RouterLink>
|
||||
<Transition v-if="item.children?.length" name="admin-sidebar__accordion">
|
||||
<ul v-show="isExpanded(item)" :id="submenuId(index)" class="admin-sidebar__submenu">
|
||||
<li v-for="child in item.children" :key="child.title">
|
||||
<RouterLink
|
||||
:to="child.to"
|
||||
class="admin-sidebar__sublink"
|
||||
:class="{ 'admin-sidebar__sublink--active': child.active }"
|
||||
>
|
||||
{{ child.title }}
|
||||
</RouterLink>
|
||||
</li>
|
||||
</ul>
|
||||
</Transition>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -34,16 +54,43 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { gallery } from '@/utils/gallery'
|
||||
import SvgIcon from '@/components/icons/SvgIcon.vue'
|
||||
import SidebarLogout from '@/layouts/sidebars/SidebarLogout.vue'
|
||||
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
menuItems: { type: Array, default: () => [] },
|
||||
})
|
||||
|
||||
const logo = gallery.logoPinkishRed
|
||||
const expandedItems = ref({})
|
||||
|
||||
watch(
|
||||
() => props.menuItems,
|
||||
(items) => {
|
||||
const next = { ...expandedItems.value }
|
||||
items.forEach((item) => {
|
||||
if (!item.children?.length) return
|
||||
if (!(item.title in next)) next[item.title] = false
|
||||
if (item.active) next[item.title] = true
|
||||
})
|
||||
expandedItems.value = next
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const isExpanded = (item) => Boolean(expandedItems.value[item.title])
|
||||
|
||||
const toggle = (item) => {
|
||||
expandedItems.value = {
|
||||
...expandedItems.value,
|
||||
[item.title]: !isExpanded(item),
|
||||
}
|
||||
}
|
||||
|
||||
const submenuId = (index) => `admin-sidebar-submenu-${index}`
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -82,6 +129,7 @@ const logo = gallery.logoPinkishRed
|
||||
}
|
||||
|
||||
&__link {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
@@ -91,6 +139,9 @@ const logo = gallery.logoPinkishRed
|
||||
font-size: 0.875rem;
|
||||
color: #4b5563;
|
||||
text-decoration: none;
|
||||
text-align: start;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
transition: background-color 0.15s, color 0.15s;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -105,6 +156,15 @@ const logo = gallery.logoPinkishRed
|
||||
}
|
||||
}
|
||||
|
||||
&__chevron {
|
||||
margin-inline-start: auto;
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&--expanded {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
&__submenu {
|
||||
margin-top: 0.25rem !important;
|
||||
padding-inline-start: 1.5rem !important;
|
||||
@@ -130,5 +190,23 @@ const logo = gallery.logoPinkishRed
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
&__accordion-enter-active,
|
||||
&__accordion-leave-active {
|
||||
overflow: hidden;
|
||||
transition: max-height 0.2s ease, opacity 0.2s ease;
|
||||
}
|
||||
|
||||
&__accordion-enter-from,
|
||||
&__accordion-leave-to {
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&__accordion-enter-to,
|
||||
&__accordion-leave-from {
|
||||
max-height: 16rem;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user