fix
Deploy banu-front / deploy (push) Successful in 1m20s

This commit is contained in:
sajjadtalkhabi
2026-06-27 20:07:58 +03:30
parent 2c617258f4
commit e700609236
22 changed files with 236 additions and 177 deletions
@@ -21,10 +21,6 @@
</div>
<div class="assignment-details__grid">
<LineInfoBlock
title="تاریخ شروع"
:numeric-desc="formatJalaaliDate(assignment.startDate) || '—'"
/>
<LineInfoBlock title="تاریخ پایان" :numeric-desc="deadlineLabel" />
<LineInfoBlock title="مدت زمان" :numeric-desc="durationLabel" />
<LineInfoBlock title="اولویت" :desc="priorityLabel" />
@@ -198,7 +198,6 @@ const normalizeExistingQuestions = (raw = []) => {
optionText: o.optionText || '',
isCorrect: !!o.isCorrect,
})),
// No `__local` flag — these came from the server, so the builder will lock them.
}
})
}
@@ -280,7 +279,6 @@ const submitting = computed(
const postQuestionsSequentially = async (id, list) => {
for (const payload of list) {
// Sequential so question position ordering is preserved on the backend.
// eslint-disable-next-line no-await-in-loop
await addQuestionMutation.mutateAsync({ examId: id, payload })
}
@@ -302,7 +300,7 @@ const onSubmit = async () => {
if (targetExamId && newQuestions.length > 0) {
await postQuestionsSequentially(targetExamId, newQuestions)
}
await queryClient.invalidateQueries({ queryKey: adminExamsKeys.all })
await queryClient.invalidateQueries({ queryKey: adminExamsKeys.all, refetchType: 'all' })
router.push({ name: 'admin-exams' })
}
@@ -59,7 +59,8 @@ const { data, isLoading } = useAdminTicketsListQuery(filters, pagination, {
keepPreviousData: true,
})
const tickets = computed(() => data.value?.data ?? [])
const tickets = computed(() => data.value?.items ?? [])
const paginationMeta = computed(() => ({
page: pagination.value.page,
perPage: pagination.value.perPage,
@@ -39,11 +39,9 @@
:src="mediaUrl"
:video-id="session.id"
/>
<VoiceRecorder
v-else-if="contentType === 'voice'"
:model-value="mediaUrl"
:disabled="true"
/>
<div v-else-if="contentType === 'voice'" class="session-details__voice">
<VoiceRecorder :model-value="mediaUrl" :disabled="true" />
</div>
<a
v-else-if="mediaUrl"
:href="mediaUrl"
@@ -111,9 +109,9 @@ const courseTitle = computed(() => session.value?.course?.title || '—')
const isOnline = computed(() => session.value?.type === 'online')
const collectionToContentType = (collectionName) => {
if (collectionName === 'videos') return 'video'
if (collectionName === 'video') return 'video'
if (collectionName === 'voice') return 'voice'
if (collectionName === 'pdfs') return 'text'
if (collectionName === 'pdf') return 'text'
return ''
}
@@ -173,6 +171,22 @@ const mediaFileName = computed(() => contentMedia.value?.fileName || 'فایل
justify-content: center;
}
&__voice {
width: 100%;
/* stylelint-disable-next-line selector-class-pattern */
:deep(.voice-recorder) {
min-height: auto;
padding: 0.75rem;
}
/* stylelint-disable-next-line selector-class-pattern */
:deep(.voice-recorder__main-btn) {
width: 4rem;
height: 4rem;
}
}
&__file {
display: inline-flex;
align-items: center;
@@ -357,7 +357,10 @@ const onSubmit = async () => {
} else {
await addMutation.mutateAsync(payload)
}
await queryClient.invalidateQueries({ queryKey: adminSessionsKeys.all })
// The list query is inactive while we're on the form page, and the global
// `refetchOnMount: false` means navigating back won't refetch a merely-stale
// query — so force a refetch of all matching queries, active or not.
await queryClient.invalidateQueries({ queryKey: adminSessionsKeys.all, refetchType: 'all' })
router.push({ name: 'admin-sessions' })
}
@@ -17,19 +17,19 @@
<LineInfoBlock title="نام و نام خانوادگی" :numeric-desc="fullName" />
<LineInfoBlock
title="تاریخ تولد"
:numeric-desc="profile.faBirthDate || formatJalaaliDate(profile.birthDate) || ''"
:numeric-desc="formatJalaaliDate(personal.birthDate) || ''"
/>
<LineInfoBlock title="شماره تماس" :numeric-desc="user.phoneNumber" />
<LineInfoBlock title="کد ملی" :numeric-desc="user.nationalCode" />
<LineInfoBlock title="شماره تماس" :numeric-desc="personal.phone" />
<LineInfoBlock title="کد ملی" :numeric-desc="personal.nationalCode" />
<LineInfoBlock
title="وضعیت تاهل"
:desc="profile.faMaritalStatus || MARITAL_STATUS[profile.maritalStatus] || ''"
:desc="MARITAL_STATUS[personal.maritalStatus] || ''"
/>
<LineInfoBlock title="جنسیت" :desc="profile.faGender || GENDER[profile.gender] || ''" />
<LineInfoBlock title="استان" :desc="user?.province?.name || '-'" />
<LineInfoBlock title="شهر" :desc="user?.city?.name || '-'" />
<LineInfoBlock title="جنسیت" :desc="GENDER[personal.gender] || ''" />
<LineInfoBlock title="استان" :desc="personal.province || '-'" />
<LineInfoBlock title="شهر" :desc="personal.city || '-'" />
<div class="user-verification-details__cell user-verification-details__cell--span-3">
<LineInfoBlock title="آدرس" :desc="user?.address || '-'" />
<LineInfoBlock title="آدرس" :desc="personal.address || '-'" />
</div>
</div>
</section>
@@ -39,15 +39,15 @@
<div class="user-verification-details__grid user-verification-details__grid--4">
<LineInfoBlock
title="وضعیت تحصیلی"
:desc="profile.faEducationStatus || EDUCATION_STATUS[profile.educationStatus] || ''"
:desc="EDUCATION_STATUS[profile.educationStatus] || ''"
/>
<LineInfoBlock
title="آخرین مدرک تحصیلی (طلبه)"
:desc="profile.faSeminaryLevel || SEMINARY_LEVEL[profile.seminaryLevel] || ''"
:desc="SEMINARY_LEVEL[profile.seminaryLevel] || ''"
/>
<LineInfoBlock
title="آخرین مدرک تحصیلی"
:desc="profile.faUniversityLevel || UNIVERSITY_LEVEL[profile.universityLevel] || ''"
:desc="UNIVERSITY_LEVEL[profile.universityLevel] || ''"
/>
<LineInfoBlock title="نام حوزه علمیه/دانشگاه" :desc="profile.universityName || ''" />
<div class="user-verification-details__cell user-verification-details__cell--full">
@@ -56,30 +56,12 @@
:desc="profile.fieldOfStudy || ''"
/>
</div>
<div class="user-verification-details__cell user-verification-details__cell--full">
<LineInfoBlock
title="خلاصه‌ای از سوابق شغلی"
:desc="profile.workExperienceSummary || ''"
/>
</div>
<div class="user-verification-details__cell user-verification-details__cell--full">
<LineInfoBlock
title="خلاصه‌ای از فعالیت‌ها در موقعیت‌های شغلی"
:desc="profile.activitySummary || ''"
/>
</div>
</div>
</section>
<section class="user-verification-details__section">
<LineTitleBlock title="اطلاعات شغلی" title-en="Job information" />
<div class="user-verification-details__grid user-verification-details__grid--4">
<div class="user-verification-details__cell user-verification-details__cell--full">
<LineInfoBlock
title="گرایش تخصصی حوزوی / رشته تحصیلی دانشگاهی"
:desc="profile.fieldOfStudy || ''"
/>
</div>
<div class="user-verification-details__cell user-verification-details__cell--full">
<LineInfoBlock
title="خلاصه‌ای از سوابق شغلی"
@@ -190,7 +172,9 @@
<section v-if="faithProduction?.url" class="user-verification-details__section">
<LineTitleBlock title="بخش صوت" title-en="Audio section" />
<LineInfoBlock title="نظر و تحلیل از ویدئو ارائه شده" />
<audio :src="faithProduction.url" controls class="user-verification-details__audio" />
<div class="user-verification-details__voice">
<VoiceRecorder :model-value="faithProduction" disabled />
</div>
</section>
<section v-if="leaderMessage?.url" class="user-verification-details__section">
@@ -214,6 +198,7 @@ import useModal from '@/composables/useModal'
import BasicModal from '@/components/BasicModal.vue'
import { formatJalaaliDate } from '@/utils/date-utils'
import LineTitleBlock from '@/components/LineTitleBlock.vue'
import VoiceRecorder from '@/components/form/VoiceRecorder.vue'
import LineInfoBlock from '@/components/blocks/LineInfoBlock.vue'
import { useAdminUserQuery, useAdminUserRegisterDataQuery } from '@/services/query/admin-users'
import {
@@ -227,7 +212,6 @@ import {
SEMINARY_LEVEL,
SESSION_CANCELLATION_RESPONSE,
UNIVERSITY_LEVEL,
VERIFICATION_MEDIA_TYPE,
} from '@/enums'
defineOptions({ name: 'UserVerificationDetailsModal' })
@@ -248,15 +232,23 @@ const { data: registerData } = useAdminUserRegisterDataQuery(userIdRef, {
const user = computed(() => modalData.value.user || fetched.value || null)
// The register-data endpoint returns the answers the user gave during the
// registration steps — the same fields the sections below render. Merge it over
// the user's stored profile so reviewing admins see what was actually submitted.
const profile = computed(() => ({ ...user.value?.profile, ...registerData.value }))
const fullName = computed(
() =>
user.value?.name || `${user.value?.firstName || ''} ${user.value?.lastName || ''}`.trim() || '—'
)
const fullName = computed(() => user.value?.name || '—')
const personal = computed(() => {
const u = user.value || {}
const p = u.profile || {}
return {
birthDate: u.birthday || p.birthDate || '',
phone: u.phone || u.phoneNumber || '',
nationalCode: u.nationalCode || '',
maritalStatus: u.marriageStatus || p.maritalStatus || '',
gender: u.gender || p.gender || '',
address: typeof u.address === 'string' ? u.address : u.address?.address || '',
province: u.province?.name || u.address?.province?.name || '',
city: u.city?.name || u.address?.city?.name || '',
}
})
const platforms = computed(() =>
Array.isArray(profile.value.propagationPlatforms) ? profile.value.propagationPlatforms : []
@@ -276,13 +268,10 @@ const onlinePlatformDetails = computed(
() => platforms.value.find((p) => p.platform === 'online')?.platformDetails || ''
)
const verificationMedia = computed(() => user.value?.verification?.media || [])
const faithProduction = computed(() =>
verificationMedia.value.find((m) => m.type === VERIFICATION_MEDIA_TYPE.FAITH_PRODUCTION)
)
const leaderMessage = computed(() =>
verificationMedia.value.find((m) => m.type === VERIFICATION_MEDIA_TYPE.LEADER_MESSAGE)
)
// Skill parts 6 & 7 store the uploaded media objects in register-data under
// `faithProductionAudio` / `leaderMessageVideo` (each carries a `url`).
const faithProduction = computed(() => profile.value.faithProductionAudio || null)
const leaderMessage = computed(() => profile.value.leaderMessageVideo || null)
</script>
<style lang="scss" scoped>
@@ -428,6 +417,22 @@ const leaderMessage = computed(() =>
margin-top: 0.5rem;
}
&__voice {
margin-top: 0.5rem;
/* stylelint-disable-next-line selector-class-pattern */
:deep(.voice-recorder) {
min-height: auto;
padding: 0.75rem;
}
/* stylelint-disable-next-line selector-class-pattern */
:deep(.voice-recorder__main-btn) {
width: 4rem;
height: 4rem;
}
}
&__divider {
border-block-end: 1px solid var(--color-thd-gray);
margin-block: 1.5rem;