@@ -7,7 +7,6 @@
|
||||
</template>
|
||||
</TextField>
|
||||
<SelectField
|
||||
v-if="showTermFilter"
|
||||
v-model="form.termId"
|
||||
name="termId"
|
||||
label="ترم"
|
||||
@@ -68,7 +67,6 @@ const props = defineProps({
|
||||
type: Object,
|
||||
default: () => ({ title: '', termId: '', status: '', fromDate: '', toDate: '' }),
|
||||
},
|
||||
showTermFilter: { type: Boolean, default: false },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'apply', 'reset'])
|
||||
@@ -100,9 +98,7 @@ const statusOptions = [
|
||||
|
||||
const termFilters = ref({})
|
||||
const termPagination = ref({ page: 1, perPage: 10 })
|
||||
const { data: termsResponse } = useAdminTermsListQuery(termFilters, termPagination, {
|
||||
enabled: () => props.showTermFilter,
|
||||
})
|
||||
const { data: termsResponse } = useAdminTermsListQuery(termFilters, termPagination)
|
||||
const termOptions = computed(() => termsResponse.value?.data ?? [])
|
||||
|
||||
const hasFilters = computed(() =>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<div class="course-details__grid">
|
||||
<LineInfoBlock title="عنوان دوره" :desc="course.title || '—'" />
|
||||
<LineInfoBlock title="تعداد جلسات" :numeric-desc="course.sessionsCount ?? 0" />
|
||||
<LineInfoBlock title="پیشنیاز دوره" :desc="teacherName" />
|
||||
<LineInfoBlock title="پیشنیاز دوره" :desc="prerequisiteCourses" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -99,11 +99,9 @@ const { data: course, isLoading } = useAdminCourseQuery(courseId, {
|
||||
enabled: () => !!courseId.value,
|
||||
})
|
||||
|
||||
const teacherName = computed(() => {
|
||||
const t = course.value?.teacher
|
||||
if (!t) return '—'
|
||||
return `${t.firstName || ''} ${t.lastName || ''}`.trim() || t.fullName || '—'
|
||||
})
|
||||
const prerequisiteCourses = computed(() =>
|
||||
course.value.prerequisiteCourses.map((course_) => course_.title).join(' ,')
|
||||
)
|
||||
|
||||
const sessionsFilters = computed(() => ({ courseId: courseId.value }))
|
||||
const { pagination: sessionsPagination, setPage: setSessionsPage } = usePagination({
|
||||
|
||||
@@ -65,28 +65,30 @@
|
||||
</div>
|
||||
<div class="course-form__cell course-form__cell--third">
|
||||
<SelectField
|
||||
v-model="form.prerequisites"
|
||||
name="prerequisites"
|
||||
v-model="form.termId"
|
||||
name="termId"
|
||||
label="ترم"
|
||||
:options="termOptions"
|
||||
option-label="title"
|
||||
option-value="id"
|
||||
:error="errors.termId"
|
||||
/>
|
||||
</div>
|
||||
<div class="course-form__cell course-form__cell--third">
|
||||
<SelectField
|
||||
v-model="form.prerequisiteCourseIds"
|
||||
name="prerequisiteCourseIds"
|
||||
label="دورههای پیشنیاز"
|
||||
:options="prerequisiteOptions"
|
||||
option-label="title"
|
||||
option-value="id"
|
||||
:multiple="true"
|
||||
:searchable="true"
|
||||
:on-search="searchPrerequisites"
|
||||
/>
|
||||
</div>
|
||||
<div class="course-form__cell course-form__cell--third">
|
||||
<SelectField
|
||||
v-model="form.contentType"
|
||||
name="contentType"
|
||||
label="نوع فایل دوره"
|
||||
:options="contentTypeOptions"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
:error="errors.contentType"
|
||||
:disabled="!form.termId"
|
||||
:on-search="searchPrerequisiteCourses"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="course-form__cell course-form__cell--full">
|
||||
<TextareaField
|
||||
v-model="form.description"
|
||||
@@ -132,7 +134,6 @@
|
||||
import { toast } from 'vue3-toastify'
|
||||
import useYup from '@/composables/useYup'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { COURSE_CONTENT_TYPE } from '@/enums'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import useDebounce from '@/composables/useDebounce'
|
||||
import { useQueryClient } from '@tanstack/vue-query'
|
||||
@@ -146,6 +147,7 @@ import { useUploadMediaMutation } from '@/services/query/auth'
|
||||
import { courseSchema } from '@/features/admin/courses/schema'
|
||||
import TextareaField from '@/components/form/TextareaField.vue'
|
||||
import { useAdminUsersListQuery } from '@/services/query/admin-users'
|
||||
import { useAdminTermsListQuery } from '@/services/query/admin-terms'
|
||||
import BoxedIconTitleBlock from '@/components/blocks/BoxedIconTitleBlock.vue'
|
||||
import {
|
||||
adminCoursesKeys,
|
||||
@@ -167,8 +169,7 @@ const form = ref({
|
||||
title: '',
|
||||
teacherId: '',
|
||||
capacity: '',
|
||||
prerequisites: [],
|
||||
contentType: '',
|
||||
prerequisiteCourseIds: [],
|
||||
description: '',
|
||||
coverMediaId: null,
|
||||
termId: termId.value,
|
||||
@@ -176,11 +177,6 @@ const form = ref({
|
||||
|
||||
const image = ref(null)
|
||||
|
||||
const contentTypeOptions = Object.entries(COURSE_CONTENT_TYPE).map(([value, label]) => ({
|
||||
value,
|
||||
label,
|
||||
}))
|
||||
|
||||
const { validate, validateAt, errors } = useYup(courseSchema)
|
||||
|
||||
const teacherSearch = ref('')
|
||||
@@ -203,6 +199,11 @@ const teacherOptions = computed(() => {
|
||||
}))
|
||||
})
|
||||
|
||||
const termFilters = ref({})
|
||||
const termPagination = ref({ page: 1, perPage: 100 })
|
||||
const { data: termsResponse } = useAdminTermsListQuery(termFilters, termPagination)
|
||||
const termOptions = computed(() => termsResponse.value?.data ?? [])
|
||||
|
||||
const prereqSearch = ref('')
|
||||
const prereqFilters = computed(() => ({ title: prereqSearch.value }))
|
||||
const prereqPagination = ref({ page: 1, perPage: 10 })
|
||||
@@ -211,13 +212,14 @@ const selectedPrereqs = ref([])
|
||||
const prerequisiteOptions = computed(() => {
|
||||
const base = prereqsResponse.value?.data ?? []
|
||||
const extras = selectedPrereqs.value.filter((p) => !base.some((b) => b.id === p.id))
|
||||
return [...base, ...extras]
|
||||
const merged = [...base, ...extras]
|
||||
return courseId.value ? merged.filter((c) => c.id !== courseId.value) : merged
|
||||
})
|
||||
|
||||
const searchTeachers = useDebounce((q) => {
|
||||
teacherSearch.value = q || ''
|
||||
}, 400)
|
||||
const searchPrerequisites = useDebounce((q) => {
|
||||
const searchPrerequisiteCourses = useDebounce((q) => {
|
||||
prereqSearch.value = q || ''
|
||||
}, 400)
|
||||
|
||||
@@ -225,25 +227,29 @@ const { data: existingCourse } = useAdminCourseQuery(courseId, {
|
||||
enabled: () => !!courseId.value,
|
||||
})
|
||||
|
||||
watch(existingCourse, (course) => {
|
||||
if (!course) return
|
||||
const teacher = course.teacher
|
||||
if (teacher) selectedTeacher.value = teacher
|
||||
const prereqs = Array.isArray(course.prerequisites) ? course.prerequisites : []
|
||||
selectedPrereqs.value = prereqs.map((p) => p.course || p).filter((c) => c?.id)
|
||||
watch(
|
||||
existingCourse,
|
||||
(course) => {
|
||||
if (!course) return
|
||||
const teacher = course.teacher
|
||||
if (teacher) selectedTeacher.value = teacher
|
||||
const prereqs = Array.isArray(course.prerequisiteCourseIds) ? course.prerequisiteCourseIds : []
|
||||
selectedPrereqs.value = prereqs.map((p) => p.course || p).filter((c) => c?.id)
|
||||
|
||||
form.value = {
|
||||
title: course.title || '',
|
||||
teacherId: teacher?.id || course.teacherId || '',
|
||||
capacity: course.capacity ?? '',
|
||||
prerequisites: prereqs.map((p) => p.courseId ?? p.id).filter(Boolean),
|
||||
contentType: course.contentType || '',
|
||||
description: course.description || '',
|
||||
coverMediaId: course.coverMediaId || null,
|
||||
termId: course.termId ?? termId.value,
|
||||
}
|
||||
if (course.coverUrl) image.value = { url: course.coverUrl }
|
||||
})
|
||||
form.value = {
|
||||
title: course.title || '',
|
||||
teacherId: teacher?.id || course.teacherId || '',
|
||||
capacity: course.capacity ?? '',
|
||||
prerequisiteCourseIds: prereqs.map((p) => p.courseId ?? p.id).filter(Boolean),
|
||||
contentType: course.contentType || '',
|
||||
description: course.description || '',
|
||||
coverMediaId: course.coverMediaId || null,
|
||||
termId: course.termId ?? termId.value,
|
||||
}
|
||||
if (course.coverUrl) image.value = { url: course.coverUrl }
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const uploadMutation = useUploadMediaMutation()
|
||||
|
||||
|
||||
@@ -10,19 +10,14 @@
|
||||
</template>
|
||||
</BoxedIconTitleBlock>
|
||||
|
||||
<CoursesFilters
|
||||
v-model="currentFilters"
|
||||
:show-term-filter="activeTab === 'offered'"
|
||||
@apply="onFilterApply"
|
||||
@reset="onFilterReset"
|
||||
/>
|
||||
<CoursesFilters v-model="offeredFilters" @apply="onFilterApply" @reset="onFilterReset" />
|
||||
|
||||
<TabsBlock :tabs="tabs" v-model="activeTab" @change-tab="onTabChange">
|
||||
<template #templates>
|
||||
<SkeletonLoaderBlock v-if="templatesPending" :rows="6" :cols-per-row="1" />
|
||||
<div v-else-if="templates.length > 0">
|
||||
<TabsBlock :tabs="tabs">
|
||||
<template #offered>
|
||||
<SkeletonLoaderBlock v-if="offeredPending" :rows="6" :cols-per-row="1" />
|
||||
<div v-else-if="offered.length > 0">
|
||||
<CourseItem
|
||||
v-for="course in templates"
|
||||
v-for="course in offered"
|
||||
:key="course.id"
|
||||
:course="course"
|
||||
@edit="onEditCourse"
|
||||
@@ -31,36 +26,11 @@
|
||||
@show-details="onShowDetails"
|
||||
/>
|
||||
</div>
|
||||
<NoItems v-else title="متاسفیم" desc="دوره الگویی برای نمایش وجود ندارد." />
|
||||
<PaginationBlock :pagination="templatesPaginationMeta" @update:page="setTemplatesPage" />
|
||||
</template>
|
||||
|
||||
<template #offered>
|
||||
<NoItems
|
||||
v-if="!hasOfferedTerm"
|
||||
title="ترم را انتخاب کنید"
|
||||
desc="برای نمایش دورههای ارائه شده، ابتدا ترم را از فیلترها انتخاب کنید."
|
||||
/>
|
||||
<template v-else>
|
||||
<SkeletonLoaderBlock v-if="offeredPending" :rows="6" :cols-per-row="1" />
|
||||
<div v-else-if="offered.length > 0">
|
||||
<CourseItem
|
||||
v-for="course in offered"
|
||||
:key="course.id"
|
||||
:course="course"
|
||||
@edit="onEditCourse"
|
||||
@delete="onAskDelete"
|
||||
@change-status="onChangeStatus"
|
||||
@show-details="onShowDetails"
|
||||
/>
|
||||
</div>
|
||||
<NoItems v-else title="متاسفیم" desc="دورهای برای نمایش وجود ندارد." />
|
||||
<PaginationBlock :pagination="offeredPaginationMeta" @update:page="setOfferedPage" />
|
||||
</template>
|
||||
<NoItems v-else title="متاسفیم" desc="دورهای برای نمایش وجود ندارد." />
|
||||
<PaginationBlock :pagination="offeredPaginationMeta" @update:page="setOfferedPage" />
|
||||
</template>
|
||||
</TabsBlock>
|
||||
|
||||
<AddOfferedCourseModal v-if="isModal('AddOfferedCourseModal')" />
|
||||
<CourseDetailsModal v-if="isModal('CourseDetailsModal')" />
|
||||
<AddSessionToCourseModal v-if="isModal('AddSessionToCourseModal')" />
|
||||
</div>
|
||||
@@ -81,7 +51,6 @@ import BoxedIconTitleBlock from '@/components/blocks/BoxedIconTitleBlock.vue'
|
||||
import SkeletonLoaderBlock from '@/components/blocks/SkeletonLoaderBlock.vue'
|
||||
import CoursesFilters from '@/features/admin/courses/components/CoursesFilters.vue'
|
||||
import CourseDetailsModal from '@/features/admin/courses/components/modals/CourseDetailsModal.vue'
|
||||
import AddOfferedCourseModal from '@/features/admin/courses/components/modals/AddOfferedCourseModal.vue'
|
||||
import AddSessionToCourseModal from '@/features/admin/courses/components/modals/AddSessionToCourseModal.vue'
|
||||
import {
|
||||
adminCoursesKeys,
|
||||
@@ -98,25 +67,10 @@ const { openModal, isModal } = useModal()
|
||||
const routeTermId = computed(() => (route.params.termId ? Number(route.params.termId) : null))
|
||||
|
||||
const onAdd = () => {
|
||||
if (activeTab.value === 'templates') {
|
||||
router.push({ name: 'admin-add-course' }).catch(() => {})
|
||||
} else {
|
||||
openModal('AddOfferedCourseModal', {
|
||||
mode: 'add',
|
||||
termId: routeTermId.value ?? undefined,
|
||||
})
|
||||
}
|
||||
router.push({ name: 'admin-add-course' }).catch(() => {})
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
name: 'templates',
|
||||
label: 'دورههای الگو',
|
||||
icon: 'list-bullets',
|
||||
hasButton: true,
|
||||
textButton: 'افزودن دوره الگوی جدید',
|
||||
buttonAction: onAdd,
|
||||
},
|
||||
{
|
||||
name: 'offered',
|
||||
label: 'دورههای ارائه شده',
|
||||
@@ -126,58 +80,21 @@ const tabs = [
|
||||
buttonAction: onAdd,
|
||||
},
|
||||
]
|
||||
const activeTab = ref(routeTermId.value ? 'offered' : 'templates')
|
||||
|
||||
const templateFilters = ref({})
|
||||
const offeredFilters = ref({})
|
||||
|
||||
const currentFilters = computed({
|
||||
get: () => (activeTab.value === 'templates' ? templateFilters.value : offeredFilters.value),
|
||||
set: (val) => {
|
||||
if (activeTab.value === 'templates') templateFilters.value = val
|
||||
else offeredFilters.value = val
|
||||
},
|
||||
})
|
||||
|
||||
const {
|
||||
pagination: templatesPagination,
|
||||
setPage: setTemplatesPage,
|
||||
reset: resetTemplatesPagination,
|
||||
} = usePagination({ page: 1, perPage: 10 })
|
||||
|
||||
const {
|
||||
pagination: offeredPagination,
|
||||
setPage: setOfferedPage,
|
||||
reset: resetOfferedPagination,
|
||||
} = usePagination({ page: 1, perPage: 10 })
|
||||
|
||||
const { data: templatesData, isLoading: templatesPending } = useAdminCoursesListQuery(
|
||||
templateFilters,
|
||||
templatesPagination,
|
||||
{
|
||||
enabled: () => activeTab.value === 'templates',
|
||||
keepPreviousData: true,
|
||||
}
|
||||
)
|
||||
|
||||
const hasOfferedTerm = computed(() => !!offeredFilters.value.termId)
|
||||
|
||||
const { data: offeredData, isLoading: offeredPending } = useAdminCoursesListQuery(
|
||||
offeredFilters,
|
||||
offeredPagination,
|
||||
{
|
||||
enabled: () => activeTab.value === 'offered' && hasOfferedTerm.value,
|
||||
keepPreviousData: true,
|
||||
}
|
||||
{ keepPreviousData: true }
|
||||
)
|
||||
|
||||
const templates = computed(() => templatesData.value?.data ?? [])
|
||||
const templatesPaginationMeta = computed(() => ({
|
||||
page: templatesPagination.value.page,
|
||||
perPage: templatesPagination.value.perPage,
|
||||
...templatesData.value?.meta,
|
||||
}))
|
||||
|
||||
const offered = computed(() => offeredData.value?.data ?? [])
|
||||
const offeredPaginationMeta = computed(() => ({
|
||||
page: offeredPagination.value.page,
|
||||
@@ -185,14 +102,7 @@ const offeredPaginationMeta = computed(() => ({
|
||||
...offeredData.value?.meta,
|
||||
}))
|
||||
|
||||
const onTabChange = () => {
|
||||
/* tab managed via v-model; no further action required */
|
||||
}
|
||||
|
||||
const onFilterApply = () => {
|
||||
if (activeTab.value === 'templates') resetTemplatesPagination()
|
||||
else resetOfferedPagination()
|
||||
}
|
||||
const onFilterApply = () => resetOfferedPagination()
|
||||
const onFilterReset = onFilterApply
|
||||
|
||||
const onEditCourse = (course) => {
|
||||
@@ -200,16 +110,12 @@ const onEditCourse = (course) => {
|
||||
}
|
||||
|
||||
const onShowDetails = (course) => {
|
||||
openModal('CourseDetailsModal', {
|
||||
id: course.id,
|
||||
type: activeTab.value === 'templates' ? 'template' : 'offered',
|
||||
})
|
||||
openModal('CourseDetailsModal', { id: course.id })
|
||||
}
|
||||
|
||||
const invalidate = () => queryClient.invalidateQueries({ queryKey: adminCoursesKeys.all })
|
||||
|
||||
const deleteMutation = useDeleteAdminCourseMutation()
|
||||
// Backend has no dedicated status endpoint — PATCH /courses/:id with { isActive }.
|
||||
const updateMutation = useUpdateAdminCourseMutation()
|
||||
|
||||
const onAskDelete = (course) => {
|
||||
@@ -226,7 +132,6 @@ const onChangeStatus = ({ id, isActive }) => {
|
||||
|
||||
const syncRouteTermId = (termId) => {
|
||||
if (!termId) return
|
||||
activeTab.value = 'offered'
|
||||
offeredFilters.value = { ...offeredFilters.value, termId }
|
||||
resetOfferedPagination()
|
||||
}
|
||||
|
||||
@@ -3,12 +3,11 @@ import { array, boolean, mixed, number, object, string } from 'yup'
|
||||
export const courseSchema = object().shape({
|
||||
title: string().required().min(3).max(255),
|
||||
teacherId: mixed().required(),
|
||||
capacity: number().required().min(1),
|
||||
prerequisites: array().nullable().default([]),
|
||||
contentType: string().oneOf(['video', 'voice', 'text']).required(),
|
||||
capacity: number().typeError('ظرفیت باید عدد باشد').required().min(1),
|
||||
prerequisiteCourseIds: array().nullable().default([]),
|
||||
contentMediaId: number().nullable().notRequired(),
|
||||
description: string().nullable().notRequired(),
|
||||
termId: mixed().nullable(),
|
||||
termId: mixed().required(),
|
||||
isActive: boolean().nullable().notRequired(),
|
||||
})
|
||||
|
||||
|
||||
@@ -15,10 +15,6 @@
|
||||
<span class="session-item__pill-label">متعلق به دوره:</span>
|
||||
<span class="session-item__pill-value">{{ session.course?.title || '—' }}</span>
|
||||
</div>
|
||||
<div class="session-item__pill">
|
||||
<span class="session-item__pill-label">مدت جلسه:</span>
|
||||
<span class="session-item__pill-value">{{ session.durationMinutes ?? '—' }}</span>
|
||||
</div>
|
||||
<div class="session-item__pill">
|
||||
<span class="session-item__pill-label">نوع جلسه:</span>
|
||||
<span class="session-item__pill-value">{{ sessionTypeLabel }}</span>
|
||||
@@ -83,9 +79,7 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['edit', 'delete', 'show-details', 'show-attendance'])
|
||||
|
||||
const sessionTypeLabel = computed(
|
||||
() => props.session.sessionTypeFa || SESSION_TYPE[props.session.sessionType] || '—'
|
||||
)
|
||||
const sessionTypeLabel = computed(() => SESSION_TYPE[props.session.type] || '—')
|
||||
|
||||
const hasUsedTerms = computed(() => (props.session.usedInTerms?.length ?? 0) > 0)
|
||||
</script>
|
||||
|
||||
@@ -112,7 +112,7 @@ const isOnline = computed(() => session.value?.type === 'online')
|
||||
|
||||
const collectionToContentType = (collectionName) => {
|
||||
if (collectionName === 'videos') return 'video'
|
||||
if (collectionName === 'voices') return 'voice'
|
||||
if (collectionName === 'voice') return 'voice'
|
||||
if (collectionName === 'pdfs') return 'text'
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -235,6 +235,13 @@ const { data: existingSession } = useAdminSessionQuery(sessionId, {
|
||||
enabled: () => !!sessionId.value,
|
||||
})
|
||||
|
||||
const collectionToContentType = (collectionName) => {
|
||||
if (collectionName === 'video') return 'video'
|
||||
if (collectionName === 'voice') return 'voice'
|
||||
if (collectionName === 'pdf') return 'text'
|
||||
return ''
|
||||
}
|
||||
|
||||
watch(
|
||||
existingSession,
|
||||
(session) => {
|
||||
@@ -277,13 +284,6 @@ const purposeForContentType = (contentType) => {
|
||||
return 'pdf'
|
||||
}
|
||||
|
||||
const collectionToContentType = (collectionName) => {
|
||||
if (collectionName === 'videos') return 'video'
|
||||
if (collectionName === 'voices') return 'voice'
|
||||
if (collectionName === 'pdf') return 'text'
|
||||
return ''
|
||||
}
|
||||
|
||||
const onContentSelect = async (files) => {
|
||||
const file = files?.[0]
|
||||
if (!file) return
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
</template>
|
||||
</TextField>
|
||||
<SelectField
|
||||
v-model="form.roleId"
|
||||
name="roleId"
|
||||
v-model="form.role"
|
||||
name="role"
|
||||
label="نقش کاربر"
|
||||
:options="roleOptions"
|
||||
:options="ROLES"
|
||||
option-label="label"
|
||||
option-value="id"
|
||||
option-value="value"
|
||||
/>
|
||||
<TextField
|
||||
v-model="form.nationalCode"
|
||||
@@ -66,18 +66,17 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ROLE_LABELS } from '@/enums'
|
||||
import { ROLES } from '@/enums'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import SvgIcon from '@/components/icons/SvgIcon.vue'
|
||||
import TextField from '@/components/form/TextField.vue'
|
||||
import CircleButton from '@/components/CircleButton.vue'
|
||||
import SelectField from '@/components/form/SelectField.vue'
|
||||
import { useAdminRolesListQuery } from '@/services/query/admin-users'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({ name: '', nationalCode: '', phoneNumber: '', roleId: '' }),
|
||||
default: () => ({ name: '', nationalCode: '', phoneNumber: '', role: '' }),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -92,11 +91,6 @@ watch(
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const { data: roles = ref([]) } = useAdminRolesListQuery()
|
||||
const roleOptions = computed(() =>
|
||||
(roles.value ?? []).map((r) => ({ ...r, label: ROLE_LABELS[r.name] || r.name }))
|
||||
)
|
||||
|
||||
const hasFilters = computed(() =>
|
||||
Object.values(form.value).some((v) => v !== '' && v !== null && v !== undefined)
|
||||
)
|
||||
@@ -107,7 +101,7 @@ const onSubmit = () => {
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
form.value = { name: '', nationalCode: '', phoneNumber: '', roleId: '' }
|
||||
form.value = { name: '', nationalCode: '', phoneNumber: '', role: '' }
|
||||
emit('update:modelValue', { ...form.value })
|
||||
emit('reset')
|
||||
}
|
||||
|
||||
@@ -66,11 +66,11 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ROLES } from '@/enums'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useQueryClient } from '@tanstack/vue-query'
|
||||
import SvgIcon from '@/components/icons/SvgIcon.vue'
|
||||
import BaseButton from '@/components/BaseButton.vue'
|
||||
import { CHANGEABLE_ROLES, ROLE_LABELS } from '@/enums'
|
||||
import DropdownMenu from '@/components/DropdownMenu.vue'
|
||||
import { adminUsersKeys, useUpdateAdminUserRoleMutation } from '@/services/query/admin-users'
|
||||
|
||||
@@ -99,7 +99,8 @@ const roleName = (user) => {
|
||||
|
||||
const roleLabel = (user) => {
|
||||
const name = roleName(user)
|
||||
return name ? ROLE_LABELS[name] || name : ''
|
||||
if (!name) return ''
|
||||
return ROLES.find((r) => r.value === name)?.label || name
|
||||
}
|
||||
|
||||
const menuOpen = ref(false)
|
||||
@@ -146,11 +147,11 @@ const onChangeRole = (targetRoleName) => {
|
||||
|
||||
const roleMenuItems = computed(() => {
|
||||
const current = activeUser.value ? roleName(activeUser.value) : null
|
||||
return CHANGEABLE_ROLES.filter((r) => r !== current).map((r) => ({
|
||||
key: `role-${r}`,
|
||||
label: `تغییر به ${ROLE_LABELS[r]}`,
|
||||
return ROLES.filter((r) => r.changeable && r.value !== current).map((r) => ({
|
||||
key: `role-${r.value}`,
|
||||
label: `تغییر به ${r.label}`,
|
||||
icon: 'arrows-clockwise',
|
||||
onClick: () => onChangeRole(r),
|
||||
onClick: () => onChangeRole(r.value),
|
||||
}))
|
||||
})
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ const router = useRouter()
|
||||
const queryClient = useQueryClient()
|
||||
const { openModal, isModal } = useModal()
|
||||
|
||||
const filters = ref({ name: '', nationalCode: '', phoneNumber: '', roleId: '' })
|
||||
const filters = ref({ name: '', nationalCode: '', phoneNumber: '', role: '' })
|
||||
const { pagination, setPage, reset: resetPagination } = usePagination({ page: 1, perPage: 10 })
|
||||
|
||||
const { data, isLoading } = useAdminUsersListQuery(filters, pagination, {
|
||||
|
||||
Reference in New Issue
Block a user