feat: add delete class action and confirm dialog in class list and detail views

This commit is contained in:
2026-08-21 13:20:33 +03:30
parent f9a3f555eb
commit 73ce53bcc2
3 changed files with 107 additions and 3 deletions
+1
View File
@@ -8,6 +8,7 @@ export const classApi = {
create: (data) => axiosInstance.post('/classes/admin/create', data), create: (data) => axiosInstance.post('/classes/admin/create', data),
update: (id, data) => axiosInstance.put(`/classes/admin/update/${id}`, data), update: (id, data) => axiosInstance.put(`/classes/admin/update/${id}`, data),
delete: (id) => axiosInstance.delete(`/classes/admin/delete/${id}`), delete: (id) => axiosInstance.delete(`/classes/admin/delete/${id}`),
restore: (id) => axiosInstance.post(`/classes/admin/restore/${id}`),
registerUsers: (classId, userIds, notify) => registerUsers: (classId, userIds, notify) =>
axiosInstance.post(`/classes/admin/${classId}/register-users`, { userIds, notify }), axiosInstance.post(`/classes/admin/${classId}/register-users`, { userIds, notify }),
removeUser: (classId, userId) => removeUser: (classId, userId) =>
+48 -1
View File
@@ -11,6 +11,16 @@
@click="$router.push(`/financial-reports?classId=${classId}`)" @click="$router.push(`/financial-reports?classId=${classId}`)"
/> />
</PermissionGate> </PermissionGate>
<PermissionGate permission="classes:delete">
<Button
label="حذف کلاس"
icon="pi pi-trash"
severity="danger"
text
class="ml-2"
@click="confirmDeleteClass"
/>
</PermissionGate>
<Button label="ویرایش" icon="pi pi-pencil" class="ml-2" @click="$router.push(`/classes/edit/${classId}`)" /> <Button label="ویرایش" icon="pi pi-pencil" class="ml-2" @click="$router.push(`/classes/edit/${classId}`)" />
<Button label="بازگشت" icon="pi pi-arrow-left" text severity="secondary" @click="$router.push('/classes')" /> <Button label="بازگشت" icon="pi pi-arrow-left" text severity="secondary" @click="$router.push('/classes')" />
</PageHeader> </PageHeader>
@@ -135,12 +145,20 @@
:loading="!!removingUserId" :loading="!!removingUserId"
@confirm="handleRemoveStudent" @confirm="handleRemoveStudent"
/> />
<ConfirmDeleteDialog
v-model="deleteClassDialogVisible"
title="حذف کلاس"
:message="deleteClassDialogMessage"
:loading="isDeletingClass"
@confirm="handleDeleteClass"
/>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { classApi } from '@/api/classApi'; import { classApi } from '@/api/classApi';
import { formatClassDays, formatClassTime } from '@/utils/classSchedule'; import { formatClassDays, formatClassTime } from '@/utils/classSchedule';
import { sessionApi } from '@/api/sessionApi'; import { sessionApi } from '@/api/sessionApi';
@@ -157,6 +175,7 @@ import DataTable from 'primevue/datatable';
import Column from 'primevue/column'; import Column from 'primevue/column';
const route = useRoute(); const route = useRoute();
const router = useRouter();
const classId = route.params.id; const classId = route.params.id;
const { toPersianDigits, formatJalali } = usePersianDate(); const { toPersianDigits, formatJalali } = usePersianDate();
const { showError, showSuccess } = useToast(); const { showError, showSuccess } = useToast();
@@ -170,6 +189,34 @@ const removeDialogVisible = ref(false);
const removingUserId = ref(''); const removingUserId = ref('');
const studentToRemove = ref(null); const studentToRemove = ref(null);
const deleteClassDialogVisible = ref(false);
const isDeletingClass = ref(false);
const deleteClassDialogMessage = computed(() => {
const name = classData.value?.name;
return name
? `آیا از حذف کلاس «${name}» اطمینان دارید؟ تمام جلسات و پیوندهای مرتبط با این کلاس نیز به سطل زباله منتقل می‌شوند.`
: 'آیا از حذف این کلاس اطمینان دارید؟ تمام جلسات و پیوندهای مرتبط با این کلاس نیز به سطل زباله منتقل می‌شوند.';
});
const confirmDeleteClass = () => {
deleteClassDialogVisible.value = true;
};
const handleDeleteClass = async () => {
isDeletingClass.value = true;
try {
await classApi.delete(classId);
showSuccess('کلاس و جلسات مرتبط با موفقیت حذف شدند');
deleteClassDialogVisible.value = false;
router.push('/classes');
} catch (err) {
showError(err);
} finally {
isDeletingClass.value = false;
}
};
const removeDialogMessage = computed(() => { const removeDialogMessage = computed(() => {
const name = studentToRemove.value?.name; const name = studentToRemove.value?.name;
return name return name
+58 -2
View File
@@ -41,7 +41,7 @@
</template> </template>
</Column> </Column>
<Column header="عملیات" style="width: 120px"> <Column header="عملیات" style="width: 140px">
<template #body="{ data }"> <template #body="{ data }">
<div class="flex align-items-center gap-1"> <div class="flex align-items-center gap-1">
<Button <Button
@@ -50,6 +50,7 @@
rounded rounded
size="small" size="small"
severity="info" severity="info"
v-tooltip.top="'مشاهده'"
@click="$router.push(`/classes/view/${data._id || data.id}`)" @click="$router.push(`/classes/view/${data._id || data.id}`)"
/> />
<PermissionGate permission="classes:update"> <PermissionGate permission="classes:update">
@@ -59,29 +60,52 @@
rounded rounded
size="small" size="small"
severity="warning" severity="warning"
v-tooltip.top="'ویرایش'"
@click="$router.push(`/classes/edit/${data._id || data.id}`)" @click="$router.push(`/classes/edit/${data._id || data.id}`)"
/> />
</PermissionGate> </PermissionGate>
<PermissionGate permission="classes:delete">
<Button
icon="pi pi-trash"
text
rounded
size="small"
severity="danger"
v-tooltip.top="'حذف'"
@click="confirmDelete(data)"
/>
</PermissionGate>
</div> </div>
</template> </template>
</Column> </Column>
</DataTableWrapper> </DataTableWrapper>
<ConfirmDeleteDialog
v-model="deleteDialogVisible"
title="حذف کلاس"
:message="deleteMessage"
:loading="isDeleting"
@confirm="handleDelete"
/>
</div> </div>
</template> </template>
<script setup> <script setup>
import { onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { useDataTable } from '@/composables/useDataTable'; import { useDataTable } from '@/composables/useDataTable';
import { usePersianDate } from '@/composables/usePersianDate'; import { usePersianDate } from '@/composables/usePersianDate';
import { useToast } from '@/composables/useToast';
import { classApi } from '@/api/classApi'; import { classApi } from '@/api/classApi';
import { formatClassSchedule } from '@/utils/classSchedule'; import { formatClassSchedule } from '@/utils/classSchedule';
import PageHeader from '@/components/common/PageHeader.vue'; import PageHeader from '@/components/common/PageHeader.vue';
import DataTableWrapper from '@/components/common/DataTableWrapper.vue'; import DataTableWrapper from '@/components/common/DataTableWrapper.vue';
import ConfirmDeleteDialog from '@/components/common/ConfirmDeleteDialog.vue';
import PermissionGate from '@/components/common/PermissionGate.vue'; import PermissionGate from '@/components/common/PermissionGate.vue';
import Button from 'primevue/button'; import Button from 'primevue/button';
import Column from 'primevue/column'; import Column from 'primevue/column';
const { toPersianDigits } = usePersianDate(); const { toPersianDigits } = usePersianDate();
const { showSuccess, showError } = useToast();
const { const {
items, items,
@@ -94,6 +118,38 @@ const {
onSearch onSearch
} = useDataTable(classApi.getAll); } = useDataTable(classApi.getAll);
const deleteDialogVisible = ref(false);
const selectedClass = ref(null);
const isDeleting = ref(false);
const deleteMessage = computed(() => {
const name = selectedClass.value?.name;
return name
? `آیا از حذف کلاس «${name}» اطمینان دارید؟ تمام جلسات و پیوندهای مرتبط با این کلاس نیز به سطل زباله منتقل می‌شوند.`
: 'آیا از حذف این کلاس اطمینان دارید؟ تمام جلسات و پیوندهای مرتبط با این کلاس نیز به سطل زباله منتقل می‌شوند.';
});
const confirmDelete = (classItem) => {
selectedClass.value = classItem;
deleteDialogVisible.value = true;
};
const handleDelete = async () => {
if (!selectedClass.value) return;
isDeleting.value = true;
try {
await classApi.delete(selectedClass.value._id || selectedClass.value.id);
showSuccess('کلاس و جلسات مرتبط با موفقیت حذف شدند');
deleteDialogVisible.value = false;
selectedClass.value = null;
loadData();
} catch (err) {
showError(err);
} finally {
isDeleting.value = false;
}
};
onMounted(() => { onMounted(() => {
loadData(); loadData();
}); });