admin-panel: add shared modal/header/icon/delete-handler components
New src/components/common/: - BaseFormModal.vue — the standard shell every converted add/edit modal will use: header=title, footer=buttons, body=form. Replaces the ad-hoc 60%/70%/80% <a-modal> widths used across the codebase with 4 named sizes. - ListPageHeader.vue — the add button that goes above every list page, gated on the same rid the create route used (modelled on the one place this already existed, ListUser.vue's add-user button). New src/components/icons.ts: the legacy-Feather-name -> ant-icon map, extracted out of TooltipBtn.vue so the sidebar can resolve the same names through the same map instead of a second copy. New src/composables/useDeleteHandler.ts: the delete-then-refetch-then-toast handler that was copy-pasted verbatim in 6 list views (city/cityList.vue, user/ListUser.vue, apps/category/List.vue, finical/list.vue, apps/product/ProductList.vue, manage/Const.vue). Preserves each site's exact success-message wording and message.success/info choice, and ProductList's irregular POST-based delete endpoint. Verified: pnpm build passes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
f0de478940
commit
02d92fc465
@@ -25,7 +25,7 @@
|
||||
placement="top"
|
||||
ok-text="بله"
|
||||
cancel-text="خیر"
|
||||
@confirm="confirmDelete(record.id)"
|
||||
@confirm="confirmDelete(`category/${record.id}`)"
|
||||
>
|
||||
<template #title><p>آیا مطمئن به حذف دسته مورد نظر هستید ؟</p></template>
|
||||
<DeleteOutlined class="cursor-pointer mx-1 text-danger" />
|
||||
@@ -40,11 +40,12 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue';
|
||||
import XTable from '../../../components/x-table/XTable.vue';
|
||||
import { buildCategoryModel } from './CategoryTbl';
|
||||
import { useUsersStore } from '../../../stores/users';
|
||||
import { useDeleteHandler } from '../../../composables/useDeleteHandler';
|
||||
import api from '../../../api/client';
|
||||
|
||||
const router = useRouter();
|
||||
@@ -55,16 +56,13 @@ const noImage = '/img/no-items.png';
|
||||
const model = buildCategoryModel();
|
||||
const tbl = ref<{ fetch: () => Promise<void> } | null>(null);
|
||||
|
||||
async function confirmDelete(id: string) {
|
||||
try {
|
||||
await api.delete(`category/${id}`);
|
||||
message.info('دسته حذف شد');
|
||||
tbl.value?.fetch();
|
||||
} catch (e: any) {
|
||||
// The backend refuses while products or child categories still reference it.
|
||||
Modal.error({ title: e?.response?.data?.message ?? 'خطا در حذف دسته', okText: 'بستن' });
|
||||
}
|
||||
}
|
||||
// The backend refuses while products or child categories still reference it.
|
||||
const { remove: confirmDelete } = useDeleteHandler({
|
||||
onSuccess: () => tbl.value?.fetch(),
|
||||
successMessage: 'دسته حذف شد',
|
||||
successType: 'info',
|
||||
fallbackErrorMessage: 'خطا در حذف دسته',
|
||||
});
|
||||
|
||||
function toEdit(id: string) {
|
||||
router.push(`/category/edit/${id}`);
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import {
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
@@ -105,6 +105,7 @@ import XTable from '../../../components/x-table/XTable.vue';
|
||||
import { buildProductListModel } from './productListModel';
|
||||
import { useUsersStore } from '../../../stores/users';
|
||||
import { isBranch } from '../../../utils';
|
||||
import { useDeleteHandler } from '../../../composables/useDeleteHandler';
|
||||
import api from '../../../api/client';
|
||||
|
||||
const router = useRouter();
|
||||
@@ -132,19 +133,17 @@ async function showProductModal(record: Record<string, any>) {
|
||||
visibleProduct.value = true;
|
||||
}
|
||||
|
||||
async function confirmDelete(id: string) {
|
||||
try {
|
||||
await api.post(`delete/product/${id}`);
|
||||
message.info('محصول حذف شد');
|
||||
tblProducts.value?.fetch();
|
||||
} catch (e: any) {
|
||||
// The backend refuses when the product appears in past orders; show its
|
||||
// Farsi explanation rather than a generic failure.
|
||||
Modal.error({
|
||||
title: e?.response?.data?.message ?? 'خطا در حذف محصول',
|
||||
okText: 'بستن',
|
||||
});
|
||||
}
|
||||
// The backend refuses when the product appears in past orders; show its
|
||||
// Farsi explanation rather than a generic failure.
|
||||
const { remove: confirmDeleteById } = useDeleteHandler({
|
||||
onSuccess: () => tblProducts.value?.fetch(),
|
||||
successMessage: 'محصول حذف شد',
|
||||
successType: 'info',
|
||||
fallbackErrorMessage: 'خطا در حذف محصول',
|
||||
});
|
||||
|
||||
function confirmDelete(id: string) {
|
||||
return confirmDeleteById(`delete/product/${id}`, { method: 'post' });
|
||||
}
|
||||
|
||||
function toEdit(id: string) {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
placement="top"
|
||||
ok-text="بله"
|
||||
cancel-text="خیر"
|
||||
@confirm="confirmDelete(record.id)"
|
||||
@confirm="confirmDelete(`cities/${record.id}`)"
|
||||
>
|
||||
<template #title><p>آیا مطمئن به حذف شهر مورد نظر هستید ؟</p></template>
|
||||
<DeleteOutlined class="cursor-pointer mx-1 text-danger" />
|
||||
@@ -53,11 +53,12 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue';
|
||||
import XTable from '../../components/x-table/XTable.vue';
|
||||
import { buildCityListModel } from './CityListModel';
|
||||
import { useUsersStore } from '../../stores/users';
|
||||
import { useDeleteHandler } from '../../composables/useDeleteHandler';
|
||||
import api from '../../api/client';
|
||||
|
||||
const users = useUsersStore();
|
||||
@@ -106,15 +107,11 @@ async function save() {
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmDelete(id: string) {
|
||||
try {
|
||||
await api.delete(`cities/${id}`);
|
||||
message.success('شهر حذف شد');
|
||||
citiesTable.value?.fetch();
|
||||
} catch (e: any) {
|
||||
// The backend refuses while customers, branches or addresses reference it.
|
||||
Modal.error({ title: e?.response?.data?.message ?? 'خطا در حذف شهر', okText: 'بستن' });
|
||||
}
|
||||
}
|
||||
// The backend refuses while customers, branches or addresses reference it.
|
||||
const { remove: confirmDelete } = useDeleteHandler({
|
||||
onSuccess: () => citiesTable.value?.fetch(),
|
||||
successMessage: 'شهر حذف شد',
|
||||
fallbackErrorMessage: 'خطا در حذف شهر',
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import {
|
||||
CheckOutlined,
|
||||
CloseOutlined,
|
||||
@@ -74,6 +74,7 @@ import {
|
||||
import XTable from '../../components/x-table/XTable.vue';
|
||||
import { buildDocumentListModel } from './list_model';
|
||||
import { useUsersStore } from '../../stores/users';
|
||||
import { useDeleteHandler } from '../../composables/useDeleteHandler';
|
||||
import api from '../../api/client';
|
||||
|
||||
const router = useRouter();
|
||||
@@ -99,16 +100,16 @@ async function changeConfirme(record: Record<string, any>) {
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteCash(record: Record<string, any>) {
|
||||
try {
|
||||
await api.delete(`cash/${record.id}`);
|
||||
message.success('سند حذف شد');
|
||||
tbl.value?.fetch();
|
||||
} catch (e: any) {
|
||||
// The backend refuses to delete a confirmed entry — it has already moved the
|
||||
// customer's balance. Surface its Farsi explanation.
|
||||
Modal.error({ title: e?.response?.data?.message ?? 'خطا در حذف سند', okText: 'بستن' });
|
||||
}
|
||||
// The backend refuses to delete a confirmed entry — it has already moved the
|
||||
// customer's balance. Surface its Farsi explanation.
|
||||
const { remove: deleteCashById } = useDeleteHandler({
|
||||
onSuccess: () => tbl.value?.fetch(),
|
||||
successMessage: 'سند حذف شد',
|
||||
fallbackErrorMessage: 'خطا در حذف سند',
|
||||
});
|
||||
|
||||
function deleteCash(record: Record<string, any>) {
|
||||
return deleteCashById(`cash/${record.id}`);
|
||||
}
|
||||
|
||||
function toEdit(id: string) {
|
||||
|
||||
@@ -167,9 +167,10 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue';
|
||||
import { useUsersStore } from '../../stores/users';
|
||||
import { useDeleteHandler } from '../../composables/useDeleteHandler';
|
||||
import api from '../../api/client';
|
||||
import ImageUploadInput from '../../components/upload/ImageUploadInput.vue';
|
||||
|
||||
@@ -241,15 +242,15 @@ async function saveBrandEdit() {
|
||||
}
|
||||
}
|
||||
|
||||
async function removeBrand(id: string) {
|
||||
try {
|
||||
await api.delete(`brand/${id}`);
|
||||
await loadBrands();
|
||||
message.success('برند حذف شد');
|
||||
} catch (e: any) {
|
||||
// refused while products reference it
|
||||
Modal.error({ title: e?.response?.data?.message ?? 'خطا در حذف برند', okText: 'بستن' });
|
||||
}
|
||||
// refused while products reference it
|
||||
const { remove: removeBrandById } = useDeleteHandler({
|
||||
onSuccess: loadBrands,
|
||||
successMessage: 'برند حذف شد',
|
||||
fallbackErrorMessage: 'خطا در حذف برند',
|
||||
});
|
||||
|
||||
function removeBrand(id: string) {
|
||||
return removeBrandById(`brand/${id}`);
|
||||
}
|
||||
|
||||
// ── tags
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
placement="top"
|
||||
ok-text="بله"
|
||||
cancel-text="خیر"
|
||||
@confirm="confirmDelete(record.id)"
|
||||
@confirm="confirmDelete(`users/${record.id}`)"
|
||||
>
|
||||
<template #title><p>آیا مطمئن به حذف کاربر مورد نظر هستید ؟</p></template>
|
||||
<DeleteOutlined class="cursor-pointer mx-1 text-danger" />
|
||||
@@ -50,11 +50,12 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue';
|
||||
import XTable from '../../components/x-table/XTable.vue';
|
||||
import { buildUserListModel } from './userTbl';
|
||||
import { useUsersStore } from '../../stores/users';
|
||||
import { useDeleteHandler } from '../../composables/useDeleteHandler';
|
||||
import api from '../../api/client';
|
||||
|
||||
const router = useRouter();
|
||||
@@ -74,17 +75,13 @@ function edit(record: Record<string, any>) {
|
||||
router.push(`/user/edit/${record.id}`);
|
||||
}
|
||||
|
||||
async function confirmDelete(id: string) {
|
||||
try {
|
||||
await api.delete(`users/${id}`);
|
||||
message.success('کاربر حذف شد');
|
||||
tbl.value?.fetch();
|
||||
} catch (e: any) {
|
||||
// The backend refuses when the admin owns ledger entries or status events —
|
||||
// deleting them would erase the audit trail.
|
||||
Modal.error({ title: e?.response?.data?.message ?? 'خطا در حذف کاربر', okText: 'بستن' });
|
||||
}
|
||||
}
|
||||
// The backend refuses when the admin owns ledger entries or status events —
|
||||
// deleting them would erase the audit trail.
|
||||
const { remove: confirmDelete } = useDeleteHandler({
|
||||
onSuccess: () => tbl.value?.fetch(),
|
||||
successMessage: 'کاربر حذف شد',
|
||||
fallbackErrorMessage: 'خطا در حذف کاربر',
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user