diff --git a/src/api/authApi.js b/src/api/authApi.js index 10759ab..4e88027 100644 --- a/src/api/authApi.js +++ b/src/api/authApi.js @@ -5,5 +5,6 @@ export const authApi = { login: (data) => axiosInstance.post('/auth/login', data), refresh: (refreshToken) => axiosInstance.post('/auth/refresh', { refreshToken }), logout: (refreshToken) => axiosInstance.post('/auth/logout', { refreshToken }), + changePassword: (data) => axiosInstance.put('/auth/change-password', data), getSelf: () => axiosInstance.get('/users/user/get-self') }; diff --git a/src/api/classApi.js b/src/api/classApi.js index feede01..11ffddf 100644 --- a/src/api/classApi.js +++ b/src/api/classApi.js @@ -8,5 +8,8 @@ export const classApi = { create: (data) => axiosInstance.post('/classes/admin/create', data), update: (id, data) => axiosInstance.put(`/classes/admin/update/${id}`, data), delete: (id) => axiosInstance.delete(`/classes/admin/delete/${id}`), - registerUsers: (classId, userIds) => axiosInstance.post(`/classes/admin/${classId}/register-users`, { userIds }) + registerUsers: (classId, userIds, notify) => + axiosInstance.post(`/classes/admin/${classId}/register-users`, { userIds, notify }), + removeUser: (classId, userId) => + axiosInstance.delete(`/classes/admin/${classId}/students/${userId}`) }; diff --git a/src/components/common/NotifyChannelsField.vue b/src/components/common/NotifyChannelsField.vue new file mode 100644 index 0000000..bc23bc3 --- /dev/null +++ b/src/components/common/NotifyChannelsField.vue @@ -0,0 +1,44 @@ + + + + + + diff --git a/src/components/layout/AppSidebar.vue b/src/components/layout/AppSidebar.vue index c0c1a73..8014089 100644 --- a/src/components/layout/AppSidebar.vue +++ b/src/components/layout/AppSidebar.vue @@ -1,6 +1,6 @@ + + + @@ -119,6 +139,14 @@ :default-session-count="defaultSessionCount" /> + + @@ -132,9 +160,13 @@ import { professorApi } from '@/api/professorApi'; import { userApi } from '@/api/userApi'; import { usePersianDate } from '@/composables/usePersianDate'; import { useToast } from '@/composables/useToast'; +import { usePermission } from '@/composables/usePermission'; import PageHeader from '@/components/common/PageHeader.vue'; import AdminNotesField from '@/components/common/AdminNotesField.vue'; import ClassSessionsSection from '@/components/classes/ClassSessionsSection.vue'; +import NotifyChannelsField from '@/components/common/NotifyChannelsField.vue'; +import ConfirmDeleteDialog from '@/components/common/ConfirmDeleteDialog.vue'; +import PermissionGate from '@/components/common/PermissionGate.vue'; import InputText from 'primevue/inputtext'; import InputNumber from 'primevue/inputnumber'; import Dropdown from 'primevue/select'; @@ -149,14 +181,20 @@ import DatePicker from 'vue3-persian-datetime-picker'; const route = useRoute(); const router = useRouter(); const { showSuccess, showError } = useToast(); +const { hasPermission } = usePermission(); const { toPersianDigits, toLatinDigits } = usePersianDate(); const classId = route.params.id; const queryCourseId = route.query.courseId ? String(route.query.courseId) : null; const isEditMode = computed(() => !!classId); +const canManageStudents = computed(() => hasPermission('classes:register_users')); const isSubmitting = ref(false); const registering = ref(false); const defaultSessionCount = ref(12); +const registerNotify = reactive({ sms: true, email: true, bot: true }); +const removeDialogVisible = ref(false); +const removingUserId = ref(''); +const studentToRemove = ref(null); const courses = ref([]); const professors = ref([]); @@ -176,6 +214,13 @@ const form = reactive({ adminNotes: [] }); +const removeDialogMessage = computed(() => { + const name = studentToRemove.value?.name; + return name + ? `آیا «${name}» از این کلاس حذف شود؟` + : 'آیا این دانشجو از کلاس حذف شود؟'; +}); + const availableUsers = computed(() => { const enrolled = new Set(students.value.map((s) => String(s._id))); return allUsers.value @@ -308,7 +353,7 @@ const registerSelected = async () => { if (!selectedUserIds.value.length) return; registering.value = true; try { - const res = await classApi.registerUsers(classId, selectedUserIds.value); + const res = await classApi.registerUsers(classId, selectedUserIds.value, { ...registerNotify }); const data = res.data || res; students.value = data.students || []; selectedUserIds.value = []; @@ -320,5 +365,28 @@ const registerSelected = async () => { } }; +const confirmRemoveStudent = (student) => { + studentToRemove.value = student; + removeDialogVisible.value = true; +}; + +const handleRemoveStudent = async () => { + const userId = studentToRemove.value?._id || studentToRemove.value?.id; + if (!userId) return; + removingUserId.value = String(userId); + try { + const res = await classApi.removeUser(classId, userId); + const data = res.data || res; + students.value = data.students || []; + removeDialogVisible.value = false; + studentToRemove.value = null; + showSuccess('دانشجو از کلاس حذف شد'); + } catch (err) { + showError(err); + } finally { + removingUserId.value = ''; + } +}; + onMounted(fetchData); diff --git a/src/views/payments/PaymentListView.vue b/src/views/payments/PaymentListView.vue index afef7be..285239c 100644 --- a/src/views/payments/PaymentListView.vue +++ b/src/views/payments/PaymentListView.vue @@ -120,6 +120,8 @@ + +