@@ -249,6 +287,7 @@ 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 },
@@ -260,7 +299,7 @@ const props = defineProps({
endTime: { type: String, default: '' }
});
-const { toPersianDigits, toLatinDigits, formatJalali } = usePersianDate();
+const { toPersianDigits, toLatinDigits, formatJalali, getTodayJalali } = usePersianDate();
const { showSuccess, showError } = useToast();
const todayJalali = () => moment().locale('fa').format('jYYYY/jMM/jDD');
@@ -278,6 +317,35 @@ const bulkStatus = ref(null);
const generatedSessions = ref([]);
const manualSessions = ref([{ date: todayJalali(), 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' },
diff --git a/src/components/common/StatusTag.vue b/src/components/common/StatusTag.vue
index 052e7ff..f97be67 100644
--- a/src/components/common/StatusTag.vue
+++ b/src/components/common/StatusTag.vue
@@ -44,7 +44,7 @@ const NOTIFICATION_LABELS = {
const PAYMENT_LABELS = {
paid: 'تسویهشده',
- partial: 'پرداخت جزئی',
+ partial: 'پیش پرداخت',
pending: 'در انتظار پرداخت',
overdue: 'معوق',
cancelled: 'لغوشده'
@@ -63,7 +63,7 @@ const severity = computed(() => {
if (props.type === 'payment') {
if (val === 'paid' || val === 'تسویه شده' || val === 'تسویهشده') return 'success';
- if (val === 'partial' || val === 'پرداخت جزئی') return 'contrast';
+ if (val === 'partial' || val === 'پرداخت جزئی' || val === 'پیش پرداخت') return 'contrast';
if (val === 'pending' || val === 'در انتظار پرداخت') return 'info';
if (val === 'overdue' || val === 'معوق شده' || val === 'معوق') return 'danger';
if (val === 'cancelled' || val === 'canceled' || val === 'لغوشده') return 'secondary';
diff --git a/src/views/attendances/AttendanceEntryView.vue b/src/views/attendances/AttendanceEntryView.vue
index 6bfafcd..31a135f 100644
--- a/src/views/attendances/AttendanceEntryView.vue
+++ b/src/views/attendances/AttendanceEntryView.vue
@@ -5,9 +5,48 @@
:title="$t('attendances.title')"
:subtitle="`دوره: ${session.course?.title || '-'} | کلاس: ${session.class?.name || '-'} | تاریخ: ${formatJalali(session.day)} (${session.startTime || ''} تا ${session.endTime || ''})`"
>
-
{
+ if (!sessionId) return;
+ isNotifying.value = true;
+ try {
+ const res = await sessionApi.notifyHolding(sessionId);
+ const count = res.data?.sentCount || res.data?.totalStudents || '';
+ showSuccess(`پیامک برگزاری جلسه با موفقیت به ${count ? toPersianDigits(count) + ' نفر از ' : ''}دانشجویان ارسال شد`);
+ notifyDialogVisible.value = false;
+ } catch (err) {
+ showError(err);
+ } finally {
+ isNotifying.value = false;
+ }
+};
const statusOptions = [
{ label: 'حاضر', value: 'present' },
diff --git a/src/views/payments/PaymentDetailView.vue b/src/views/payments/PaymentDetailView.vue
index df14fb0..6e1a8ed 100644
--- a/src/views/payments/PaymentDetailView.vue
+++ b/src/views/payments/PaymentDetailView.vue
@@ -63,7 +63,7 @@
تاریخچه تراکنشهای دریافتی
-
+
@@ -316,6 +316,16 @@ const defaultTrxForm = () => ({
const trxForm = reactive(defaultTrxForm());
+const openTransactionModal = () => {
+ Object.assign(trxForm, defaultTrxForm());
+ if (remainingAmount.value > 0) {
+ trxForm.amount = remainingAmount.value;
+ }
+ trxForm.dueDate = getTodayJalali();
+ trxForm.date = getTodayJalali();
+ showTransactionModal.value = true;
+};
+
const editForm = reactive({
amount: 0,
status: 'paid',
diff --git a/src/views/payments/PaymentListView.vue b/src/views/payments/PaymentListView.vue
index ad32a0c..5152b4a 100644
--- a/src/views/payments/PaymentListView.vue
+++ b/src/views/payments/PaymentListView.vue
@@ -225,7 +225,7 @@ const createForm = reactive({
amount: 0,
discount: 0,
notes: '',
- dueDate: ''
+ dueDate: getTodayJalali()
});
const hasDiscount = ref(false);
const createNotify = reactive({ sms: true, email: true, bot: true });
@@ -287,10 +287,8 @@ const onClassesSelected = () => {
if ((createForm.discount || 0) > (createForm.amount || 0)) {
createForm.discount = createForm.amount || 0;
}
-
- const firstSelectedClass = classesList.value.find((item) => String(item._id || item.id) === String(createForm.classes[0]));
- if (firstSelectedClass) {
- createForm.dueDate = calculateClassMidDate(firstSelectedClass);
+ if (!createForm.dueDate) {
+ createForm.dueDate = getTodayJalali();
}
};
diff --git a/src/views/sessions/SessionFormView.vue b/src/views/sessions/SessionFormView.vue
index 21a78a7..9ace9c5 100644
--- a/src/views/sessions/SessionFormView.vue
+++ b/src/views/sessions/SessionFormView.vue
@@ -112,7 +112,7 @@ import Button from 'primevue/button';
const route = useRoute();
const router = useRouter();
const { showSuccess, showError } = useToast();
-const { toLatinDigits } = usePersianDate();
+const { toLatinDigits, getTodayJalali } = usePersianDate();
const sessionId = route.params.id;
const isEditMode = computed(() => !!sessionId);
diff --git a/src/views/sessions/SessionListView.vue b/src/views/sessions/SessionListView.vue
index bd17852..079efcf 100644
--- a/src/views/sessions/SessionListView.vue
+++ b/src/views/sessions/SessionListView.vue
@@ -116,7 +116,7 @@
-
+
@@ -131,6 +131,19 @@
/>
+
+
+
+
@@ -150,6 +163,34 @@
:message="bulkDeleteMessage"
@confirm="handleDeleteConfirm"
/>
+
+
@@ -170,6 +211,7 @@ import Column from 'primevue/column';
import Tag from 'primevue/tag';
import Select from 'primevue/select';
import Dropdown from 'primevue/select';
+import Dialog from 'primevue/dialog';
const { formatJalali, toPersianDigits } = usePersianDate();
const { showSuccess, showError } = useToast();
@@ -200,6 +242,35 @@ const selectedSession = ref(null);
const bulkDeleteMode = ref(false);
const isDeleting = ref(false);
+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' },