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