Files
banu-front/src/features/admin/assignments/components/modals/AssignmentDetailsModal.vue
T
sajjadtalkhabi 3f3c66a02d fix: test
2026-05-28 15:40:22 +03:30

250 lines
7.2 KiB
Vue

<template>
<BasicModal
title="جزئیات تکلیف"
title-en="Assignment Details"
width="95%"
max-width="60rem"
min-width="auto"
:show-close-button="true"
>
<template #default="{ close }">
<div class="assignment-details">
<SkeletonLoaderBlock v-if="isLoading" :rows="3" :cols-per-row="1" />
<template v-else-if="assignment">
<div class="assignment-details__head">
<p class="assignment-details__title">{{ assignment.title || '—' }}</p>
<p class="assignment-details__sub">
<span>دوره: {{ courseTitle }}</span>
<span class="assignment-details__sep">|</span>
<span>جلسه: {{ sessionTitle }}</span>
</p>
</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" />
<LineInfoBlock title="تعداد فرستندگان" :numeric-desc="submittersLabel" />
<LineInfoBlock
title="تاریخ ثبت"
:numeric-desc="
assignment.faCreatedAt || formatJalaaliDate(assignment.createdAt) || '—'
"
/>
</div>
<div v-if="assignment.description" class="assignment-details__desc">
<LineInfoBlock title="توضیحات" :desc="assignment.description" />
</div>
<div v-if="attachmentItems.length" class="assignment-details__attachments">
<LineTitleBlock title="فایل‌های تکلیف" title-en="Attachments" />
<ul class="assignment-details__attachments-list">
<li v-for="file in attachmentItems" :key="file.id">
<a
:href="file.url || '#'"
target="_blank"
rel="noopener noreferrer"
class="assignment-details__file"
>
<SvgIcon name="file" :size="16" color="var(--color-prim-gray)" />
<span>{{ file.title }}</span>
</a>
</li>
</ul>
</div>
</template>
<NoItems v-else title="یافت نشد" desc="اطلاعات این تکلیف در دسترس نیست." />
<div class="assignment-details__divider" />
<div class="assignment-details__footer">
<BaseButton text="بستن" custom-class="assignment-details__close-btn" @click="close">
<template #appendIcon>
<SvgIcon name="arrow-left" :size="18" color="#fff" />
</template>
</BaseButton>
</div>
</div>
</template>
</BasicModal>
</template>
<script setup>
import { computed } from 'vue'
import useModal from '@/composables/useModal'
import { ASSIGNMENT_PRIORITY } from '@/enums'
import BasicModal from '@/components/BasicModal.vue'
import BaseButton from '@/components/BaseButton.vue'
import SvgIcon from '@/components/icons/SvgIcon.vue'
import NoItems from '@/components/blocks/NoItems.vue'
import { formatJalaaliDate } from '@/utils/date-utils'
import LineTitleBlock from '@/components/LineTitleBlock.vue'
import LineInfoBlock from '@/components/blocks/LineInfoBlock.vue'
import { useAdminAssignmentQuery } from '@/services/query/admin-assignments'
import SkeletonLoaderBlock from '@/components/blocks/SkeletonLoaderBlock.vue'
defineOptions({ name: 'AssignmentDetailsModal' })
const { getModal } = useModal()
const modalData = computed(() => getModal('AssignmentDetailsModal')?.data ?? {})
const assignmentId = computed(() => modalData.value.id ?? null)
const { data: assignment, isLoading } = useAdminAssignmentQuery(assignmentId, {
enabled: () => !!assignmentId.value,
})
const courseTitle = computed(
() =>
assignment.value?.session?.course?.title ||
assignment.value?.course?.title ||
assignment.value?.courseTitle ||
'—'
)
const sessionTitle = computed(
() => assignment.value?.session?.title || assignment.value?.sessionTitle || '—'
)
const deadlineRaw = computed(() => assignment.value?.endDate || assignment.value?.deadline || '')
const deadlineLabel = computed(() => formatJalaaliDate(deadlineRaw.value) || '—')
const durationLabel = computed(() => {
if (assignment.value?.durationDays != null) return `${assignment.value.durationDays} روز`
const start = assignment.value?.startDate || assignment.value?.createdAt
const end = deadlineRaw.value
if (!start || !end) return '—'
const diff = Math.ceil((new Date(end).getTime() - new Date(start).getTime()) / 86_400_000)
return diff >= 0 ? `${diff} روز` : '—'
})
const priorityLabel = computed(() => {
const priority =
assignment.value?.priority ||
(typeof assignment.value?.isPriority === 'boolean'
? assignment.value.isPriority
? 'mandatory'
: 'optional'
: null)
return ASSIGNMENT_PRIORITY[priority] || '—'
})
const submittersLabel = computed(
() => assignment.value?.submittersCount ?? assignment.value?.submissionsCount ?? 0
)
const attachmentItems = computed(() => {
const media = assignment.value?.media
if (Array.isArray(media) && media.length > 0) {
return media.map((m) => ({
id: m.id,
title: m.fileName || `فایل ${m.id}`,
url: m.url || m.downloadUrl || '',
}))
}
const legacy = assignment.value?.attachments
if (Array.isArray(legacy)) {
return legacy.map((a) => ({
id: a.id,
title: a.title || `فایل ${a.id}`,
url: a.fileUrl || '',
}))
}
return []
})
</script>
<style lang="scss" scoped>
.assignment-details {
width: 100%;
text-align: start;
&__head {
margin: 1rem 0 0.5rem;
}
&__title {
font-family: var(--font-family-fa);
font-size: 1.1rem;
font-weight: 500;
color: #4b4b4b;
margin: 0 0 0.25rem;
}
&__sub {
font-family: var(--font-family-fa);
font-size: 0.75rem;
color: #535353;
margin: 0;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
&__sep {
color: #c4c4c4;
}
&__grid {
display: grid;
grid-template-columns: 1fr;
gap: 0.25rem;
@media (min-width: 640px) {
grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 1024px) {
grid-template-columns: repeat(3, 1fr);
}
}
&__desc,
&__attachments {
margin-top: 1rem;
}
&__attachments-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
&__file {
display: inline-flex;
align-items: center;
gap: 0.375rem;
color: var(--color-primary);
text-decoration: none;
font-family: var(--font-family-fa);
font-size: 0.85rem;
&:hover {
text-decoration: underline;
}
}
&__divider {
border-block-end: 1px solid var(--color-thd-gray);
margin-block: 1.25rem;
}
&__footer {
display: flex;
justify-content: flex-end;
}
&__close-btn {
min-width: 10rem;
}
}
</style>