diff --git a/src/components/common/LoadingOverlay.vue b/src/components/common/LoadingOverlay.vue
new file mode 100644
index 0000000..30c8c71
--- /dev/null
+++ b/src/components/common/LoadingOverlay.vue
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+ {{ message }}
+ {{ subMessage }}
+
+
+
+
+
+
+
+
+
diff --git a/src/views/classes/ClassDetailView.vue b/src/views/classes/ClassDetailView.vue
index 4d384c1..acfc311 100644
--- a/src/views/classes/ClassDetailView.vue
+++ b/src/views/classes/ClassDetailView.vue
@@ -1,6 +1,7 @@
-
-
+
@@ -256,6 +258,7 @@ import { useToast } from '@/composables/useToast';
import { usePermission } from '@/composables/usePermission';
import { getPayableAmount } from '@/utils/paymentAmount';
import PageHeader from '@/components/common/PageHeader.vue';
+import LoadingOverlay from '@/components/common/LoadingOverlay.vue';
import StatusTag from '@/components/common/StatusTag.vue';
import TableSkeleton from '@/components/common/TableSkeleton.vue';
import ConfirmDeleteDialog from '@/components/common/ConfirmDeleteDialog.vue';
@@ -402,12 +405,17 @@ const removeDialogMessage = computed(() => {
: 'آیا این دانشجو از کلاس حذف شود؟';
});
+const isFetching = ref(true);
+
const fetchClass = async () => {
+ isFetching.value = true;
try {
const res = await classApi.getOne(classId);
classData.value = res.data || res;
} catch (err) {
showError(err);
+ } finally {
+ isFetching.value = false;
}
};
diff --git a/src/views/classes/ClassFormView.vue b/src/views/classes/ClassFormView.vue
index 3725f60..d66d2d2 100644
--- a/src/views/classes/ClassFormView.vue
+++ b/src/views/classes/ClassFormView.vue
@@ -24,7 +24,8 @@
-
@@ -67,6 +69,7 @@ import { courseApi } from '@/api/courseApi';
import { sessionApi } from '@/api/sessionApi';
import { usePersianDate } from '@/composables/usePersianDate';
import PageHeader from '@/components/common/PageHeader.vue';
+import LoadingOverlay from '@/components/common/LoadingOverlay.vue';
import PermissionGate from '@/components/common/PermissionGate.vue';
import StatusTag from '@/components/common/StatusTag.vue';
import Button from 'primevue/button';
@@ -78,10 +81,12 @@ const route = useRoute();
const courseId = route.params.id;
const { toPersianDigits, formatJalali } = usePersianDate();
+const isFetching = ref(true);
const course = ref(null);
const courseSessions = ref([]);
const fetchDetail = async () => {
+ isFetching.value = true;
try {
const res = await courseApi.getOne(courseId);
course.value = res.data || res;
@@ -99,6 +104,8 @@ const fetchDetail = async () => {
courseSessions.value = Array.isArray(list) ? list : [];
} catch (e) {
console.warn('Fetch course detail error:', e);
+ } finally {
+ isFetching.value = false;
}
};
diff --git a/src/views/courses/CourseFormView.vue b/src/views/courses/CourseFormView.vue
index e9fb103..05f9f21 100644
--- a/src/views/courses/CourseFormView.vue
+++ b/src/views/courses/CourseFormView.vue
@@ -9,7 +9,8 @@
-