fix: admin users
Deploy banu-front / deploy (push) Failing after 16s

This commit is contained in:
sajjadtalkhabi
2026-06-10 20:55:54 +03:30
parent b8ccbcc030
commit b9fa65b84f
51 changed files with 373 additions and 255 deletions
+6 -5
View File
@@ -23,7 +23,11 @@
:loading="!!data?.confirmLoading"
custom-class="confirm-modal__btn confirm-modal__btn--confirm"
@click="confirm(data)"
/>
>
<template #appendIcon>
<SvgIcon name="arrow-left" :size="18" color="#fff" />
</template>
</BaseButton>
</div>
</div>
</template>
@@ -31,6 +35,7 @@
</template>
<script setup>
import SvgIcon from './icons/SvgIcon.vue'
import { useModalStore } from '@/store/modal'
import BasicModal from '@/components/BasicModal.vue'
import BaseButton from '@/components/BaseButton.vue'
@@ -82,10 +87,6 @@ const cancel = (data) => {
&__btn {
min-width: 100px;
padding: 0.5rem 1.5rem;
&--confirm {
padding-inline: 1.5rem 2.5rem;
}
}
}
</style>
+4 -3
View File
@@ -85,7 +85,7 @@ const props = defineProps({
name: { type: String, default: '' },
label: { type: String, default: '' },
placeholder: { type: String, default: 'انتخاب کنید' },
options: { type: Array, required: true },
options: { type: Array, default: () => [] },
optionLabel: { type: String, default: 'name' },
optionValue: { type: String, default: 'id' },
multiple: { type: Boolean, default: false },
@@ -118,13 +118,14 @@ const isSelected = (opt) => {
const selectedLabel = computed(() => {
const value = props.modelValue
if (value === undefined || value === null || value === '') return props.placeholder
const options = props.options ?? []
if (props.multiple && Array.isArray(value)) {
const selected = props.options.filter((o) => value.includes(o[props.optionValue]))
const selected = options.filter((o) => value.includes(o[props.optionValue]))
if (selected.length === 0) return props.placeholder
if (selected.length <= 2) return selected.map((s) => s[props.optionLabel]).join('، ')
return `${selected.length} مورد انتخاب شده`
}
const found = props.options.find((o) => o[props.optionValue] === value)
const found = options.find((o) => o[props.optionValue] === value)
return found ? found[props.optionLabel] : props.placeholder
})
+1 -1
View File
@@ -88,9 +88,9 @@
<script setup>
import { toast } from 'vue3-toastify'
import useModal from '@/composables/useModal'
import { PermissionModal } from '@/features/auth'
import SvgIcon from '@/components/icons/SvgIcon.vue'
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import PermissionModal from '@/features/auth/components/studentRegister/PermissionModal.vue'
const props = defineProps({
modelValue: { type: [Object, File, String, null], default: null },
-3
View File
@@ -13,9 +13,6 @@ export type IconName =
| 'caret-right'
| 'chat'
| 'chat-centered-dots'
| 'chat-slash'
| 'chat-text'
| 'chats'
| 'check'
| 'check-square'
| 'close'
+8 -2
View File
@@ -38,11 +38,17 @@ export const STUDENT_REGISTRATION = Object.freeze({
export const STUDENT_REGISTRATION_ORDER = Object.freeze(Object.values(STUDENT_REGISTRATION))
// User lifecycle states (Set User Status endpoint):
// pending just registered
// verified phone OTP-confirmed
// approved full access
// blocked no access (existing tokens revoked)
export const USER_STATUS = Object.freeze({
approved: 'تأیید شده',
pending: 'در انتظار',
rejected: 'رد شده',
verified: 'تأیید تلفن',
approved: 'تأیید شده',
blocked: 'مسدود',
rejected: 'رد شده',
})
export const MARITAL_STATUS = Object.freeze({
+8
View File
@@ -0,0 +1,8 @@
export { default as AddAssignmentModal } from './components/modals/AddAssignmentModal.vue'
export { default as AssignmentDetailsModal } from './components/modals/AssignmentDetailsModal.vue'
export { default as AssignmentItem } from './components/AssignmentItem.vue'
export { default as AssignmentSubmissionDetailsModal } from './components/modals/AssignmentSubmissionDetailsModal.vue'
export { default as AssignmentSubmissionsModal } from './components/modals/AssignmentSubmissionsModal.vue'
export { default as AssignmentsFilters } from './components/AssignmentsFilters.vue'
export * from './schema'
@@ -0,0 +1,3 @@
export { default as ConsultationDetailsModal } from './components/modals/ConsultationDetailsModal.vue'
export { default as ConsultationItem } from './components/ConsultationItem.vue'
export { default as ConsultationsFilters } from './components/ConsultationsFilters.vue'
+8
View File
@@ -0,0 +1,8 @@
export { default as AddOfferedCourseModal } from './components/modals/AddOfferedCourseModal.vue'
export { default as AddSessionToCourseModal } from './components/modals/AddSessionToCourseModal.vue'
export { default as CourseDetailsModal } from './components/modals/CourseDetailsModal.vue'
export { default as CourseItem } from './components/CourseItem.vue'
export { default as CourseSessionItem } from './components/CourseSessionItem.vue'
export { default as CoursesFilters } from './components/CoursesFilters.vue'
export * from './schema'
+8
View File
@@ -0,0 +1,8 @@
export { default as ExamDetailsModal } from './components/modals/ExamDetailsModal.vue'
export { default as ExamItem } from './components/ExamItem.vue'
export { default as ExamParticipantDetailsModal } from './components/modals/ExamParticipantDetailsModal.vue'
export { default as ExamParticipantsModal } from './components/modals/ExamParticipantsModal.vue'
export { default as ExamQuestionBuilder } from './components/ExamQuestionBuilder.vue'
export { default as ExamsFilters } from './components/ExamsFilters.vue'
export * from './schema'
+3
View File
@@ -0,0 +1,3 @@
export { default as TicketDetailsModal } from './components/modals/TicketDetailsModal.vue'
export { default as TicketItem } from './components/TicketItem.vue'
export { default as TicketsFilters } from './components/TicketsFilters.vue'
+4
View File
@@ -0,0 +1,4 @@
export { default as SendMessageModal } from './components/modals/SendMessageModal.vue'
export { default as ServiceDetailsModal } from './components/modals/ServiceDetailsModal.vue'
export { default as ServiceItem } from './components/ServiceItem.vue'
export { default as ServicesFilters } from './components/ServicesFilters.vue'
+6
View File
@@ -0,0 +1,6 @@
export { default as SessionAttendanceModal } from './components/modals/SessionAttendanceModal.vue'
export { default as SessionDetailsModal } from './components/modals/SessionDetailsModal.vue'
export { default as SessionItem } from './components/SessionItem.vue'
export { default as SessionsFilters } from './components/SessionsFilters.vue'
export * from './schema'
+8
View File
@@ -0,0 +1,8 @@
export { default as AddTermStudentModal } from './components/modals/AddTermStudentModal.vue'
export { default as AttachCourseToTermModal } from './components/modals/AttachCourseToTermModal.vue'
export { default as CourseItem } from './components/CourseItem.vue'
export { default as TermDetailsModal } from './components/modals/TermDetailsModal.vue'
export { default as TermItem } from './components/TermItem.vue'
export { default as TermsFilters } from './components/TermsFilters.vue'
export * from './schema'
@@ -65,10 +65,9 @@ import BoxedIconTitleBlock from '@/components/blocks/BoxedIconTitleBlock.vue'
import SkeletonLoaderBlock from '@/components/blocks/SkeletonLoaderBlock.vue'
import TermsFilters from '@/features/admin/terms/components/TermsFilters.vue'
import SimpleTitleIconBlock from '@/components/blocks/SimpleTitleIconBlock.vue'
import { CourseDetailsModal, AddOfferedCourseModal } from '@/features/admin/courses'
import TermDetailsModal from '@/features/admin/terms/components/modals/TermDetailsModal.vue'
import AddTermStudentModal from '@/features/admin/terms/components/modals/AddTermStudentModal.vue'
import CourseDetailsModal from '@/features/admin/courses/components/modals/CourseDetailsModal.vue'
import AddOfferedCourseModal from '@/features/admin/courses/components/modals/AddOfferedCourseModal.vue'
import AttachCourseToTermModal from '@/features/admin/terms/components/modals/AttachCourseToTermModal.vue'
import {
adminTermsKeys,
@@ -26,8 +26,8 @@
</template>
</TextField>
<TextField
v-model="form.phoneNumber"
name="phoneNumber"
v-model="form.phone"
name="phone"
label="شماره همراه کاربر"
inputmode="numeric"
:convert-digits="true"
@@ -76,7 +76,7 @@ import SelectField from '@/components/form/SelectField.vue'
const props = defineProps({
modelValue: {
type: Object,
default: () => ({ name: '', nationalCode: '', phoneNumber: '', role: '' }),
default: () => ({ name: '', nationalCode: '', phone: '', role: '' }),
},
})
@@ -101,7 +101,7 @@ const onSubmit = () => {
}
const onReset = () => {
form.value = { name: '', nationalCode: '', phoneNumber: '', role: '' }
form.value = { name: '', nationalCode: '', phone: '', role: '' }
emit('update:modelValue', { ...form.value })
emit('reset')
}
@@ -2,18 +2,22 @@
<div class="users-table">
<div v-for="user in users" :key="user.id" class="users-table__row">
<div class="users-table__avatar">
<img v-if="user.avatarUrl" :src="user.avatarUrl" :alt="fullName(user)" />
<img v-if="user.avatarUrl" :src="user.avatarUrl" :alt="user?.name" />
<SvgIcon v-else name="user" :size="36" color="#cbd5e1" />
</div>
<div class="users-table__info">
<p class="users-table__name">{{ fullName(user) || '—' }}</p>
<p class="users-table__name">{{ user?.name || '—' }}</p>
<div class="users-table__meta">
<span v-if="user.phoneNumber">{{ user.phoneNumber }}</span>
<span v-if="user.phone">{{ user.phone }}</span>
<span v-if="user.nationalCode" class="users-table__sep">|</span>
<span v-if="user.nationalCode">کد ملی: {{ user.nationalCode }}</span>
</div>
</div>
<Badge v-if="user.status === 'blocked'" variant="danger" size="sm" dot>
{{ USER_STATUS.blocked }}
</Badge>
<button
type="button"
class="users-table__role-badge"
@@ -66,8 +70,9 @@
</template>
<script setup>
import { ROLES } from '@/enums'
import { computed, ref } from 'vue'
import Badge from '@/components/Badge.vue'
import { ROLES, USER_STATUS } from '@/enums'
import { useQueryClient } from '@tanstack/vue-query'
import SvgIcon from '@/components/icons/SvgIcon.vue'
import BaseButton from '@/components/BaseButton.vue'
@@ -80,17 +85,7 @@ defineProps({
const queryClient = useQueryClient()
const emit = defineEmits([
'edit',
'delete',
'block',
'show-details',
'show-assignments',
'show-exams',
'export-pdf',
])
const fullName = (user) => `${user.firstName || ''} ${user.lastName || ''}`.trim()
const emit = defineEmits(['edit', 'delete', 'block', 'unblock', 'show-details'])
const roleName = (user) => {
const first = user.roles?.[0]
@@ -155,45 +150,38 @@ const roleMenuItems = computed(() => {
}))
})
const menuItems = computed(() => [
{
key: 'edit',
label: 'ویرایش',
icon: 'pencil',
onClick: () => emit('edit', activeUser.value),
},
{
key: 'delete',
label: 'حذف',
icon: 'trash',
onClick: () => emit('delete', activeUser.value),
},
{
key: 'block',
label: 'مسدود کردن کاربر',
icon: 'close',
danger: true,
onClick: () => emit('block', activeUser.value),
},
{
key: 'show-assignments',
label: 'جزئیات تکلیف',
icon: 'file',
onClick: () => emit('show-assignments', activeUser.value),
},
{
key: 'show-exams',
label: 'جزئیات آزمون',
icon: 'file',
onClick: () => emit('show-exams', activeUser.value),
},
{
key: 'export-pdf',
label: 'خروجی PDF',
icon: 'upload',
onClick: () => emit('export-pdf', activeUser.value),
},
])
const menuItems = computed(() => {
const user = activeUser.value
const isBlocked = user?.status === 'blocked'
return [
{
key: 'edit',
label: 'ویرایش',
icon: 'pencil',
onClick: () => emit('edit', user),
},
{
key: 'delete',
label: 'حذف',
icon: 'trash',
onClick: () => emit('delete', user),
},
isBlocked
? {
key: 'unblock',
label: 'رفع مسدودیت کاربر',
icon: 'check',
onClick: () => emit('unblock', user),
}
: {
key: 'block',
label: 'مسدود کردن کاربر',
icon: 'close',
danger: true,
onClick: () => emit('block', user),
},
]
})
</script>
<style lang="scss" scoped>
@@ -40,11 +40,14 @@
<div class="user-details__grid">
<LineInfoBlock
title="تاریخ تولد"
:numeric-desc="user.faBirthDate || formatJalaaliDate(user.birthDate) || ''"
:numeric-desc="formatJalaaliDate(user.birthday) || ''"
/>
<LineInfoBlock title="شماره تماس" :numeric-desc="user.phoneNumber || ''" />
<LineInfoBlock title="شماره تماس" :numeric-desc="user.phone || ''" />
<LineInfoBlock title="کد ملی" :numeric-desc="user.nationalCode || ''" />
<LineInfoBlock title="وضعیت تاهل" :desc="MARITAL_STATUS[user.maritalStatus] || '—'" />
<LineInfoBlock
title="وضعیت تاهل"
:desc="MARITAL_STATUS[user.marriageStatus] || '—'"
/>
<LineInfoBlock title="جنسیت" :desc="GENDER[user.gender] || '—'" />
<LineInfoBlock title="استان" :desc="user?.province?.name || '—'" />
<LineInfoBlock title="شهر" :desc="user?.city?.name || '—'" />
@@ -122,9 +125,7 @@ const { data: user, isLoading } = useAdminUserQuery(userId, {
enabled: () => !!userId.value,
})
const fullName = computed(() =>
`${user.value?.firstName || ''} ${user.value?.lastName || ''}`.trim()
)
const fullName = computed(() => `${user.value?.name || ''}`.trim())
const roleName = computed(() => {
const first = user.value?.roles?.[0]
+12
View File
@@ -0,0 +1,12 @@
export { default as RejectReasonDetailsModal } from './components/verifications/modals/RejectReasonDetailsModal.vue'
export { default as RejectReasonModal } from './components/verifications/modals/RejectReasonModal.vue'
export { default as UserAssignmentsModal } from './components/modals/UserAssignmentsModal.vue'
export { default as UserDetailsModal } from './components/modals/UserDetailsModal.vue'
export { default as UserExamsModal } from './components/modals/UserExamsModal.vue'
export { default as UserVerificationDetailsModal } from './components/verifications/modals/UserVerificationDetailsModal.vue'
export { default as UsersFilters } from './components/UsersFilters.vue'
export { default as UsersTable } from './components/UsersTable.vue'
export { default as VerificationItem } from './components/verifications/VerificationItem.vue'
export { default as VerificationsFilters } from './components/verifications/VerificationsFilters.vue'
export * from './schema'
+73 -68
View File
@@ -29,36 +29,23 @@
<div class="user-form__row">
<div class="user-form__cell user-form__cell--third">
<SelectField
v-model="form.roleId"
name="roleId"
v-model="form.role"
name="role"
label="نقش"
:options="roleOptions"
:options="ROLES"
option-label="label"
option-value="id"
:error="errors.roleId"
@change="validateAt('roleId', form.roleId)"
option-value="value"
:error="errors.role"
@change="validateAt('role', form.role)"
/>
</div>
<div class="user-form__cell user-form__cell--third">
<TextField
v-model="form.firstName"
name="firstName"
label="نام"
:error="errors.firstName"
@blur="validateAt('firstName', form.firstName)"
>
<template #appendIcon>
<SvgIcon name="user" :size="20" color="var(--color-thd-gray)" />
</template>
</TextField>
</div>
<div class="user-form__cell user-form__cell--third">
<TextField
v-model="form.lastName"
name="lastName"
label="نام خانوادگی"
:error="errors.lastName"
@blur="validateAt('lastName', form.lastName)"
v-model="form.name"
name="name"
label="نام و نام خانوادگی"
:error="errors.name"
@blur="validateAt('name', form.name)"
>
<template #appendIcon>
<SvgIcon name="user" :size="20" color="var(--color-thd-gray)" />
@@ -67,11 +54,11 @@
</div>
<div class="user-form__cell user-form__cell--third">
<DatePickerField
v-model="form.birthDate"
name="birthDate"
v-model="form.birthday"
name="birthday"
label="تاریخ تولد"
:max="todayIso"
:error="errors.birthDate"
:error="errors.birthday"
/>
</div>
<div class="user-form__cell user-form__cell--third">
@@ -125,13 +112,13 @@
<div class="user-form__row">
<div class="user-form__cell user-form__cell--third">
<TextField
v-model="form.phoneNumber"
name="phoneNumber"
v-model="form.phone"
name="phone"
label="شماره تلفن همراه"
inputmode="numeric"
:convert-digits="true"
:error="errors.phoneNumber"
@blur="validateAt('phoneNumber', form.phoneNumber)"
:error="errors.phone"
@blur="validateAt('phone', form.phone)"
>
<template #appendIcon>
<SvgIcon name="phone" :size="20" color="var(--color-thd-gray)" />
@@ -226,10 +213,10 @@ import { useQueryClient } from '@tanstack/vue-query'
import BaseButton from '@/components/BaseButton.vue'
import SvgIcon from '@/components/icons/SvgIcon.vue'
import TextField from '@/components/form/TextField.vue'
import { GENDER, MARITAL_STATUS, ROLES } from '@/enums'
import SelectField from '@/components/form/SelectField.vue'
import LineTitleBlock from '@/components/LineTitleBlock.vue'
import ImageCropper from '@/components/form/ImageCropper.vue'
import { GENDER, MARITAL_STATUS, ROLE_LABELS } from '@/enums'
import { objectToFormData } from '@/utils/object-to-formdata'
import { useUploadMediaMutation } from '@/services/query/auth'
import TextareaField from '@/components/form/TextareaField.vue'
@@ -242,7 +229,6 @@ import { useGetCitiesOfProvinceQuery, useGetProvincesQuery } from '@/services/qu
import {
adminUsersKeys,
useAddAdminUserMutation,
useAdminRolesListQuery,
useAdminUserQuery,
useUpdateAdminUserMutation,
} from '@/services/query/admin-users'
@@ -269,10 +255,6 @@ const maritalStatusOptions = Object.entries(MARITAL_STATUS).map(([value, label])
}))
const genderOptions = Object.entries(GENDER).map(([value, label]) => ({ value, label }))
const { data: roles = ref([]) } = useAdminRolesListQuery()
const roleOptions = computed(() =>
(roles.value ?? []).map((r) => ({ ...r, label: ROLE_LABELS[r.name] || r.name }))
)
const { data: provinces = ref([]) } = useGetProvincesQuery()
const provinceIdRef = ref(null)
@@ -283,15 +265,14 @@ const { data: cities = ref([]) } = useGetCitiesOfProvinceQuery(provinceIdRef, {
const initialNationalCode = ref('')
const form = ref({
roleId: '',
firstName: '',
lastName: '',
birthDate: '',
role: '',
name: '',
birthday: '',
nationalCode: '',
maritalStatus: '',
gender: '',
bio: '',
phoneNumber: '',
phone: '',
provinceId: '',
cityId: '',
address: '',
@@ -322,31 +303,34 @@ const { data: existingUser } = useAdminUserQuery(userId, {
enabled: () => !!userId.value,
})
watch(existingUser, (user) => {
if (!user) return
const firstRole = Array.isArray(user.roles) ? user.roles[0] : null
const firstRoleId = typeof firstRole === 'object' ? firstRole?.id : null
form.value = {
...form.value,
roleId: user.roleId || user.profile?.roleId || firstRoleId || '',
firstName: user.firstName || '',
lastName: user.lastName || '',
birthDate: user.profile?.birthDate || '',
nationalCode: user.nationalCode || '',
maritalStatus: user.profile?.maritalStatus || '',
gender: user.profile?.gender || '',
bio: user.profile?.bio || '',
phoneNumber: user.phoneNumber || '',
provinceId: user?.province?.id || '',
cityId: user?.city?.id || '',
address: user?.address || '',
avatarId: user.profile?.avatarId || null,
}
initialNationalCode.value = user.nationalCode || ''
if (user.avatarUrl) {
avatar.value = { url: user.avatarUrl }
}
})
watch(
existingUser,
(user) => {
if (!user) return
const firstRole = Array.isArray(user.roles) ? user.roles[0] : null
const roleValue = typeof firstRole === 'object' ? firstRole?.name : firstRole
form.value = {
...form.value,
role: roleValue || '',
name: user.name || `${user.firstName || ''} ${user.lastName || ''}`.trim(),
birthday: user.birthday || '',
nationalCode: user.nationalCode || '',
maritalStatus: user.marriageStatus ?? user.profile?.maritalStatus ?? '',
gender: user.gender ?? user.profile?.gender ?? '',
bio: user.bio ?? user.profile?.bio ?? '',
phone: user.phone || '',
provinceId: user?.province?.id || '',
cityId: user?.city?.id || '',
address: typeof user.address === 'string' ? user.address : user.address?.address || '',
avatarId: user.avatarId ?? user.profile?.avatarId ?? null,
}
initialNationalCode.value = user.nationalCode || ''
if (user.avatarUrl) {
avatar.value = { url: user.avatarUrl }
}
},
{ immediate: true }
)
const uploadMutation = useUploadMediaMutation()
@@ -369,11 +353,32 @@ const updateMutation = useUpdateAdminUserMutation()
const submitting = computed(() => addMutation.isPending.value || updateMutation.isPending.value)
// Maps the form to the backend Update User contract:
// { name, roles, province_id, city_id, marriage_status, birthday, gender,
// address, national_code } (snake-case conversion happens in the http layer),
// plus phone/bio/avatar that the form also edits.
const buildUpdatePayload = () => {
return {
name: form.value.name,
roles: form.value.role ? [form.value.role] : [],
provinceId: form.value.provinceId || null,
cityId: form.value.cityId || null,
marriageStatus: form.value.maritalStatus || null,
birthday: form.value.birthday || null,
gender: form.value.gender || null,
address: form.value.address || '',
nationalCode: form.value.nationalCode,
phone: form.value.phone,
bio: form.value.bio || '',
avatarId: form.value.avatarId || null,
}
}
const onSubmit = async () => {
const { isValid, payload } = await validate(form.value)
if (!isValid) return
if (isEditMode.value) {
await updateMutation.mutateAsync({ id: userId.value, payload })
await updateMutation.mutateAsync({ id: userId.value, payload: buildUpdatePayload() })
} else {
await addMutation.mutateAsync(payload)
}
@@ -31,17 +31,15 @@
:users="users"
@edit="onEdit"
@delete="onAskDelete"
@block="onAskBlock"
@unblock="onAskUnblock"
@show-details="onShowDetails"
@show-assignments="onShowAssignments"
@show-exams="onShowExams"
/>
<NoItems v-else title="متاسفیم" desc="کاربری برای نمایش وجود ندارد." />
<PaginationBlock :pagination="paginationMeta" @update:page="setPage" />
<UserDetailsModal v-if="isModal('UserDetailsModal')" />
<UserAssignmentsModal v-if="isModal('UserAssignmentsModal')" />
<UserExamsModal v-if="isModal('UserExamsModal')" />
</div>
</template>
@@ -60,12 +58,11 @@ import BoxedIconTitleBlock from '@/components/blocks/BoxedIconTitleBlock.vue'
import SkeletonLoaderBlock from '@/components/blocks/SkeletonLoaderBlock.vue'
import UsersFilters from '@/features/admin/users/components/UsersFilters.vue'
import SimpleTitleIconBlock from '@/components/blocks/SimpleTitleIconBlock.vue'
import UserExamsModal from '@/features/admin/users/components/modals/UserExamsModal.vue'
import UserDetailsModal from '@/features/admin/users/components/modals/UserDetailsModal.vue'
import UserAssignmentsModal from '@/features/admin/users/components/modals/UserAssignmentsModal.vue'
import {
adminUsersKeys,
useAdminUsersListQuery,
useChangeAdminUserStatusMutation,
useDeleteAdminUserMutation,
} from '@/services/query/admin-users'
@@ -73,7 +70,7 @@ const router = useRouter()
const queryClient = useQueryClient()
const { openModal, isModal } = useModal()
const filters = ref({ name: '', nationalCode: '', phoneNumber: '', role: '' })
const filters = ref({ name: '', nationalCode: '', phone: '', role: '' })
const { pagination, setPage, reset: resetPagination } = usePagination({ page: 1, perPage: 10 })
const { data, isLoading } = useAdminUsersListQuery(filters, pagination, {
@@ -99,22 +96,38 @@ const onEdit = (user) => {
}
const onShowDetails = (user) => openModal('UserDetailsModal', { id: user.id })
const onShowAssignments = (user) => openModal('UserAssignmentsModal', { id: user.id })
const onShowExams = (user) => openModal('UserExamsModal', { id: user.id })
const invalidateUsers = () => queryClient.invalidateQueries({ queryKey: adminUsersKeys.all })
const userName = (user) =>
user.name || `${user.firstName || ''} ${user.lastName || ''}`.trim() || user.phone
const deleteMutation = useDeleteAdminUserMutation()
const onAskDelete = (user) => {
const name = `${user.firstName || ''} ${user.lastName || ''}`.trim() || user.phoneNumber
openModal('ConfirmModal', {
title: 'حذف کاربر',
message: `آیا از حذف <strong>${name}</strong> مطمئن هستید؟`,
onConfirm: () =>
deleteMutation.mutate(user.id, {
onSuccess: () => queryClient.invalidateQueries({ queryKey: adminUsersKeys.all }),
}),
message: `آیا از حذف <strong>${userName(user)}</strong> مطمئن هستید؟`,
onConfirm: () => deleteMutation.mutate(user.id, { onSuccess: invalidateUsers }),
})
}
const statusMutation = useChangeAdminUserStatusMutation()
const askStatusChange = (user, { status, title, verb }) => {
openModal('ConfirmModal', {
title,
message: `آیا از ${verb} <strong>${userName(user)}</strong> مطمئن هستید؟`,
onConfirm: () =>
statusMutation.mutate({ id: user.id, payload: { status } }, { onSuccess: invalidateUsers }),
})
}
const onAskBlock = (user) =>
askStatusChange(user, { status: 'blocked', title: 'مسدود کردن کاربر', verb: 'مسدود کردن' })
const onAskUnblock = (user) =>
askStatusChange(user, { status: 'approved', title: 'رفع مسدودیت کاربر', verb: 'رفع مسدودیت' })
</script>
<style lang="scss" scoped>
+4 -5
View File
@@ -16,12 +16,11 @@ export const rejectionReasonSchema = object().shape({
})
const baseUserShape = {
roleId: string().required(),
firstName: string().required().min(2),
lastName: string().required().min(2),
phoneNumber: phoneNumberRule,
role: string().required(),
name: string().required().min(3),
phone: phoneNumberRule,
nationalCode: nationalCodeRule,
birthDate: string().nullable().notRequired(),
birthday: string().nullable().notRequired(),
maritalStatus: string().nullable().notRequired(),
gender: string().nullable().notRequired(),
bio: string().nullable().notRequired(),
+6 -6
View File
@@ -4,13 +4,13 @@
<AuthHeading title="ورود" subtitle="در این قسمت اطلاعات خود را وارد کنید." />
<TextField
v-model="form.phoneNumber"
name="phoneNumber"
v-model="form.phone"
name="phone"
label="شماره تلفن همراه"
inputmode="numeric"
:convert-digits="true"
:error="errors.phoneNumber"
@blur="validateAt('phoneNumber', form.phoneNumber)"
:error="errors.phone"
@blur="validateAt('phone', form.phone)"
>
<template #appendIcon>
<SvgIcon name="phone" :size="22" color="var(--color-thd-gray)" />
@@ -81,7 +81,7 @@ const emit = defineEmits(['next'])
const schema = loginSchema
const form = ref({ phoneNumber: '', password: '' })
const form = ref({ phone: '', password: '' })
const { validate, validateAt, errors } = useYup(schema)
const loginMutation = useLoginMutation()
@@ -92,7 +92,7 @@ const onSubmit = async () => {
const { isValid, payload } = await validate(form.value)
if (!isValid) return
const response = await loginMutation.mutateAsync({
phone: payload.phoneNumber,
phone: payload.phone,
password: payload.password,
deviceName: 'web',
})
@@ -11,13 +11,13 @@
/>
<TextField
v-model="form.phoneNumber"
name="phoneNumber"
v-model="form.phone"
name="phone"
label="شماره تلفن همراه"
inputmode="numeric"
:convert-digits="true"
:error="errors.phoneNumber"
@blur="validateAt('phoneNumber', form.phoneNumber)"
:error="errors.phone"
@blur="validateAt('phone', form.phone)"
>
<template #appendIcon>
<SvgIcon name="phone" :size="22" color="var(--color-thd-gray)" />
@@ -67,7 +67,7 @@ const emit = defineEmits(['next', 'back'])
const isForgotPassword = computed(() => props.mode === 'forgotPassword')
const schema = loginWithCodeSchema
const form = ref({ phoneNumber: '' })
const form = ref({ phone: '' })
const { validate, validateAt, errors } = useYup(schema)
const { savePhoneNumber } = useAuth()
@@ -86,8 +86,8 @@ const onSubmit = async () => {
} else {
await loginCodeMutation.mutateAsync(payload)
}
savePhoneNumber(payload.phoneNumber)
emit('next', { phoneNumber: payload.phoneNumber, mode: props.mode })
savePhoneNumber(payload.phone)
emit('next', { phone: payload.phone, mode: props.mode })
}
</script>
+8 -8
View File
@@ -28,13 +28,13 @@
</TextField>
<TextField
v-model="form.phoneNumber"
name="phoneNumber"
v-model="form.phone"
name="phone"
label="شماره موبایل"
inputmode="numeric"
:convert-digits="true"
:error="errors.phoneNumber"
@blur="validateAt('phoneNumber', form.phoneNumber)"
:error="errors.phone"
@blur="validateAt('phone', form.phone)"
>
<template #appendIcon>
<SvgIcon name="phone" :size="22" color="var(--color-thd-gray)" />
@@ -102,7 +102,7 @@ const schema = signupSchema
const form = ref({
firstName: '',
lastName: '',
phoneNumber: '',
phone: '',
password: '',
passwordConfirmation: '',
})
@@ -117,12 +117,12 @@ const onSubmit = async () => {
const name = [payload.firstName, payload.lastName].filter(Boolean).join(' ').trim()
await registerMutation.mutateAsync({
name,
phone: payload.phoneNumber,
phone: payload.phone,
password: payload.password,
passwordConfirmation: payload.passwordConfirmation,
})
savePhoneNumber(payload.phoneNumber)
emit('next', { phoneNumber: payload.phoneNumber })
savePhoneNumber(payload.phone)
emit('next', { phone: payload.phone })
}
</script>
@@ -103,7 +103,7 @@ const resending = computed(
const onSubmit = async () => {
const { isValid, payload } = await validate(form.value)
if (!isValid) return
const body = { phoneNumber: props.phoneNumber, verifyCode: payload.verifyCode }
const body = { phone: props.phoneNumber, verifyCode: payload.verifyCode }
if (props.mode === 'loginWithCode') {
const response = await verifyLoginMutation.mutateAsync(body)
@@ -93,14 +93,14 @@
<div class="personal-info__row">
<div class="personal-info__cell personal-info__cell--full-sm">
<TextField
v-model="form.phoneNumber"
name="phoneNumber"
v-model="form.phone"
name="phone"
label="شماره تلفن همراه"
inputmode="numeric"
:convert-digits="true"
:disabled="!!initialPhoneNumber"
:error="errors.phoneNumber"
@blur="validateAt('phoneNumber', form.phoneNumber)"
:disabled="!!initialPhone"
:error="errors.phone"
@blur="validateAt('phone', form.phone)"
>
<template #appendIcon>
<SvgIcon name="phone" :size="20" color="var(--color-thd-gray)" />
@@ -206,9 +206,7 @@ const maxBirthDate = computed(() => {
}
})
const initialPhoneNumber = ref(
user.value?.phone || user.value?.phoneNumber || store.personal.phoneNumber || ''
)
const initialPhone = ref(user.value?.phone || store.personal.phone || '')
const form = ref({
firstName: store.personal.firstName || '',
@@ -217,7 +215,7 @@ const form = ref({
nationalCode: store.personal.nationalCode || '',
maritalStatus: store.personal.maritalStatus || '',
gender: store.personal.gender || '',
phoneNumber: initialPhoneNumber.value,
phone: initialPhone.value,
provinceId: store.personal.provinceId || null,
cityId: store.personal.cityId || null,
address: store.personal.address || '',
+26
View File
@@ -0,0 +1,26 @@
export { default as AuthCopyright } from './components/AuthCopyright.vue'
export { default as AuthHeading } from './components/AuthHeading.vue'
export { default as AuthShell } from './components/AuthShell.vue'
export { default as EducationalInformation } from './components/studentRegister/EducationalInformation.vue'
export { default as LoginForm } from './components/LoginForm.vue'
export { default as LoginWithVerifyCodeForm } from './components/LoginWithVerifyCodeForm.vue'
export { default as MediaRecorder } from './components/studentRegister/MediaRecorder.vue'
export { default as PatternSection } from './components/PatternSection.vue'
export { default as PermissionModal } from './components/studentRegister/PermissionModal.vue'
export { default as PersonalInformation } from './components/studentRegister/PersonalInformation.vue'
export { default as ProfessionalInformation } from './components/studentRegister/ProfessionalInformation.vue'
export { default as SideBarSteps } from './components/studentRegister/SideBarSteps.vue'
export { default as SignupForm } from './components/SignupForm.vue'
export { default as SkillAssessmentPart1 } from './components/studentRegister/SkillAssessmentPart1.vue'
export { default as SkillAssessmentPart2 } from './components/studentRegister/SkillAssessmentPart2.vue'
export { default as SkillAssessmentPart3 } from './components/studentRegister/SkillAssessmentPart3.vue'
export { default as SkillAssessmentPart4 } from './components/studentRegister/SkillAssessmentPart4.vue'
export { default as SkillAssessmentPart5 } from './components/studentRegister/SkillAssessmentPart5.vue'
export { default as SkillAssessmentPart6 } from './components/studentRegister/SkillAssessmentPart6.vue'
export { default as SkillAssessmentPart7 } from './components/studentRegister/SkillAssessmentPart7.vue'
export { default as SkillSelectStep } from './components/studentRegister/SkillSelectStep.vue'
export { default as StepActions } from './components/studentRegister/StepActions.vue'
export { default as SuccessModal } from './components/studentRegister/SuccessModal.vue'
export { default as VerifyCodeForm } from './components/VerifyCodeForm.vue'
export * from './schema'
@@ -5,10 +5,10 @@
<AuthHeading title="تغییر رمز عبور" subtitle="در این قسمت رمز عبور جدید خود را وارد کنید" />
<TextField
v-model="form.phoneNumber"
name="phoneNumber"
v-model="form.phone"
name="phone"
label="شماره موبایل"
:disabled="!!(user?.phone || user?.phoneNumber)"
:disabled="!!(user?.phone || user?.phone)"
>
<template #appendIcon>
<SvgIcon name="phone" :size="22" color="var(--color-thd-gray)" />
@@ -71,7 +71,7 @@ const redirectAfterLogin = useRedirectAfterLogin()
const schema = resetPasswordSchema
const form = ref({
phoneNumber: user.value?.phone || user.value?.phoneNumber || '',
phone: user.value?.phone || '',
password: '',
passwordConfirmation: '',
})
+3 -3
View File
@@ -4,18 +4,18 @@ import { phoneNumberRule } from '@/constants/rules/phoneNumberRule'
import { passwordConfirmationRule } from '@/constants/rules/passwordConfirmationRule'
export const loginSchema = object().shape({
phoneNumber: phoneNumberRule,
phone: phoneNumberRule,
password: string().required(),
})
export const loginWithCodeSchema = object().shape({
phoneNumber: phoneNumberRule,
phone: phoneNumberRule,
})
export const signupSchema = object().shape({
firstName: string().required().min(3),
lastName: string().required().min(3),
phoneNumber: phoneNumberRule,
phone: phoneNumberRule,
password: passwordRule,
passwordConfirmation: passwordConfirmationRule,
})
+1
View File
@@ -0,0 +1 @@
export { default as CounselorConsultationDetailsModal } from './components/modals/CounselorConsultationDetailsModal.vue'
@@ -50,8 +50,7 @@ import PaginationBlock from '@/components/blocks/PaginationBlock.vue'
import BoxedIconTitleBlock from '@/components/blocks/BoxedIconTitleBlock.vue'
import SkeletonLoaderBlock from '@/components/blocks/SkeletonLoaderBlock.vue'
import SimpleTitleIconBlock from '@/components/blocks/SimpleTitleIconBlock.vue'
import ConsultationItem from '@/features/admin/consultations/components/ConsultationItem.vue'
import ConsultationsFilters from '@/features/admin/consultations/components/ConsultationsFilters.vue'
import { ConsultationItem, ConsultationsFilters } from '@/features/admin/consultations'
import CounselorConsultationDetailsModal from '@/features/counselor/components/modals/CounselorConsultationDetailsModal.vue'
import {
counselorTicketsKeys,
@@ -62,7 +61,7 @@ import {
const { openModal, isModal } = useModal()
const queryClient = useQueryClient()
const filters = ref({ type: 'advise', userName: '', status: '', fromDate: '', toDate: '' })
const filters = ref({ userName: '', status: '', fromDate: '', toDate: '' })
const { pagination, setPage, reset: resetPagination } = usePagination({ page: 1, perPage: 10 })
const { data, isLoading } = useCounselorTicketsListQuery(filters, pagination, {
+4
View File
@@ -0,0 +1,4 @@
export { default as ItemDetailsModal } from './components/ItemDetailsModal.vue'
export { default as SampleModal } from './components/SampleModal.vue'
export * from './schema'
@@ -28,15 +28,15 @@
@blur="validateAt('fullName', form.fullName)"
/>
<TextField
v-model="form.phoneNumber"
name="phoneNumber"
v-model="form.phone"
name="phone"
label="شماره موبایل"
placeholder="09xxxxxxxxx"
inputmode="numeric"
:convert-digits="true"
:error="errors.phoneNumber"
:error="errors.phone"
:vibration="true"
@blur="validateAt('phoneNumber', form.phoneNumber)"
@blur="validateAt('phone', form.phone)"
/>
<TextField
v-model="form.nationalCode"
@@ -295,7 +295,7 @@ const onCitySearch = (q) => {
const form = ref({
fullName: '',
phoneNumber: '',
phone: '',
nationalCode: '',
disabled: 'فقط برای نمایش',
bio: '',
@@ -344,7 +344,7 @@ const onSubmit = async () => {
const reset = () => {
form.value = {
fullName: '',
phoneNumber: '',
phone: '',
disabled: 'فقط برای نمایش',
bio: '',
province: '',
@@ -0,0 +1 @@
export { default as CertificateItem } from './components/CertificateItem.vue'
@@ -0,0 +1,2 @@
export { default as ConsultantItem } from './components/ConsultantItem.vue'
export { default as RequestConsultantForm } from './components/RequestConsultantForm.vue'
+1
View File
@@ -0,0 +1 @@
export { default as StudentCourseItem } from './components/StudentCourseItem.vue'
+4
View File
@@ -0,0 +1,4 @@
export { default as StudentExamAttemptItem } from './components/StudentExamAttemptItem.vue'
export { default as StudentExamItem } from './components/StudentExamItem.vue'
export { default as StudentExamQuestion } from './components/StudentExamQuestion.vue'
export { default as StudentExamSummary } from './components/StudentExamSummary.vue'
+1
View File
@@ -0,0 +1 @@
export { default as StudentLessonItem } from './components/StudentLessonItem.vue'
@@ -98,13 +98,13 @@ import { useRoute, useRouter } from 'vue-router'
import SvgIcon from '@/components/icons/SvgIcon.vue'
import NoItems from '@/components/blocks/NoItems.vue'
import TabsBlock from '@/components/blocks/TabsBlock.vue'
import { StudentExamItem } from '@/features/student/exams'
import { StudentSessionItem } from '@/features/student/sessions'
import { useStudentCourseQuery } from '@/services/query/student-courses'
import { useStudentExamsListQuery } from '@/services/query/student-exams'
import BoxedIconTitleBlock from '@/components/blocks/BoxedIconTitleBlock.vue'
import SkeletonLoaderBlock from '@/components/blocks/SkeletonLoaderBlock.vue'
import { useStudentSessionsListQuery } from '@/services/query/student-sessions'
import StudentExamItem from '@/features/student/exams/components/StudentExamItem.vue'
import StudentSessionItem from '@/features/student/sessions/components/StudentSessionItem.vue'
const route = useRoute()
const router = useRouter()
+7
View File
@@ -0,0 +1,7 @@
export { default as DispatchDetailsModal } from './components/modals/DispatchDetailsModal.vue'
export { default as MissionaryDispatchItem } from './components/MissionaryDispatchItem.vue'
export { default as MissionaryNarrativeItem } from './components/MissionaryNarrativeItem.vue'
export { default as MissionaryRequestItem } from './components/MissionaryRequestItem.vue'
export { default as MissionaryUserCard } from './components/MissionaryUserCard.vue'
export { default as NarrativeDetailsModal } from './components/modals/NarrativeDetailsModal.vue'
export { default as RequestDetailsModal } from './components/modals/RequestDetailsModal.vue'
+1
View File
@@ -0,0 +1 @@
export * from './schema'
+3
View File
@@ -0,0 +1,3 @@
export { default as CreateServiceForm } from './components/CreateServiceForm.vue'
export { default as ServiceDetailsModal } from './components/modals/ServiceDetailsModal.vue'
export { default as ServiceItem } from './components/ServiceItem.vue'
+1
View File
@@ -0,0 +1 @@
export { default as StudentSessionItem } from './components/StudentSessionItem.vue'
+2
View File
@@ -0,0 +1,2 @@
export { default as StudentTermItem } from './components/StudentTermItem.vue'
export { default as StudentTermSummary } from './components/StudentTermSummary.vue'
@@ -41,13 +41,13 @@ import { computed, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import SvgIcon from '@/components/icons/SvgIcon.vue'
import NoItems from '@/components/blocks/NoItems.vue'
import { StudentLessonItem } from '@/features/student/lessons'
import { useStudentTermQuery } from '@/services/query/student-terms'
import { useStudentCoursesListQuery } from '@/services/query/student-courses'
import BoxedIconTitleBlock from '@/components/blocks/BoxedIconTitleBlock.vue'
import SkeletonLoaderBlock from '@/components/blocks/SkeletonLoaderBlock.vue'
import SimpleTitleIconBlock from '@/components/blocks/SimpleTitleIconBlock.vue'
import StudentTermSummary from '@/features/student/terms/components/StudentTermSummary.vue'
import StudentLessonItem from '@/features/student/lessons/components/StudentLessonItem.vue'
const route = useRoute()
const router = useRouter()
+4
View File
@@ -0,0 +1,4 @@
export { default as CreateTicketModal } from './components/modals/CreateTicketModal.vue'
export { default as StatusTile } from './components/StatusTile.vue'
export { default as TicketDetailsModal } from './components/modals/TicketDetailsModal.vue'
export { default as TicketItem } from './components/TicketItem.vue'
+1 -3
View File
@@ -14,8 +14,6 @@ export const apiUpdateAdminUserRole = (id, payload) =>
http.patch(buildUrl(endpoints.updateUserRole, { id }), payload)
export const apiChangeAdminUserStatus = (id, payload) =>
http.post(buildUrl(endpoints.changeUserStatus, { id }), payload)
http.patch(buildUrl(endpoints.changeUserStatus, { id }), payload)
export const apiDeleteAdminUser = (id) => http.delete(buildUrl(endpoints.deleteUser, { id }))
export const apiGetAdminRoles = () => http.get(endpoints.getRolesList)
-1
View File
@@ -56,7 +56,6 @@ export const endpoints = {
addNewUser: '/admin/users',
updateUser: '/admin/users/:id',
updateUserRole: '/admin/users/:id/role',
getRolesList: '/admin/roles',
changeUserStatus: '/admin/users/:id/status',
deleteUser: '/admin/users/:id',
+1 -1
View File
@@ -78,6 +78,7 @@ const makeUser = (i, overrides = {}) => {
avatarUrl: '',
avatarDownloadUrl: '',
createdAt: new Date(Date.now() - i * 86_400_000).toISOString(),
birthday: new Date(1990, i % 12, ((i * 3) % 27) + 1).toISOString(),
// --- ui-only (pending backend) ---
firstName,
@@ -94,7 +95,6 @@ const makeUser = (i, overrides = {}) => {
},
profile: {
bio: '',
birthDate: new Date(1990, i % 12, ((i * 3) % 27) + 1).toISOString(),
maritalStatus: i % 2 ? 'single' : 'married',
gender: i % 2 ? 'female' : 'male',
},
+28 -22
View File
@@ -15,7 +15,9 @@ import {
register('GET', endpoints.getApprovedUsers, ({ query }) => {
let list = filterItems(adminUsers, query, {
name: (item, v) =>
`${item.firstName} ${item.lastName}`.toLowerCase().includes(String(v).toLowerCase()),
(item.name || `${item.firstName} ${item.lastName}`)
.toLowerCase()
.includes(String(v).toLowerCase()),
nationalCode: 'includes',
phoneNumber: 'includes',
roleId: (item, v) => String(item.roleId) === String(v),
@@ -39,30 +41,32 @@ register('POST', endpoints.addNewUser, ({ data }) => {
const id = makeId()
const roleByName = (n) => roles.find((r) => r.name === n)
const role =
roles.find((r) => r.id === Number(data.roleId)) ||
(data.role ? roleByName(data.role) : null) ||
(Array.isArray(data.roles) && data.roles[0] ? roleByName(data.roles[0]) : null) ||
roles.find((r) => r.id === Number(data.roleId)) ||
null
const province = data.provinceId ? { id: data.provinceId, name: '' } : null
const city = data.cityId ? { id: data.cityId, name: '' } : null
const firstName = data.firstName || ''
const lastName = data.lastName || ''
const fullName = `${firstName} ${lastName}`.trim()
const name = data.name || `${data.firstName || ''} ${data.lastName || ''}`.trim()
const [firstName, ...rest] = name.split(' ')
const lastName = rest.join(' ')
const phoneNumber = data.phoneNumber || ''
const user = {
// --- spec ---
id,
name: data.name || fullName,
name,
email: data.email || '',
phone: data.phone || (phoneNumber ? phoneNumber.replace(/^0/, '+98') : null),
roles: Array.isArray(data.roles) ? data.roles : role ? [role.name] : [],
avatarUrl: '',
avatarDownloadUrl: null,
createdAt: isoNow(),
birthday: data.birthday || null,
// --- ui-only ---
firstName,
lastName,
fullName,
fullName: name,
phoneNumber,
nationalCode: data.nationalCode || '',
status: 'approved',
@@ -70,8 +74,7 @@ register('POST', endpoints.addNewUser, ({ data }) => {
address: { address: data.address || '', province, city },
profile: {
bio: data.bio || '',
birthDate: data.birthDate || '',
maritalStatus: data.maritalStatus || '',
maritalStatus: data.marriageStatus || data.maritalStatus || '',
gender: data.gender || '',
avatarId: data.avatarId || null,
},
@@ -87,24 +90,25 @@ register('POST', endpoints.addNewUser, ({ data }) => {
register('PATCH', endpoints.updateUser, ({ params, data }) => {
const roleByName = (n) => roles.find((r) => r.name === n)
const role =
roles.find((r) => r.id === Number(data.roleId)) ||
(Array.isArray(data.roles) && data.roles[0] ? roleByName(data.roles[0]) : null) ||
roles.find((r) => r.id === Number(data.roleId)) ||
null
const firstName = data.firstName ?? ''
const lastName = data.lastName ?? ''
const fullName = `${firstName} ${lastName}`.trim()
const name = data.name ?? `${data.firstName ?? ''} ${data.lastName ?? ''}`.trim()
const [firstName, ...rest] = name.split(' ')
const lastName = rest.join(' ')
const phoneNumber = data.phoneNumber ?? ''
const patch = {
// --- spec ---
name: data.name ?? fullName,
name,
email: data.email,
phone: data.phone ?? (phoneNumber ? phoneNumber.replace(/^0/, '+98') : undefined),
roles: Array.isArray(data.roles) ? data.roles : role ? [role.name] : [],
birthday: data.birthday ?? null,
// --- ui-only ---
firstName,
lastName,
fullName,
fullName: name,
phoneNumber,
nationalCode: data.nationalCode,
roleId: role?.id,
@@ -115,8 +119,7 @@ register('PATCH', endpoints.updateUser, ({ params, data }) => {
},
profile: {
bio: data.bio || '',
birthDate: data.birthDate || '',
maritalStatus: data.maritalStatus || '',
maritalStatus: data.marriageStatus ?? data.maritalStatus ?? '',
gender: data.gender || '',
avatarId: data.avatarId || null,
},
@@ -148,9 +151,14 @@ register('PATCH', endpoints.updateUserRole, ({ params, data }) => {
}
})
register('POST', endpoints.changeUserStatus, ({ params, data }) => ({
data: updateById(adminUsers, params.id, { status: data.status }),
}))
register('PATCH', endpoints.changeUserStatus, ({ params, data }) => {
const updated = updateById(adminUsers, params.id, { status: data.status })
return {
success: true,
message: 'User status updated.',
data: { id: updated.id, name: updated.name, status: updated.status },
}
})
register('DELETE', endpoints.deleteUser, ({ params }) => {
removeById(adminUsers, params.id)
@@ -160,5 +168,3 @@ register('DELETE', endpoints.deleteUser, ({ params }) => {
data: null,
}
})
register('GET', endpoints.getRolesList, () => ({ data: roles }))
-10
View File
@@ -4,7 +4,6 @@ import {
apiAddAdminUser,
apiChangeAdminUserStatus,
apiDeleteAdminUser,
apiGetAdminRoles,
apiGetAdminUsers,
apiShowAdminUser,
apiUpdateAdminUser,
@@ -15,7 +14,6 @@ export const adminUsersKeys = {
all: ['admin', 'users'],
list: (filters, pagination) => ['admin', 'users', 'list', filters, pagination],
detail: (id) => ['admin', 'users', 'detail', id],
roles: () => ['admin', 'users', 'roles'],
}
export const useAdminUsersListQuery = (filtersRef, paginationRef, options = {}) =>
@@ -37,14 +35,6 @@ export const useAdminUserQuery = (idRef, options = {}) =>
...options,
})
export const useAdminRolesListQuery = (options = {}) =>
useQuery({
queryKey: adminUsersKeys.roles(),
queryFn: () => apiGetAdminRoles(),
select: (response) => response?.data ?? response,
...options,
})
export const useAddAdminUserMutation = () =>
useMutation({ mutationFn: (payload) => apiAddAdminUser(payload) })