diff --git a/src/App.vue b/src/App.vue index 242c89b..be31b27 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,6 +1,33 @@ diff --git a/src/router/index.js b/src/router/index.js index b82ed7b..99ec735 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -4,7 +4,7 @@ import axios from 'axios'; import Cookies from 'js-cookie'; const loginGuard = async (to, from, next) => { - const token = Cookies.get('bm-token'); + const token = Cookies.get('bm-token') || (typeof localStorage !== 'undefined' ? localStorage.getItem('bm-token') : null); if (!token) { next('/auth/login'); // Redirect to log in if no token diff --git a/src/service/CategoryService.js b/src/service/CategoryService.js index 25027a0..a8ed349 100644 --- a/src/service/CategoryService.js +++ b/src/service/CategoryService.js @@ -2,46 +2,37 @@ import axios from 'axios'; export const CategoryService = { getAllCategories: async () => { - const config = { - method: 'get', - url: '/api/categories/admin/get-all', - withCredentials: true - }; try { - const response = await axios(config); - return response.data.data; + const response = await axios.get('/api/categories/admin/get-all'); + return response.data?.data || []; } catch (err) { - console.log(err); + console.error('Error in getAllCategories:', err); + return []; } }, createCategory: async (data) => { - const config = { - method: 'post', - url: '/api/categories/admin/create', - data, - withCredentials: true - }; - try { - const response = await axios(config); + const response = await axios.post('/api/categories/admin/create', data); return 'success'; } catch (err) { - return err.response.data.message; + return err.response?.data?.message || 'خطا در ساخت دستهبندی'; } }, updateCategory: async (categoryId, data) => { - const config = { - method: 'put', - url: '/api/categories/admin/update/' + categoryId, - data, - withCredentials: true - }; - try { - const response = await axios(config); + const response = await axios.patch('/api/categories/admin/update/' + categoryId, data); return 'success'; } catch (err) { - return err.response.data.message; + return err.response?.data?.message || 'خطا در ویرایش دستهبندی'; + } + }, + deleteCategory: async (categoryId) => { + try { + await axios.delete('/api/categories/admin/delete/' + categoryId); + return 'success'; + } catch (err) { + return err.response?.data?.message || 'خطا در حذف دستهبندی'; } } }; + diff --git a/src/service/FeatureService.js b/src/service/FeatureService.js index 12c11f4..06bfaa4 100644 --- a/src/service/FeatureService.js +++ b/src/service/FeatureService.js @@ -2,46 +2,46 @@ import axios from 'axios'; export const FeatureService = { getAllFeatures: async () => { - const config = { - method: 'get', - url: '/api/categories/admin/get-all', - withCredentials: true - }; try { - const response = await axios(config); - return response.data.data; + const response = await axios.get('/api/features/admin/get-all'); + return response.data?.data || []; } catch (err) { - console.log(err); + console.error('Error in getAllFeatures:', err); + return []; + } + }, + getFeaturesByCategory: async (categoryId) => { + try { + const response = await axios.get(`/api/features/admin/get-by-category/${categoryId}`); + return response.data?.data || []; + } catch (err) { + console.error('Error in getFeaturesByCategory:', err); + return []; } }, createFeature: async (data) => { - const config = { - method: 'post', - url: '/api/features/admin/create', - data, - withCredentials: true - }; - try { - const response = await axios(config); + await axios.post('/api/features/admin/create', data); return 'success'; } catch (err) { - return err.response.data.message; + return err.response?.data?.message || 'خطا در ساخت ویژگی'; } }, updateFeature: async (featureId, data) => { - const config = { - method: 'put', - url: '/api/features/admin/update/' + featureId, - data, - withCredentials: true - }; - try { - const response = await axios(config); + await axios.put('/api/features/admin/update/' + featureId, data); return 'success'; } catch (err) { - return err.response.data.message; + return err.response?.data?.message || 'خطا در ویرایش ویژگی'; + } + }, + deleteFeature: async (featureId) => { + try { + await axios.delete('/api/features/admin/delete/' + featureId); + return 'success'; + } catch (err) { + return err.response?.data?.message || 'خطا در حذف ویژگی'; } } }; + diff --git a/src/service/PropertyService.js b/src/service/PropertyService.js index 1cb7ee3..f29bcd5 100644 --- a/src/service/PropertyService.js +++ b/src/service/PropertyService.js @@ -2,14 +2,13 @@ import axios from 'axios'; export const PropertyService = { _getAuthHeader: () => { - const bmToken = document.cookie - .split('; ') - .find((row) => row.startsWith('bm-token=')) - ?.split('=')[1]; + const bmToken = (typeof document !== 'undefined' + ? document.cookie.split('; ').find((row) => row.startsWith('bm-token='))?.split('=')[1] + : null) || (typeof localStorage !== 'undefined' ? localStorage.getItem('bm-token') : null); - return { - Authorization: `Bearer ${bmToken}` - }; + return bmToken && bmToken !== 'undefined' && bmToken !== 'null' + ? { Authorization: `Bearer ${bmToken}` } + : {}; }, getProperty: async (propertyId) => { diff --git a/src/service/SettingService.js b/src/service/SettingService.js index 9d9d1f2..6b74c62 100644 --- a/src/service/SettingService.js +++ b/src/service/SettingService.js @@ -2,14 +2,13 @@ import axios from 'axios'; export const SettingService = { _getAuthHeader: () => { - const bmToken = document.cookie - .split('; ') - .find((row) => row.startsWith('bm-token=')) - ?.split('=')[1]; + const bmToken = (typeof document !== 'undefined' + ? document.cookie.split('; ').find((row) => row.startsWith('bm-token='))?.split('=')[1] + : null) || (typeof localStorage !== 'undefined' ? localStorage.getItem('bm-token') : null); - return { - Authorization: `Bearer ${bmToken}` - }; + return bmToken && bmToken !== 'undefined' && bmToken !== 'null' + ? { Authorization: `Bearer ${bmToken}` } + : {}; }, /** diff --git a/src/service/TransactionService.js b/src/service/TransactionService.js index e539f5c..ad21502 100644 --- a/src/service/TransactionService.js +++ b/src/service/TransactionService.js @@ -2,14 +2,13 @@ import axios from 'axios'; export const TransactionService = { _getAuthHeader: () => { - const bmToken = document.cookie - .split('; ') - .find((row) => row.startsWith('bm-token=')) - ?.split('=')[1]; + const bmToken = (typeof document !== 'undefined' + ? document.cookie.split('; ').find((row) => row.startsWith('bm-token='))?.split('=')[1] + : null) || (typeof localStorage !== 'undefined' ? localStorage.getItem('bm-token') : null); - return { - Authorization: `Bearer ${bmToken}` - }; + return bmToken && bmToken !== 'undefined' && bmToken !== 'null' + ? { Authorization: `Bearer ${bmToken}` } + : {}; }, /** * Get a single transaction by ID diff --git a/src/service/WarehouseItemService.js b/src/service/WarehouseItemService.js index 1ca7f6b..c98a814 100644 --- a/src/service/WarehouseItemService.js +++ b/src/service/WarehouseItemService.js @@ -1,11 +1,10 @@ import axios from 'axios'; const getAuthHeader = () => { - const bmToken = document.cookie - .split('; ') - .find((row) => row.startsWith('bm-token=')) - ?.split('=')[1]; - return { Authorization: `Bearer ${bmToken}` }; + const bmToken = (typeof document !== 'undefined' + ? document.cookie.split('; ').find((row) => row.startsWith('bm-token='))?.split('=')[1] + : null) || (typeof localStorage !== 'undefined' ? localStorage.getItem('bm-token') : null); + return bmToken && bmToken !== 'undefined' && bmToken !== 'null' ? { Authorization: `Bearer ${bmToken}` } : {}; }; export const WarehouseItemService = { diff --git a/src/service/WarehouseService.js b/src/service/WarehouseService.js index 57993e3..b001054 100644 --- a/src/service/WarehouseService.js +++ b/src/service/WarehouseService.js @@ -1,11 +1,10 @@ import axios from 'axios'; const getAuthHeader = () => { - const bmToken = document.cookie - .split('; ') - .find((row) => row.startsWith('bm-token=')) - ?.split('=')[1]; - return { Authorization: `Bearer ${bmToken}` }; + const bmToken = (typeof document !== 'undefined' + ? document.cookie.split('; ').find((row) => row.startsWith('bm-token='))?.split('=')[1] + : null) || (typeof localStorage !== 'undefined' ? localStorage.getItem('bm-token') : null); + return bmToken && bmToken !== 'undefined' && bmToken !== 'null' ? { Authorization: `Bearer ${bmToken}` } : {}; }; export const WarehouseService = { diff --git a/src/views/pages/Category/ListCategories.vue b/src/views/pages/Category/ListCategories.vue index a34b944..1f983be 100644 --- a/src/views/pages/Category/ListCategories.vue +++ b/src/views/pages/Category/ListCategories.vue @@ -3,17 +3,30 @@ import { FilterMatchMode } from '@primevue/core/api'; import { onMounted, ref } from 'vue'; import { CategoryService } from '@/service/CategoryService'; import { useToast } from 'primevue/usetoast'; + const toast = useToast(); -import axios from 'axios'; const dataLoading = ref(true); +const dt = ref(); +const categories = ref([]); +const categoryDialog = ref(false); +const deleteCategoryDialog = ref(false); +const deleteSelectedDialog = ref(false); +const category = ref({}); +const selectedCategories = ref([]); +const filters = ref({ + global: { value: null, matchMode: FilterMatchMode.CONTAINS } +}); +const submitted = ref(false); const loadData = async () => { dataLoading.value = true; try { - categories.value = await CategoryService.getAllCategories(); + const data = await CategoryService.getAllCategories(); + categories.value = Array.isArray(data) ? data : []; } catch (err) { - toast.add({ severity: 'error', summary: 'خطا', detail: 'خطا در دریافت اطلاعات', life: 3000 }); + toast.add({ severity: 'error', summary: 'خطا', detail: 'خطا در دریافت اطلاعات دستهبندیها', life: 3000 }); + categories.value = []; } finally { dataLoading.value = false; } @@ -23,19 +36,6 @@ onMounted(() => { loadData(); }); -const dt = ref(); -const categories = ref(); -const categoryDialog = ref(false); -const deleteCategoryDialog = ref(false); -const deleteProductsDialog = ref(false); -const category = ref({}); -const selectedCategories = ref(); -const filters = ref({ - global: { value: null, matchMode: FilterMatchMode.CONTAINS } -}); -const submitted = ref(false); - - function openNew() { category.value = {}; submitted.value = false; @@ -49,24 +49,25 @@ function hideDialog() { async function saveCategory() { submitted.value = true; - let status = 'failed'; + if (!category.value?.nativeName || !category.value?.englishName) { + return; + } - if (category?.value._id?.trim()) { + let status = 'failed'; + if (category.value._id?.trim()) { status = await CategoryService.updateCategory(category.value._id, category.value); } else { status = await CategoryService.createCategory(category.value); } if (status === 'success') { - toast.add({ severity: 'success', summary: 'موفق', detail: 'دسته بندی جدید ساخته شد.', life: 3000 }); + toast.add({ severity: 'success', summary: 'موفق', detail: 'دستهبندی با موفقیت ذخیره شد.', life: 3000 }); + categoryDialog.value = false; + category.value = {}; + await loadData(); } else { toast.add({ severity: 'error', summary: 'ناموفق', detail: status, life: 3000 }); } - - categoryDialog.value = false; - category.value = {}; - - CategoryService.getAllCategories().then((data) => (categories.value = data)); } function editCategory(cat) { @@ -74,42 +75,47 @@ function editCategory(cat) { categoryDialog.value = true; } -function confirmDeleteCategory(prod) { - category.value = prod; +function confirmDeleteCategory(cat) { + category.value = cat; deleteCategoryDialog.value = true; } async function deleteCategory() { - const config = { - method: 'delete', - url: '/api/categories/admin/delete/' + category.value._id, - data: category.value, - withCredentials: true - }; - try { - const response = await axios(config); - toast.add({ severity: 'success', summary: 'موفق', detail: 'دسته بندی جدید حذف شد.', life: 3000 }); - } catch (err) { - toast.add({ severity: 'error', summary: 'ناموفق', detail: err.response.data.message, life: 3000 }); + if (!category.value?._id) return; + const status = await CategoryService.deleteCategory(category.value._id); + if (status === 'success') { + toast.add({ severity: 'success', summary: 'موفقیتآمیز', detail: 'دستهبندی با موفقیت حذف شد.', life: 3000 }); + deleteCategoryDialog.value = false; + category.value = {}; + await loadData(); + } else { + toast.add({ severity: 'error', summary: 'ناموفق', detail: status, life: 3000 }); } - CategoryService.getAllCategories().then((data) => (categories.value = data)); - deleteCategoryDialog.value = false; - category.value = {}; -} - -function exportCSV() { - dt.value.exportCSV(); } function confirmDeleteSelected() { - deleteProductsDialog.value = true; + deleteSelectedDialog.value = true; } -function deleteSelectedCategories() { - categories.value = categories.value.filter((val) => !selectedCategories.value.includes(val)); - deleteProductsDialog.value = false; - selectedCategories.value = null; - toast.add({ severity: 'success', summary: 'موفقیت آمیز', detail: 'دسته بندی مورد نظر حذف شد.', life: 3000 }); +async function deleteSelectedCategories() { + if (!selectedCategories.value || !selectedCategories.value.length) return; + + let successCount = 0; + for (const cat of selectedCategories.value) { + if (cat._id) { + const res = await CategoryService.deleteCategory(cat._id); + if (res === 'success') successCount++; + } + } + + toast.add({ severity: 'success', summary: 'موفقیتآمیز', detail: `${successCount} دستهبندی حذف شد.`, life: 3000 }); + deleteSelectedDialog.value = false; + selectedCategories.value = []; + await loadData(); +} + +function exportCSV() { + dt.value?.exportCSV(); } @@ -119,11 +125,13 @@ function deleteSelectedCategories() { - - + + - + + + - مدیریت دسته بندی ها + مدیریت دسته بندی ها ({{ categories.length }} مورد) @@ -167,10 +175,17 @@ function deleteSelectedCategories() { + + + + {{ slotProps.data.features?.length || 0 }} ویژگی + + + - - + + @@ -185,7 +200,7 @@ function deleteSelectedCategories() { نام انگلیسی - + ورود نام انگلیسی الزامی است. @@ -196,27 +211,27 @@ function deleteSelectedCategories() { - + - + آیا از حذف دسته بندی {{ category.nativeName }} مطمئن هستید؟آیا از حذف دسته بندی {{ category.nativeName }} مطمئن هستید؟ - + - + - - Are you sure you want to delete the selected products? + + آیا از حذف {{ selectedCategories?.length || 0 }} دستهبندی انتخاب شده اطمینان دارید؟ - - + + diff --git a/src/views/pages/Features/ListFeatures.vue b/src/views/pages/Features/ListFeatures.vue index fe11e5c..ca46cf7 100644 --- a/src/views/pages/Features/ListFeatures.vue +++ b/src/views/pages/Features/ListFeatures.vue @@ -1,148 +1,300 @@ - - - ویژگی دسته بندی ها + + + + + + ویژگی دستهبندیها + تعریف و مدیریت فیلدهای اختصاصی برای هر دستهبندی + + + + + - - - + + + + - - - - {{ category.nativeName }} + + + + هیچ دستهبندیای یافت نشد. ابتدا یک دستهبندی ایجاد کنید. + + + + + + + + + {{ cat.nativeName }} + + {{ cat.features?.length || 0 }} + + - - لیست ویژگی های {{ category.nativeName }} - - - افزودن + + + + لیست ویژگیهای «{{ cat.nativeName }}» ({{ cat.features?.length || 0 }} مورد) + + + + + + + + + + # + نام فارسی + نام انگلیسی + نوع ویژگی + واحد + تگ دستهبندی + عملیات + + + + + {{ idx + 1 }} + {{ feat.nativeName }} + {{ feat.englishName }} + + + {{ getFeatureTypeLabel(feat.featureType) }} + + + {{ feat.unit || '-' }} + {{ feat.tag || '-' }} + + + + + + + + + + + + + + هیچ ویژگیای برای این دستهبندی تعریف نشده است. + - - + + + + - نام فارسی - + نام فارسی * + ورود نام فارسی الزامی است. - نام انگلیسی - + نام انگلیسی * + ورود نام انگلیسی الزامی است. - واحد - - - - نوع ویژگی - + نوع دادهای * + ورود نوع ویژگی الزامی است. + + واحد اندازهگیری (اختیاری) + + + + تگ گروهی (اختیاری) + + - + + + + + + + + + + آیا از حذف ویژگی «{{ featureToDelete.nativeName }}» مطمئن هستید؟ + + + + + diff --git a/src/views/pages/Property/ListProperties.vue b/src/views/pages/Property/ListProperties.vue index 9f1c916..dc59abf 100644 --- a/src/views/pages/Property/ListProperties.vue +++ b/src/views/pages/Property/ListProperties.vue @@ -1,7 +1,7 @@ @@ -467,8 +472,12 @@ const onCardHover = (propertyId) => { :key="prop._id || prop.id" :id="`property-card-${prop._id || prop.id}`" class="property-card" - :class="{ 'card-selected': selectedPropertyId === (prop._id || prop.id) }" + :class="{ + 'card-selected': selectedPropertyId === (prop._id || prop.id), + 'card-hovered': hoveredPropertyId === (prop._id || prop.id) + }" @mouseenter="onCardHover(prop._id || prop.id)" + @mouseleave="onCardLeave" @click="onSelectProperty(prop._id || prop.id)" > @@ -564,6 +573,7 @@ const onCardHover = (propertyId) => { ref="splitMapRef" :properties="properties" :selectedPropertyId="selectedPropertyId" + :hoveredPropertyId="hoveredPropertyId" @select-property="onSelectProperty" @edit-property="editProperty" /> @@ -576,6 +586,7 @@ const onCardHover = (propertyId) => { ref="mapRef" :properties="properties" :selectedPropertyId="selectedPropertyId" + :hoveredPropertyId="hoveredPropertyId" @select-property="onSelectProperty" @edit-property="editProperty" /> diff --git a/src/views/pages/Property/PropertyMap.vue b/src/views/pages/Property/PropertyMap.vue index b7777c1..a4020ce 100644 --- a/src/views/pages/Property/PropertyMap.vue +++ b/src/views/pages/Property/PropertyMap.vue @@ -163,7 +163,7 @@
تعریف و مدیریت فیلدهای اختصاصی برای هر دستهبندی
هیچ دستهبندیای یافت نشد. ابتدا یک دستهبندی ایجاد کنید.
لیست ویژگی های {{ category.nativeName }}
هیچ ویژگیای برای این دستهبندی تعریف نشده است.