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.
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
:title="$t('attendances.title')"
|
:title="$t('attendances.title')"
|
||||||
:subtitle="`دوره: ${session.course?.title || '-'} | کلاس: ${session.class?.name || '-'} | تاریخ: ${formatJalali(session.day)} (${session.startTime || ''} تا ${session.endTime || ''})`"
|
: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')" />
|
<Button label="بازگشت" icon="pi pi-arrow-left" text severity="secondary" @click="$router.push('/attendances')" />
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<div class="surface-card p-4 sm:p-5 border-round border-1 border-color shadow-sm mb-3" v-if="session.note">
|
<div class="surface-card p-4 sm:p-5 border-round border-1 border-color shadow-sm mb-3" v-if="session.note">
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
</DataTable>
|
</DataTable>
|
||||||
|
|
||||||
<div class="flex justify-content-end gap-2 mt-4 pt-3 border-top-1 border-color">
|
<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="انصراف" text severity="secondary" @click="$router.push('/attendances')" />
|
||||||
<Button
|
<Button
|
||||||
:label="$t('attendances.submitAttendance')"
|
:label="$t('attendances.submitAttendance')"
|
||||||
icon="pi pi-save"
|
icon="pi pi-save"
|
||||||
@@ -147,7 +147,7 @@ const handleSubmit = async () => {
|
|||||||
}));
|
}));
|
||||||
await attendanceApi.recordSessionAttendance(sessionId, payloadList);
|
await attendanceApi.recordSessionAttendance(sessionId, payloadList);
|
||||||
showSuccess('حضور و غیاب جلسه با موفقیت ثبت شد');
|
showSuccess('حضور و غیاب جلسه با موفقیت ثبت شد');
|
||||||
router.push('/sessions');
|
router.push('/attendances');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showError(err);
|
showError(err);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="attendance-list-view">
|
<div class="attendance-list-view">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="حضور و غیاب"
|
title="حضور و غیاب"
|
||||||
subtitle="ثبت و پیگیری حضور و غیاب جلسات آموزشی"
|
subtitle="جلسات سررسید گذشته که ثبت حضور دانشجویان آنها ناقص است"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
label="مشاهده جلسات"
|
label="مشاهده جلسات"
|
||||||
@@ -16,27 +16,27 @@
|
|||||||
<div class="grid mb-4">
|
<div class="grid mb-4">
|
||||||
<div class="col-12 sm:col-4">
|
<div class="col-12 sm:col-4">
|
||||||
<div class="surface-card p-4 border-round-xl border-1 border-color shadow-sm">
|
<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-muted text-xs block mb-1">در انتظار ثبت حضور</span>
|
||||||
<span class="text-3xl font-bold text-orange-500">{{ toPersianDigits(summary.pending) }}</span>
|
<span class="text-3xl font-bold text-orange-500">{{ toPersianDigits(summary.pending) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 sm:col-4">
|
<div class="col-12 sm:col-4">
|
||||||
<div class="surface-card p-4 border-round-xl border-1 border-color shadow-sm">
|
<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-muted text-xs block mb-1">ثبت کامل شده</span>
|
||||||
<span class="text-3xl font-bold text-green-500">{{ toPersianDigits(summary.recorded) }}</span>
|
<span class="text-3xl font-bold text-green-500">{{ toPersianDigits(summary.recorded) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 sm:col-4">
|
<div class="col-12 sm:col-4">
|
||||||
<div class="surface-card p-4 border-round-xl border-1 border-color shadow-sm">
|
<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-muted text-xs block mb-1">جلسات سررسید گذشته</span>
|
||||||
<span class="text-3xl font-bold text-color">{{ toPersianDigits(summary.total) }}</span>
|
<span class="text-3xl font-bold text-color">{{ toPersianDigits(summary.total) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DataTableWrapper
|
<DataTableWrapper
|
||||||
:items="displayItems"
|
:items="items"
|
||||||
:totalCount="displayTotal"
|
:totalCount="totalCount"
|
||||||
:page="queryParams.page"
|
:page="queryParams.page"
|
||||||
:limit="queryParams.limit"
|
:limit="queryParams.limit"
|
||||||
:sortBy="queryParams.sortBy"
|
:sortBy="queryParams.sortBy"
|
||||||
@@ -53,6 +53,7 @@
|
|||||||
optionLabel="label"
|
optionLabel="label"
|
||||||
optionValue="value"
|
optionValue="value"
|
||||||
class="text-sm"
|
class="text-sm"
|
||||||
|
@change="onAttendanceFilterChange"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -89,8 +90,8 @@
|
|||||||
<Column field="attendanceStatus" header="وضعیت حضور">
|
<Column field="attendanceStatus" header="وضعیت حضور">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Tag
|
<Tag
|
||||||
:value="hasAttendance(data) ? 'ثبت شده' : 'در انتظار ثبت'"
|
:value="getAttendanceStatus(data).label"
|
||||||
:severity="hasAttendance(data) ? 'success' : 'warn'"
|
:severity="getAttendanceStatus(data).severity"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
@@ -105,9 +106,9 @@
|
|||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Button
|
<Button
|
||||||
icon="pi pi-check-square"
|
icon="pi pi-check-square"
|
||||||
:label="hasAttendance(data) ? 'ویرایش' : 'ثبت'"
|
:label="data.attendanceComplete ? 'ویرایش' : 'ثبت'"
|
||||||
size="small"
|
size="small"
|
||||||
:severity="hasAttendance(data) ? 'secondary' : 'success'"
|
:severity="data.attendanceComplete ? 'secondary' : 'success'"
|
||||||
@click="$router.push(`/sessions/attendance/${data._id || data.id}`)"
|
@click="$router.push(`/sessions/attendance/${data._id || data.id}`)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -117,7 +118,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { useDataTable } from '@/composables/useDataTable';
|
import { useDataTable } from '@/composables/useDataTable';
|
||||||
import { usePersianDate } from '@/composables/usePersianDate';
|
import { usePersianDate } from '@/composables/usePersianDate';
|
||||||
import { sessionApi } from '@/api/sessionApi';
|
import { sessionApi } from '@/api/sessionApi';
|
||||||
@@ -131,15 +132,20 @@ import SelectButton from 'primevue/selectbutton';
|
|||||||
|
|
||||||
const { formatJalali, toPersianDigits } = usePersianDate();
|
const { formatJalali, toPersianDigits } = usePersianDate();
|
||||||
|
|
||||||
const attendanceFilter = ref('all');
|
const attendanceFilter = ref('pending');
|
||||||
const filterOptions = [
|
const filterOptions = [
|
||||||
{ label: 'همه', value: 'all' },
|
|
||||||
{ label: 'در انتظار ثبت', value: 'pending' },
|
{ label: 'در انتظار ثبت', value: 'pending' },
|
||||||
{ label: 'ثبت شده', value: 'recorded' }
|
{ label: 'ثبت شده', value: 'recorded' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const summary = ref({ pending: 0, recorded: 0, total: 0 });
|
const summary = ref({ pending: 0, recorded: 0, total: 0 });
|
||||||
|
|
||||||
|
const fetchAttendanceSessions = (params) =>
|
||||||
|
sessionApi.getAll({
|
||||||
|
...params,
|
||||||
|
attendanceScope: params.attendanceScope || attendanceFilter.value
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
totalCount,
|
totalCount,
|
||||||
@@ -149,38 +155,40 @@ const {
|
|||||||
onPageChange,
|
onPageChange,
|
||||||
onSort,
|
onSort,
|
||||||
onSearch
|
onSearch
|
||||||
} = useDataTable(sessionApi.getAll, {
|
} = useDataTable(fetchAttendanceSessions, {
|
||||||
sortBy: 'day',
|
sortBy: 'day',
|
||||||
sortOrder: 'desc'
|
sortOrder: 'desc',
|
||||||
|
attendanceScope: 'pending'
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasAttendance = (session) => Array.isArray(session.attendanceList) && session.attendanceList.length > 0;
|
const getAttendanceStatus = (session) => {
|
||||||
|
if (session.attendanceComplete) {
|
||||||
const displayItems = computed(() => {
|
return { label: 'ثبت شده', severity: 'success' };
|
||||||
if (attendanceFilter.value === 'pending') {
|
|
||||||
return items.value.filter((s) => !hasAttendance(s));
|
|
||||||
}
|
}
|
||||||
if (attendanceFilter.value === 'recorded') {
|
if (session.attendancePending) {
|
||||||
return items.value.filter((s) => hasAttendance(s));
|
return { label: 'در انتظار ثبت', severity: 'warn' };
|
||||||
}
|
}
|
||||||
return items.value;
|
return { label: 'برگزار نشده', severity: 'secondary' };
|
||||||
});
|
};
|
||||||
|
|
||||||
const displayTotal = computed(() => {
|
const onAttendanceFilterChange = () => {
|
||||||
if (attendanceFilter.value === 'all') return totalCount.value;
|
queryParams.attendanceScope = attendanceFilter.value;
|
||||||
return displayItems.value.length;
|
queryParams.page = 1;
|
||||||
});
|
loadData();
|
||||||
|
};
|
||||||
|
|
||||||
const loadSummary = async () => {
|
const loadSummary = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await sessionApi.getAll({ page: 1, limit: 500, sortBy: 'day', sortOrder: 'desc' });
|
const [pendingRes, recordedRes] = await Promise.all([
|
||||||
const data = res.data || res;
|
sessionApi.getAll({ page: 1, limit: 1, attendanceScope: 'pending', sortBy: 'day', sortOrder: 'desc' }),
|
||||||
const sessions = Array.isArray(data) ? data : (data.data || []);
|
sessionApi.getAll({ page: 1, limit: 1, attendanceScope: 'recorded', sortBy: 'day', sortOrder: 'desc' })
|
||||||
const total = res.meta?.totalCount ?? sessions.length;
|
]);
|
||||||
|
const pending = pendingRes.meta?.totalCount ?? 0;
|
||||||
|
const recorded = recordedRes.meta?.totalCount ?? 0;
|
||||||
summary.value = {
|
summary.value = {
|
||||||
total,
|
pending,
|
||||||
recorded: sessions.filter((s) => hasAttendance(s)).length,
|
recorded,
|
||||||
pending: sessions.filter((s) => !hasAttendance(s)).length
|
total: pending + recorded
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
summary.value = { pending: 0, recorded: 0, total: 0 };
|
summary.value = { pending: 0, recorded: 0, total: 0 };
|
||||||
|
|||||||
Reference in New Issue
Block a user