Files
gameno-dashboard/src/views/classes/ClassDetailView.vue
T
kavehhn 91527e64bb feat(classes): add class days and meeting times to class forms
Let admins set the weekly schedule on the class and show it in lists, details, and session generation.
2026-08-15 23:59:08 +03:30

233 lines
8.8 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- /src/views/classes/ClassDetailView.vue -->
<template>
<div class="class-detail-view" v-if="classData">
<PageHeader :title="classData.name" :subtitle="`دوره: ${classData.course?.title || '-'}`">
<Button label="ویرایش" icon="pi pi-pencil" class="ml-2" @click="$router.push(`/classes/edit/${classId}`)" />
<Button label="بازگشت" icon="pi pi-arrow-left" text severity="secondary" @click="$router.push('/classes')" />
</PageHeader>
<div class="surface-card p-4 sm:p-5 border-round border-1 border-color shadow-sm mb-4">
<h2 class="text-lg font-bold text-color mb-3">مشخصات کلاس</h2>
<div class="grid">
<div class="col-12 sm:col-4">
<span class="text-muted text-xs block mb-1">استاد مدرس</span>
<span class="font-bold text-color text-sm">
{{ classData.professor ? `${classData.professor.name || ''} ${classData.professor.surname || ''}`.trim() : '—' }}
</span>
</div>
<div class="col-12 sm:col-4">
<span class="text-muted text-xs block mb-1">ظرفیت کل</span>
<span class="font-bold text-color text-sm">{{ toPersianDigits(classData.capacity || 0) }} نفر</span>
</div>
<div class="col-12 sm:col-4">
<span class="text-muted text-xs block mb-1">تعداد ثبتنامیها</span>
<span class="font-bold text-color text-sm">{{ toPersianDigits((classData.students || []).length) }} نفر</span>
</div>
<div class="col-12 sm:col-4">
<span class="text-muted text-xs block mb-1">شهریه</span>
<span class="font-bold text-color text-sm">{{ toPersianDigits((classData.tuitionFee || 0).toLocaleString('en-US')) }} تومان</span>
</div>
<div class="col-12 sm:col-4">
<span class="text-muted text-xs block mb-1">بازه زمانی</span>
<span class="font-bold text-color text-sm">
{{ formatJalali(classData.startDate) }} {{ formatJalali(classData.endDate) }}
</span>
</div>
<div class="col-12 sm:col-4">
<span class="text-muted text-xs block mb-1">روزهای برگزاری</span>
<span class="font-bold text-color text-sm">{{ formatClassDays(classData.days) || '—' }}</span>
</div>
<div class="col-12 sm:col-4">
<span class="text-muted text-xs block mb-1">ساعت کلاس</span>
<span class="font-bold text-color text-sm" dir="ltr">{{ formatClassTime(classData.startTime, classData.endTime) || '—' }}</span>
</div>
<div class="col-12 sm:col-4">
<span class="text-muted text-xs block mb-1">وضعیت</span>
<StatusTag :status="classData.isActive !== false" />
</div>
</div>
</div>
<div class="surface-card p-4 sm:p-5 border-round border-1 border-color shadow-sm mb-4">
<h2 class="text-lg font-bold mb-3">دانشجویان</h2>
<DataTable :value="classData.students || []" class="p-datatable-sm text-sm" emptyMessage="دانشجویی ثبت نشده">
<Column header="#">
<template #body="{ index }">{{ toPersianDigits(index + 1) }}</template>
</Column>
<Column header="نام">
<template #body="{ data }">{{ data.name }}</template>
</Column>
<Column header="موبایل">
<template #body="{ data }"><span dir="ltr">{{ data.phoneNumber || '—' }}</span></template>
</Column>
<Column v-if="canManageStudents" header="عملیات" style="width: 90px">
<template #body="{ data }">
<PermissionGate permission="classes:register_users">
<Button
icon="pi pi-user-minus"
text
rounded
size="small"
severity="danger"
v-tooltip.top="'حذف از کلاس'"
:loading="removingUserId === String(data._id || data.id)"
@click="confirmRemoveStudent(data)"
/>
</PermissionGate>
</template>
</Column>
</DataTable>
</div>
<div class="surface-card p-4 sm:p-5 border-round border-1 border-color shadow-sm">
<div class="flex align-items-center justify-content-between mb-3">
<h2 class="text-lg font-bold m-0">جلسات کلاس</h2>
<Button
label="مدیریت جلسات"
icon="pi pi-calendar"
text
size="small"
@click="$router.push(`/classes/edit/${classId}`)"
/>
</div>
<TableSkeleton v-if="loadingSessions" :rows="5" :columns="5" />
<DataTable
v-else
:value="sessions"
class="p-datatable-sm text-sm"
emptyMessage="جلسه‌ای برای این کلاس ثبت نشده است"
>
<Column header="#">
<template #body="{ index }">{{ toPersianDigits(index + 1) }}</template>
</Column>
<Column header="موضوع">
<template #body="{ data }">{{ data.topic || '—' }}</template>
</Column>
<Column header="تاریخ">
<template #body="{ data }">{{ formatJalali(data.day || data.date) }}</template>
</Column>
<Column header="زمان">
<template #body="{ data }">
<span dir="ltr">{{ data.startTime || '—' }} {{ data.endTime || '—' }}</span>
</template>
</Column>
<Column header="وضعیت">
<template #body="{ data }">
<StatusTag :status="data.status || 'scheduled'" type="session" />
</template>
</Column>
</DataTable>
</div>
<ConfirmDeleteDialog
v-model="removeDialogVisible"
title="حذف از کلاس"
:message="removeDialogMessage"
:loading="!!removingUserId"
@confirm="handleRemoveStudent"
/>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { classApi } from '@/api/classApi';
import { formatClassDays, formatClassTime } from '@/utils/classSchedule';
import { sessionApi } from '@/api/sessionApi';
import { usePersianDate } from '@/composables/usePersianDate';
import { useToast } from '@/composables/useToast';
import { usePermission } from '@/composables/usePermission';
import PageHeader from '@/components/common/PageHeader.vue';
import StatusTag from '@/components/common/StatusTag.vue';
import TableSkeleton from '@/components/common/TableSkeleton.vue';
import ConfirmDeleteDialog from '@/components/common/ConfirmDeleteDialog.vue';
import PermissionGate from '@/components/common/PermissionGate.vue';
import Button from 'primevue/button';
import DataTable from 'primevue/datatable';
import Column from 'primevue/column';
const route = useRoute();
const classId = route.params.id;
const { toPersianDigits, formatJalali } = usePersianDate();
const { showError, showSuccess } = useToast();
const { hasPermission } = usePermission();
const canManageStudents = computed(() => hasPermission('classes:register_users'));
const classData = ref(null);
const sessions = ref([]);
const loadingSessions = ref(false);
const removeDialogVisible = ref(false);
const removingUserId = ref('');
const studentToRemove = ref(null);
const removeDialogMessage = computed(() => {
const name = studentToRemove.value?.name;
return name
? `آیا «${name}» از این کلاس حذف شود؟`
: 'آیا این دانشجو از کلاس حذف شود؟';
});
const fetchClass = async () => {
try {
const res = await classApi.getOne(classId);
classData.value = res.data || res;
} catch (err) {
showError(err);
}
};
const fetchSessions = async () => {
loadingSessions.value = true;
try {
const res = await sessionApi.getAll({
limit: 100,
class: classId,
sortBy: 'day',
sortOrder: 'asc'
});
if (Array.isArray(res)) {
sessions.value = res;
} else if (Array.isArray(res?.data)) {
sessions.value = res.data;
} else if (Array.isArray(res?.items)) {
sessions.value = res.items;
} else {
sessions.value = [];
}
} catch (err) {
showError(err);
sessions.value = [];
} finally {
loadingSessions.value = false;
}
};
onMounted(() => {
fetchClass();
fetchSessions();
});
const confirmRemoveStudent = (student) => {
studentToRemove.value = student;
removeDialogVisible.value = true;
};
const handleRemoveStudent = async () => {
const userId = studentToRemove.value?._id || studentToRemove.value?.id;
if (!userId) return;
removingUserId.value = String(userId);
try {
const res = await classApi.removeUser(classId, userId);
classData.value = res.data || res;
removeDialogVisible.value = false;
studentToRemove.value = null;
showSuccess('دانشجو از کلاس حذف شد');
} catch (err) {
showError(err);
} finally {
removingUserId.value = '';
}
};
</script>