Files
gameno-dashboard/src/views/attendances/AttendanceListView.vue
T
kavehhn e977ccd356 fix: show only past-due sessions needing attendance on attendance page
Use attendanceScope filtering, correct pending status for held sessions, and return to the attendance hub after saving.
2026-08-16 08:22:18 +03:30

202 lines
6.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
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/attendances/AttendanceListView.vue -->
<template>
<div class="attendance-list-view">
<PageHeader
title="حضور و غیاب"
subtitle="جلسات سررسید گذشته که ثبت حضور دانشجویان آن‌ها ناقص است"
>
<Button
label="مشاهده جلسات"
icon="pi pi-calendar"
text
@click="$router.push('/sessions')"
/>
</PageHeader>
<div class="grid mb-4">
<div class="col-12 sm:col-4">
<div class="surface-card p-4 border-round-xl border-1 border-color shadow-sm">
<span class="text-muted text-xs block mb-1">در انتظار ثبت حضور</span>
<span class="text-3xl font-bold text-orange-500">{{ toPersianDigits(summary.pending) }}</span>
</div>
</div>
<div class="col-12 sm:col-4">
<div class="surface-card p-4 border-round-xl border-1 border-color shadow-sm">
<span class="text-muted text-xs block mb-1">ثبت کامل شده</span>
<span class="text-3xl font-bold text-green-500">{{ toPersianDigits(summary.recorded) }}</span>
</div>
</div>
<div class="col-12 sm:col-4">
<div class="surface-card p-4 border-round-xl border-1 border-color shadow-sm">
<span class="text-muted text-xs block mb-1">جلسات سررسید گذشته</span>
<span class="text-3xl font-bold text-color">{{ toPersianDigits(summary.total) }}</span>
</div>
</div>
</div>
<DataTableWrapper
:items="items"
:totalCount="totalCount"
:page="queryParams.page"
:limit="queryParams.limit"
:sortBy="queryParams.sortBy"
:sortOrder="queryParams.sortOrder"
:loading="isLoading"
@page-change="onPageChange"
@sort-change="onSort"
@search-change="onSearch"
>
<template #toolbar>
<SelectButton
v-model="attendanceFilter"
:options="filterOptions"
optionLabel="label"
optionValue="value"
class="text-sm"
@change="onAttendanceFilterChange"
/>
</template>
<Column field="topic" header="موضوع جلسه">
<template #body="{ data }">
<span class="font-semibold text-color">{{ data.topic || '—' }}</span>
</template>
</Column>
<Column field="course" header="دوره">
<template #body="{ data }">
{{ data.course?.title || data.courseTitle || '—' }}
</template>
</Column>
<Column field="class" header="کلاس">
<template #body="{ data }">
{{ data.class?.name || '—' }}
</template>
</Column>
<Column field="day" header="تاریخ" sortable sortField="day">
<template #body="{ data }">
{{ formatJalali(data.day || data.date) }}
</template>
</Column>
<Column field="timeRange" header="ساعت">
<template #body="{ data }">
<span dir="ltr">{{ data.startTime || '—' }} {{ data.endTime || '—' }}</span>
</template>
</Column>
<Column field="attendanceStatus" header="وضعیت حضور">
<template #body="{ data }">
<Tag
:value="getAttendanceStatus(data).label"
:severity="getAttendanceStatus(data).severity"
/>
</template>
</Column>
<Column field="status" header="وضعیت جلسه">
<template #body="{ data }">
<StatusTag :status="data.status || 'scheduled'" type="session" />
</template>
</Column>
<Column header="عملیات" style="width: 120px">
<template #body="{ data }">
<Button
icon="pi pi-check-square"
:label="data.attendanceComplete ? 'ویرایش' : 'ثبت'"
size="small"
:severity="data.attendanceComplete ? 'secondary' : 'success'"
@click="$router.push(`/sessions/attendance/${data._id || data.id}`)"
/>
</template>
</Column>
</DataTableWrapper>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useDataTable } from '@/composables/useDataTable';
import { usePersianDate } from '@/composables/usePersianDate';
import { sessionApi } from '@/api/sessionApi';
import PageHeader from '@/components/common/PageHeader.vue';
import DataTableWrapper from '@/components/common/DataTableWrapper.vue';
import StatusTag from '@/components/common/StatusTag.vue';
import Button from 'primevue/button';
import Column from 'primevue/column';
import Tag from 'primevue/tag';
import SelectButton from 'primevue/selectbutton';
const { formatJalali, toPersianDigits } = usePersianDate();
const attendanceFilter = ref('pending');
const filterOptions = [
{ label: 'در انتظار ثبت', value: 'pending' },
{ label: 'ثبت شده', value: 'recorded' }
];
const summary = ref({ pending: 0, recorded: 0, total: 0 });
const fetchAttendanceSessions = (params) =>
sessionApi.getAll({
...params,
attendanceScope: params.attendanceScope || attendanceFilter.value
});
const {
items,
totalCount,
isLoading,
queryParams,
loadData,
onPageChange,
onSort,
onSearch
} = useDataTable(fetchAttendanceSessions, {
sortBy: 'day',
sortOrder: 'desc',
attendanceScope: 'pending'
});
const getAttendanceStatus = (session) => {
if (session.attendanceComplete) {
return { label: 'ثبت شده', severity: 'success' };
}
if (session.attendancePending) {
return { label: 'در انتظار ثبت', severity: 'warn' };
}
return { label: 'برگزار نشده', severity: 'secondary' };
};
const onAttendanceFilterChange = () => {
queryParams.attendanceScope = attendanceFilter.value;
queryParams.page = 1;
loadData();
};
const loadSummary = async () => {
try {
const [pendingRes, recordedRes] = await Promise.all([
sessionApi.getAll({ page: 1, limit: 1, attendanceScope: 'pending', sortBy: 'day', sortOrder: 'desc' }),
sessionApi.getAll({ page: 1, limit: 1, attendanceScope: 'recorded', sortBy: 'day', sortOrder: 'desc' })
]);
const pending = pendingRes.meta?.totalCount ?? 0;
const recorded = recordedRes.meta?.totalCount ?? 0;
summary.value = {
pending,
recorded,
total: pending + recorded
};
} catch {
summary.value = { pending: 0, recorded: 0, total: 0 };
}
};
onMounted(async () => {
await Promise.all([loadData(), loadSummary()]);
});
</script>