635 lines
24 KiB
Vue
635 lines
24 KiB
Vue
<!-- Sessions management for a class: list existing, bulk create when empty, manual add at bottom -->
|
||
<template>
|
||
<div class="class-sessions-section flex flex-column gap-4">
|
||
<!-- Always show sessions list below students -->
|
||
<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 flex-wrap gap-2">
|
||
<div>
|
||
<h2 class="text-lg font-bold m-0">جلسات کلاس</h2>
|
||
<p class="text-muted text-sm m-0 mt-1">جلسات برنامهریزیشده برای این کلاس</p>
|
||
</div>
|
||
<div class="flex align-items-center gap-2 flex-wrap">
|
||
<Tag :value="`${toPersianDigits(sessions.length)} جلسه`" severity="info" />
|
||
<template v-if="selectedSessions.length">
|
||
<Tag :value="`${toPersianDigits(selectedSessions.length)} انتخابشده`" />
|
||
<Select
|
||
v-model="bulkStatus"
|
||
:options="statusOptions"
|
||
optionLabel="label"
|
||
optionValue="value"
|
||
placeholder="تغییر وضعیت"
|
||
class="text-sm"
|
||
style="min-width: 9rem"
|
||
/>
|
||
<Button
|
||
label="اعمال"
|
||
icon="pi pi-check"
|
||
size="small"
|
||
:loading="isBulkUpdating"
|
||
:disabled="!bulkStatus"
|
||
@click="applyBulkStatus"
|
||
/>
|
||
<Button
|
||
label="حذف"
|
||
icon="pi pi-trash"
|
||
size="small"
|
||
severity="danger"
|
||
outlined
|
||
@click="confirmBulkDelete"
|
||
/>
|
||
<Button label="لغو" text size="small" severity="secondary" @click="selectedSessions = []" />
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="loadingSessions" class="mb-2">
|
||
<TableSkeleton :rows="6" :columns="6" />
|
||
</div>
|
||
|
||
<DataTable
|
||
v-else
|
||
v-model:selection="selectedSessions"
|
||
:value="sessions"
|
||
dataKey="_id"
|
||
class="p-datatable-sm text-sm"
|
||
emptyMessage="جلسهای ثبت نشده است"
|
||
>
|
||
<Column selectionMode="multiple" headerStyle="width: 3rem" />
|
||
<Column header="#" style="width: 50px">
|
||
<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="وضعیت" style="width: 100px">
|
||
<template #body="{ data }">
|
||
<StatusTag :status="data.status || 'scheduled'" type="session" />
|
||
</template>
|
||
</Column>
|
||
<Column header="عملیات" style="width: 170px">
|
||
<template #body="{ data }">
|
||
<div class="flex align-items-center gap-1">
|
||
<Button
|
||
icon="pi pi-check-square"
|
||
text
|
||
rounded
|
||
size="small"
|
||
severity="success"
|
||
v-tooltip.top="'حضور و غیاب'"
|
||
@click="$router.push(`/sessions/attendance/${data._id || data.id}`)"
|
||
/>
|
||
<Button
|
||
icon="pi pi-send"
|
||
text
|
||
rounded
|
||
size="small"
|
||
severity="info"
|
||
v-tooltip.top="'اطلاعرسانی برگزاری جلسه'"
|
||
:loading="notifyingSessionId === (data._id || data.id)"
|
||
@click="openNotifyHoldingDialog(data)"
|
||
/>
|
||
<Button
|
||
icon="pi pi-pencil"
|
||
text
|
||
rounded
|
||
size="small"
|
||
severity="warning"
|
||
v-tooltip.top="'ویرایش'"
|
||
@click="$router.push(`/sessions/edit/${data._id || data.id}`)"
|
||
/>
|
||
<Button
|
||
icon="pi pi-trash"
|
||
text
|
||
rounded
|
||
size="small"
|
||
severity="danger"
|
||
v-tooltip.top="'حذف'"
|
||
@click="confirmDeleteOne(data)"
|
||
/>
|
||
</div>
|
||
</template>
|
||
</Column>
|
||
</DataTable>
|
||
</div>
|
||
|
||
<ConfirmDeleteDialog
|
||
v-model="deleteDialogVisible"
|
||
:loading="isDeleting"
|
||
:title="bulkDeleteMode ? 'حذف گروهی جلسات' : 'حذف جلسه'"
|
||
:message="deleteMessage"
|
||
@confirm="handleDeleteConfirm"
|
||
/>
|
||
|
||
<Dialog
|
||
v-model:visible="notifyDialogVisible"
|
||
header="اطلاعرسانی برگزاری جلسه"
|
||
modal
|
||
:style="{ width: '450px' }"
|
||
>
|
||
<div class="flex flex-column gap-3 py-2">
|
||
<p class="m-0 line-height-3 text-sm">
|
||
آیا از ارسال پیامک برگزاری جلسه طبق برنامه به تمامی دانشجویان این کلاس اطمینان دارید؟
|
||
</p>
|
||
<div v-if="selectedSessionForNotify" class="surface-100 p-3 border-round text-xs flex flex-column gap-1">
|
||
<div><strong>موضوع جلسه:</strong> {{ selectedSessionForNotify.topic || 'طبق برنامه' }}</div>
|
||
<div><strong>تاریخ:</strong> {{ formatJalali(selectedSessionForNotify.day || selectedSessionForNotify.date) }}</div>
|
||
<div><strong>ساعت:</strong> {{ selectedSessionForNotify.startTime || '—' }} تا {{ selectedSessionForNotify.endTime || '—' }}</div>
|
||
</div>
|
||
</div>
|
||
<template #footer>
|
||
<Button label="انصراف" text severity="secondary" @click="notifyDialogVisible = false" />
|
||
<Button
|
||
label="ارسال پیامک"
|
||
icon="pi pi-send"
|
||
severity="info"
|
||
:loading="isNotifying"
|
||
@click="handleSendSessionHolding"
|
||
/>
|
||
</template>
|
||
</Dialog>
|
||
|
||
<!-- Bulk auto-create: only when no sessions yet -->
|
||
<div v-if="!hasSessions" class="surface-card p-4 sm:p-5 border-round border-1 border-color shadow-sm">
|
||
<h2 class="text-lg font-bold text-color mb-1">ایجاد جلسات کلاس</h2>
|
||
<p class="text-muted text-sm mb-3">جلسات را بهصورت خودکار بر اساس روزهای هفته بسازید، یا از بخش دستی پایین صفحه استفاده کنید.</p>
|
||
|
||
<div class="grid">
|
||
<div class="col-12 sm:col-6 md:col-3 flex flex-column gap-2">
|
||
<label class="font-semibold text-sm">تعداد جلسات</label>
|
||
<InputNumber v-model="autoForm.sessionCount" :min="1" class="w-full text-sm" />
|
||
</div>
|
||
<div class="col-12 sm:col-6 md:col-3 flex flex-column gap-2">
|
||
<label class="font-semibold text-sm">تاریخ شروع</label>
|
||
<DatePicker v-model="autoForm.startDate" class="w-full text-sm" :placeholder="getTodayJalali()" />
|
||
</div>
|
||
<div class="col-12 sm:col-6 md:col-3 flex flex-column gap-2">
|
||
<label class="font-semibold text-sm">ساعت شروع</label>
|
||
<InputText v-model="autoForm.startTime" class="w-full text-sm" placeholder="19:00" dir="ltr" />
|
||
</div>
|
||
<div class="col-12 sm:col-6 md:col-3 flex flex-column gap-2">
|
||
<label class="font-semibold text-sm">ساعت پایان</label>
|
||
<InputText v-model="autoForm.endTime" class="w-full text-sm" placeholder="20:30" dir="ltr" />
|
||
</div>
|
||
<div class="col-12 flex flex-column gap-2 my-2">
|
||
<label class="font-semibold text-sm">روزهای هفته</label>
|
||
<SelectButton v-model="autoForm.selectedDays" :options="weekdays" optionLabel="label" optionValue="value" multiple class="text-sm" />
|
||
</div>
|
||
<div class="col-12">
|
||
<Button label="پیشنمایش و ساخت جلسات" icon="pi pi-cog" severity="help" @click="generateSessionsPreview" />
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="generatedSessions.length > 0" class="mt-4 pt-3 border-top-1 border-color">
|
||
<h3 class="text-md font-bold mb-3">پیشنمایش جلسات (قابل ویرایش)</h3>
|
||
<DataTable :value="generatedSessions" class="p-datatable-sm text-sm">
|
||
<Column field="sessionNumber" header="جلسه #" style="width: 70px">
|
||
<template #body="{ data }">{{ toPersianDigits(data.sessionNumber) }}</template>
|
||
</Column>
|
||
<Column field="date" header="تاریخ">
|
||
<template #body="{ data }">
|
||
<InputText v-model="data.date" class="w-full p-inputtext-sm text-sm" />
|
||
</template>
|
||
</Column>
|
||
<Column field="startTime" header="ساعت شروع">
|
||
<template #body="{ data }">
|
||
<InputText v-model="data.startTime" class="w-full p-inputtext-sm text-sm" dir="ltr" />
|
||
</template>
|
||
</Column>
|
||
<Column field="endTime" header="ساعت پایان">
|
||
<template #body="{ data }">
|
||
<InputText v-model="data.endTime" class="w-full p-inputtext-sm text-sm" dir="ltr" />
|
||
</template>
|
||
</Column>
|
||
<Column field="topic" header="موضوع جلسه">
|
||
<template #body="{ data }">
|
||
<InputText v-model="data.topic" class="w-full p-inputtext-sm text-sm" />
|
||
</template>
|
||
</Column>
|
||
</DataTable>
|
||
<div class="flex justify-content-end mt-3">
|
||
<Button label="ثبت نهایی جلسات" icon="pi pi-save" severity="success" :loading="isSaving" @click="submitGeneratedSessions" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Manual create: always at the bottom -->
|
||
<div class="surface-card p-4 sm:p-5 border-round border-1 border-color shadow-sm">
|
||
<h2 class="text-lg font-bold text-color mb-1">
|
||
{{ hasSessions ? 'افزودن جلسه دستی' : 'تعریف دستی جلسات' }}
|
||
</h2>
|
||
<p class="text-muted text-sm mb-3">
|
||
{{ hasSessions ? 'در صورت نیاز میتوانید یک یا چند جلسه اضافه کنید.' : 'جلسات را یکییکی تعریف کنید.' }}
|
||
</p>
|
||
|
||
<div v-for="(session, index) in manualSessions" :key="index" class="grid align-items-center mb-2 p-2 border-round surface-ground">
|
||
<div class="col-12 sm:col-1 text-center font-bold">#{{ toPersianDigits(index + 1) }}</div>
|
||
<div class="col-12 sm:col-3">
|
||
<InputText v-model="session.date" :placeholder="`تاریخ (${todayJalali()})`" class="w-full text-sm" />
|
||
</div>
|
||
<div class="col-6 sm:col-2">
|
||
<InputText v-model="session.startTime" placeholder="ساعت شروع" class="w-full text-sm" dir="ltr" />
|
||
</div>
|
||
<div class="col-6 sm:col-2">
|
||
<InputText v-model="session.endTime" placeholder="ساعت پایان" class="w-full text-sm" dir="ltr" />
|
||
</div>
|
||
<div class="col-12 sm:col-3">
|
||
<InputText v-model="session.topic" placeholder="موضوع جلسه" class="w-full text-sm" />
|
||
</div>
|
||
<div class="col-12 sm:col-1 text-center">
|
||
<Button
|
||
icon="pi pi-times"
|
||
text
|
||
rounded
|
||
severity="danger"
|
||
size="small"
|
||
:disabled="manualSessions.length === 1"
|
||
@click="manualSessions.splice(index, 1)"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex align-items-center justify-content-between mt-3">
|
||
<Button
|
||
label="افزودن ردیف"
|
||
icon="pi pi-plus"
|
||
text
|
||
@click="manualSessions.push({ date: todayJalali(), startTime: '19:00', endTime: '20:30', topic: '' })"
|
||
/>
|
||
<Button label="ثبت جلسات" icon="pi pi-save" severity="success" :loading="isSaving" @click="submitManualSessions" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, computed, watch } from 'vue';
|
||
import moment from 'jalali-moment';
|
||
import { sessionApi } from '@/api/sessionApi';
|
||
import { usePersianDate } from '@/composables/usePersianDate';
|
||
import { useToast } from '@/composables/useToast';
|
||
import StatusTag from '@/components/common/StatusTag.vue';
|
||
import ConfirmDeleteDialog from '@/components/common/ConfirmDeleteDialog.vue';
|
||
import TableSkeleton from '@/components/common/TableSkeleton.vue';
|
||
import InputText from 'primevue/inputtext';
|
||
import InputNumber from 'primevue/inputnumber';
|
||
import Button from 'primevue/button';
|
||
import Tag from 'primevue/tag';
|
||
import Select from 'primevue/select';
|
||
import SelectButton from 'primevue/selectbutton';
|
||
import DatePicker from 'vue3-persian-datetime-picker';
|
||
import DataTable from 'primevue/datatable';
|
||
import Column from 'primevue/column';
|
||
import Dialog from 'primevue/dialog';
|
||
|
||
const props = defineProps({
|
||
classId: { type: [String, Object], required: true },
|
||
courseId: { type: [String, Object], required: true },
|
||
professorId: { type: [String, Object], default: null },
|
||
defaultSessionCount: { type: Number, default: 12 },
|
||
days: { type: Array, default: () => [] },
|
||
startTime: { type: String, default: '' },
|
||
endTime: { type: String, default: '' }
|
||
});
|
||
|
||
const { toPersianDigits, toLatinDigits, toGregorianIso, formatJalali, getTodayJalali } = usePersianDate();
|
||
const { showSuccess, showError } = useToast();
|
||
|
||
const sessions = ref([]);
|
||
const selectedSessions = ref([]);
|
||
const loadingSessions = ref(false);
|
||
const isSaving = ref(false);
|
||
const isDeleting = ref(false);
|
||
const isBulkUpdating = ref(false);
|
||
const deleteDialogVisible = ref(false);
|
||
const bulkDeleteMode = ref(false);
|
||
const sessionToDelete = ref(null);
|
||
const bulkStatus = ref(null);
|
||
const generatedSessions = ref([]);
|
||
const manualSessions = ref([{ date: getTodayJalali(), startTime: '19:00', endTime: '20:30', topic: '' }]);
|
||
|
||
const notifyDialogVisible = ref(false);
|
||
const selectedSessionForNotify = ref(null);
|
||
const notifyingSessionId = ref(null);
|
||
const isNotifying = ref(false);
|
||
|
||
const openNotifyHoldingDialog = (session) => {
|
||
selectedSessionForNotify.value = session;
|
||
notifyDialogVisible.value = true;
|
||
};
|
||
|
||
const handleSendSessionHolding = async () => {
|
||
if (!selectedSessionForNotify.value) return;
|
||
const id = selectedSessionForNotify.value._id || selectedSessionForNotify.value.id;
|
||
notifyingSessionId.value = id;
|
||
isNotifying.value = true;
|
||
try {
|
||
const res = await sessionApi.notifyHolding(id);
|
||
const count = res.data?.sentCount || res.data?.totalStudents || '';
|
||
showSuccess(`پیامک برگزاری جلسه با موفقیت به ${count ? toPersianDigits(count) + ' نفر از ' : ''}دانشجویان ارسال شد`);
|
||
notifyDialogVisible.value = false;
|
||
selectedSessionForNotify.value = null;
|
||
} catch (err) {
|
||
showError(err);
|
||
} finally {
|
||
notifyingSessionId.value = null;
|
||
isNotifying.value = false;
|
||
}
|
||
};
|
||
|
||
const statusOptions = [
|
||
{ label: 'طبق برنامه', value: 'scheduled' },
|
||
{ label: 'برگزار شده', value: 'held' },
|
||
{ label: 'لغو شده', value: 'cancelled' }
|
||
];
|
||
|
||
const selectedIds = computed(() =>
|
||
selectedSessions.value.map((s) => s._id || s.id).filter(Boolean)
|
||
);
|
||
|
||
const deleteMessage = computed(() => {
|
||
if (bulkDeleteMode.value) {
|
||
return `آیا از حذف ${toPersianDigits(selectedSessions.value.length)} جلسه انتخابشده اطمینان دارید؟ این عملیات قابل بازگشت نیست.`;
|
||
}
|
||
return 'آیا از حذف این جلسه اطمینان دارید؟ این عملیات قابل بازگشت نیست.';
|
||
});
|
||
|
||
const resolvedClassId = computed(() => {
|
||
const value = props.classId;
|
||
if (!value) return null;
|
||
return typeof value === 'object' ? (value._id || value.id || null) : String(value);
|
||
});
|
||
|
||
const resolvedCourseId = computed(() => {
|
||
const value = props.courseId;
|
||
if (!value) return null;
|
||
return typeof value === 'object' ? (value._id || value.id || null) : String(value);
|
||
});
|
||
|
||
const resolvedProfessorId = computed(() => {
|
||
const value = props.professorId;
|
||
if (!value) return null;
|
||
return typeof value === 'object' ? (value._id || value.id || null) : String(value);
|
||
});
|
||
|
||
const hasSessions = computed(() => sessions.value.length > 0);
|
||
|
||
const normalizeList = (response) => {
|
||
if (!response) return [];
|
||
if (Array.isArray(response)) return response;
|
||
if (Array.isArray(response.data)) return response.data;
|
||
if (Array.isArray(response.items)) return response.items;
|
||
if (Array.isArray(response.sessions)) return response.sessions;
|
||
if (response.data && typeof response.data === 'object') {
|
||
if (Array.isArray(response.data.data)) return response.data.data;
|
||
if (Array.isArray(response.data.items)) return response.data.items;
|
||
if (Array.isArray(response.data.sessions)) return response.data.sessions;
|
||
}
|
||
return [];
|
||
};
|
||
|
||
const weekdays = [
|
||
{ label: 'شنبه', value: 6 },
|
||
{ label: 'یکشنبه', value: 0 },
|
||
{ label: 'دوشنبه', value: 1 },
|
||
{ label: 'سهشنبه', value: 2 },
|
||
{ label: 'چهارشنبه', value: 3 },
|
||
{ label: 'پنجشنبه', value: 4 },
|
||
{ label: 'جمعه', value: 5 }
|
||
];
|
||
|
||
const autoForm = reactive({
|
||
sessionCount: props.defaultSessionCount || 12,
|
||
startDate: getTodayJalali(),
|
||
startTime: props.startTime || '19:00',
|
||
endTime: props.endTime || '20:30',
|
||
selectedDays: props.days?.length ? [...props.days] : [6, 2]
|
||
});
|
||
|
||
watch(
|
||
() => [props.days, props.startTime, props.endTime, props.defaultSessionCount],
|
||
() => {
|
||
if (props.days?.length) autoForm.selectedDays = [...props.days];
|
||
if (props.startTime) autoForm.startTime = props.startTime;
|
||
if (props.endTime) autoForm.endTime = props.endTime;
|
||
if (props.defaultSessionCount) autoForm.sessionCount = props.defaultSessionCount;
|
||
},
|
||
{ deep: true }
|
||
);
|
||
|
||
const toSessionDayIso = (value) => {
|
||
if (!value) return null;
|
||
return toGregorianIso(value) || null;
|
||
};
|
||
|
||
const fetchSessions = async () => {
|
||
const classId = resolvedClassId.value;
|
||
if (!classId) return;
|
||
loadingSessions.value = true;
|
||
try {
|
||
// Use schema field `class` only. Sending classId alongside it makes older
|
||
// API builds add `{ classId: ... }` to the Mongo filter and return 0 rows.
|
||
const res = await sessionApi.getAll({
|
||
limit: 100,
|
||
class: classId,
|
||
sortBy: 'day',
|
||
sortOrder: 'asc'
|
||
});
|
||
sessions.value = normalizeList(res);
|
||
selectedSessions.value = [];
|
||
} catch (err) {
|
||
showError(err);
|
||
sessions.value = [];
|
||
} finally {
|
||
loadingSessions.value = false;
|
||
}
|
||
};
|
||
|
||
const confirmDeleteOne = (session) => {
|
||
bulkDeleteMode.value = false;
|
||
sessionToDelete.value = session;
|
||
deleteDialogVisible.value = true;
|
||
};
|
||
|
||
const confirmBulkDelete = () => {
|
||
if (!selectedSessions.value.length) return;
|
||
bulkDeleteMode.value = true;
|
||
sessionToDelete.value = null;
|
||
deleteDialogVisible.value = true;
|
||
};
|
||
|
||
const handleDeleteConfirm = async () => {
|
||
isDeleting.value = true;
|
||
try {
|
||
if (bulkDeleteMode.value) {
|
||
const res = await sessionApi.bulkDelete(selectedIds.value);
|
||
const deletedCount = res?.data?.deletedCount ?? selectedIds.value.length;
|
||
showSuccess(`${toPersianDigits(deletedCount)} جلسه حذف شد`);
|
||
selectedSessions.value = [];
|
||
} else if (sessionToDelete.value) {
|
||
await sessionApi.delete(sessionToDelete.value._id || sessionToDelete.value.id);
|
||
showSuccess('جلسه با موفقیت حذف شد');
|
||
}
|
||
deleteDialogVisible.value = false;
|
||
await fetchSessions();
|
||
} catch (err) {
|
||
showError(err);
|
||
} finally {
|
||
isDeleting.value = false;
|
||
}
|
||
};
|
||
|
||
const applyBulkStatus = async () => {
|
||
if (!selectedIds.value.length || !bulkStatus.value) return;
|
||
isBulkUpdating.value = true;
|
||
try {
|
||
const res = await sessionApi.bulkUpdateStatus(selectedIds.value, bulkStatus.value);
|
||
const updatedCount = res?.data?.updatedCount ?? selectedIds.value.length;
|
||
showSuccess(`وضعیت ${toPersianDigits(updatedCount)} جلسه بهروز شد`);
|
||
bulkStatus.value = null;
|
||
selectedSessions.value = [];
|
||
await fetchSessions();
|
||
} catch (err) {
|
||
showError(err);
|
||
} finally {
|
||
isBulkUpdating.value = false;
|
||
}
|
||
};
|
||
|
||
const resetCreationForms = () => {
|
||
generatedSessions.value = [];
|
||
manualSessions.value = [{ date: getTodayJalali(), startTime: '19:00', endTime: '20:30', topic: '' }];
|
||
};
|
||
|
||
const generateSessionsPreview = () => {
|
||
if (!autoForm.startDate) {
|
||
showError('لطفا تاریخ شروع را وارد کنید');
|
||
return;
|
||
}
|
||
if (!autoForm.selectedDays?.length) {
|
||
showError('لطفا حداقل یک روز از هفته را انتخاب کنید');
|
||
return;
|
||
}
|
||
|
||
const list = [];
|
||
const latinStartDate = toLatinDigits(autoForm.startDate);
|
||
let currentMoment = moment(latinStartDate, ['jYYYY/jMM/jDD', 'YYYY-MM-DD', 'YYYY/MM/DD', moment.ISO_8601]);
|
||
if (!currentMoment.isValid()) {
|
||
showError('تاریخ شروع نامعتبر است');
|
||
return;
|
||
}
|
||
|
||
let count = 0;
|
||
let safetyLimit = 365 * 5;
|
||
const targetCount = Math.max(1, parseInt(autoForm.sessionCount, 10) || 12);
|
||
|
||
while (count < targetCount && safetyLimit > 0) {
|
||
safetyLimit--;
|
||
if (autoForm.selectedDays.includes(currentMoment.day())) {
|
||
count++;
|
||
list.push({
|
||
sessionNumber: count,
|
||
date: toLatinDigits(currentMoment.locale('fa').format('jYYYY/jMM/jDD')),
|
||
startTime: autoForm.startTime,
|
||
endTime: autoForm.endTime,
|
||
topic: `جلسه ${count}`
|
||
});
|
||
}
|
||
if (count >= targetCount) break;
|
||
currentMoment.add(1, 'day');
|
||
}
|
||
generatedSessions.value = list;
|
||
};
|
||
|
||
const ensureIds = () => {
|
||
if (!resolvedClassId.value) {
|
||
showError('ابتدا کلاس را ذخیره کنید');
|
||
return false;
|
||
}
|
||
if (!resolvedCourseId.value) {
|
||
showError('دوره مرتبط با کلاس مشخص نیست');
|
||
return false;
|
||
}
|
||
if (!resolvedProfessorId.value) {
|
||
showError('لطفا استاد کلاس را انتخاب و ذخیره کنید');
|
||
return false;
|
||
}
|
||
return true;
|
||
};
|
||
|
||
const submitGeneratedSessions = async () => {
|
||
if (!ensureIds()) return;
|
||
isSaving.value = true;
|
||
try {
|
||
await sessionApi.create({
|
||
courseId: resolvedCourseId.value,
|
||
classId: resolvedClassId.value,
|
||
professorId: resolvedProfessorId.value,
|
||
sessions: generatedSessions.value.map((s) => ({
|
||
topic: s.topic,
|
||
startTime: s.startTime,
|
||
endTime: s.endTime,
|
||
day: toSessionDayIso(s.date)
|
||
}))
|
||
});
|
||
showSuccess('جلسات کلاس با موفقیت ایجاد شدند');
|
||
resetCreationForms();
|
||
await fetchSessions();
|
||
} catch (err) {
|
||
showError(err);
|
||
} finally {
|
||
isSaving.value = false;
|
||
}
|
||
};
|
||
|
||
const submitManualSessions = async () => {
|
||
if (!ensureIds()) return;
|
||
const invalid = manualSessions.value.some((s) => !s.date || !s.startTime || !s.endTime);
|
||
if (invalid) {
|
||
showError('تاریخ و ساعت همه جلسات الزامی است');
|
||
return;
|
||
}
|
||
isSaving.value = true;
|
||
try {
|
||
await sessionApi.create({
|
||
courseId: resolvedCourseId.value,
|
||
classId: resolvedClassId.value,
|
||
professorId: resolvedProfessorId.value,
|
||
sessions: manualSessions.value.map((s) => ({
|
||
topic: s.topic,
|
||
startTime: s.startTime,
|
||
endTime: s.endTime,
|
||
day: toSessionDayIso(s.date)
|
||
}))
|
||
});
|
||
showSuccess('جلسات با موفقیت ثبت شدند');
|
||
resetCreationForms();
|
||
await fetchSessions();
|
||
} catch (err) {
|
||
showError(err);
|
||
} finally {
|
||
isSaving.value = false;
|
||
}
|
||
};
|
||
|
||
watch(
|
||
() => props.defaultSessionCount,
|
||
(val) => {
|
||
if (val && !hasSessions.value) autoForm.sessionCount = val;
|
||
}
|
||
);
|
||
|
||
watch(resolvedClassId, (id) => {
|
||
if (id) fetchSessions();
|
||
}, { immediate: true });
|
||
|
||
defineExpose({ fetchSessions });
|
||
</script>
|