Files
gameno-dashboard/src/views/attendances/AttendanceEntryView.vue
T
kavehhn 1a34c3b133 Initial commit: admin dashboard for GameNo.
Vue 3 + Vite dashboard with PrimeVue, i18n, and API integration for institution management.
2026-08-09 04:18:09 +02:00

159 lines
5.6 KiB
Vue

<!-- /src/views/attendances/AttendanceEntryView.vue -->
<template>
<div class="attendance-entry-view" v-if="session">
<PageHeader
:title="$t('attendances.title')"
:subtitle="`دوره: ${session.course?.title || '-'} | کلاس: ${session.class?.name || '-'} | تاریخ: ${formatJalali(session.day)} (${session.startTime || ''} تا ${session.endTime || ''})`"
>
<Button label="بازگشت" icon="pi pi-arrow-left" text severity="secondary" @click="$router.push('/sessions')" />
</PageHeader>
<div class="surface-card p-4 sm:p-5 border-round border-1 border-color shadow-sm mb-3" v-if="session.note">
<span class="text-muted text-xs block mb-1">یادداشت جلسه</span>
<p class="m-0 white-space-pre-wrap">{{ session.note }}</p>
</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-4 pb-3 border-bottom-1 border-color">
<div>
<h2 class="text-lg font-bold text-color m-0">لیست دانشجویان کلاس</h2>
<span class="text-muted text-xs">وضعیت حضور هر دانشجو را مشخص کنید</span>
</div>
<div class="flex align-items-center gap-2">
<Button label="علامت‌گذاری همه به عنوان حاضر" icon="pi pi-check-circle" size="small" text severity="success" @click="markAllPresent" />
</div>
</div>
<DataTable :value="studentList" class="p-datatable-sm text-sm" emptyMessage="دانشجویی در این کلاس ثبت‌نام نشده است">
<Column header="#" style="width: 50px">
<template #body="{ index }">{{ toPersianDigits(index + 1) }}</template>
</Column>
<Column field="name" header="نام و نام خانوادگی دانشجو">
<template #body="{ data }">
<span class="font-bold text-color">{{ data.name }} {{ data.surname }}</span>
<span class="text-xs text-muted block">کد ملی: {{ toPersianDigits(data.nationalIdCode || '—') }}</span>
</template>
</Column>
<Column field="status" header="وضعیت حضور">
<template #body="{ data }">
<SelectButton
v-model="data.status"
:options="statusOptions"
optionLabel="label"
optionValue="value"
class="text-xs"
/>
</template>
</Column>
<Column field="note" header="یادداشت / توضیحات">
<template #body="{ data }">
<InputText v-model="data.note" class="w-full text-xs" placeholder="دلیل غیبت یا تاخیر..." />
</template>
</Column>
</DataTable>
<div class="flex justify-content-end gap-2 mt-4 pt-3 border-top-1 border-color">
<Button label="انصراف" text severity="secondary" @click="$router.push('/sessions')" />
<Button
:label="$t('attendances.submitAttendance')"
icon="pi pi-save"
severity="success"
:loading="isSubmitting"
:disabled="!studentList.length"
@click="handleSubmit"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { sessionApi } from '@/api/sessionApi';
import { attendanceApi } from '@/api/attendanceApi';
import { usePersianDate } from '@/composables/usePersianDate';
import { useToast } from '@/composables/useToast';
import PageHeader from '@/components/common/PageHeader.vue';
import Button from 'primevue/button';
import DataTable from 'primevue/datatable';
import Column from 'primevue/column';
import SelectButton from 'primevue/selectbutton';
import InputText from 'primevue/inputtext';
const route = useRoute();
const router = useRouter();
const sessionId = route.params.id;
const { toPersianDigits, formatJalali } = usePersianDate();
const { showSuccess, showError } = useToast();
const session = ref(null);
const studentList = ref([]);
const isSubmitting = ref(false);
const statusOptions = [
{ label: 'حاضر', value: 'present' },
{ label: 'غایب', value: 'absent' },
{ label: 'تاخیر', value: 'late' },
{ label: 'موجه', value: 'excused' }
];
const fetchSessionAttendance = async () => {
try {
const res = await sessionApi.getOne(sessionId);
const data = res.data || res;
session.value = data;
const roster = data.class?.students || [];
const attendanceByUser = new Map(
(data.attendanceList || []).map((row) => [
String(row.user?._id || row.user),
row
])
);
studentList.value = roster.map((user) => {
const existing = attendanceByUser.get(String(user._id));
return {
userId: user._id,
name: user.name || '',
surname: user.surname || '',
nationalIdCode: user.nationalIdCode || '',
status: existing?.status || 'present',
note: existing?.note || ''
};
});
} catch (err) {
showError(err);
}
};
const markAllPresent = () => {
studentList.value.forEach((s) => { s.status = 'present'; });
};
const handleSubmit = async () => {
isSubmitting.value = true;
try {
const payloadList = studentList.value.map((s) => ({
user: s.userId,
status: s.status,
note: s.note
}));
await attendanceApi.recordSessionAttendance(sessionId, payloadList);
showSuccess('حضور و غیاب جلسه با موفقیت ثبت شد');
router.push('/sessions');
} catch (err) {
showError(err);
} finally {
isSubmitting.value = false;
}
};
onMounted(fetchSessionAttendance);
</script>