| @@ -1,4 +1,4 @@ | |||
| <!doctype html><html lang=""><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="icon" id="favicon" href="/favicon.svg"/><link rel="stylesheet" href="/fonts/vazir.css"/><script defer="defer" src="https://bazarce.liara.run/script.js" data-website-id="7baabdd5-3224-41c1-9267-d2a1abd29d01"></script><title>NovinPlast</title><script defer="defer" src="/js/chunk-vendors.ae561124.js"></script><script defer="defer" src="/js/app.2907bfa5.js"></script><link href="/css/chunk-vendors.fd1119e3.css" rel="stylesheet"><link href="/css/app.cbef7f68.css" rel="stylesheet"></head><body lang><noscript><strong>We're sorry but NovinPlast doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>document.addEventListener("DOMContentLoaded", function () { | |||
| <!doctype html><html lang=""><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="icon" id="favicon" href="/favicon.svg"/><link rel="stylesheet" href="/fonts/vazir.css"/><script defer="defer" src="https://bazarce.liara.run/script.js" data-website-id="7baabdd5-3224-41c1-9267-d2a1abd29d01"></script><title>NovinPlast</title><script defer="defer" src="/js/chunk-vendors.1ba8209a.js"></script><script defer="defer" src="/js/app.3f68cdfe.js"></script><link href="/css/chunk-vendors.fd1119e3.css" rel="stylesheet"><link href="/css/app.cbef7f68.css" rel="stylesheet"></head><body lang><noscript><strong>We're sorry but NovinPlast doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>document.addEventListener("DOMContentLoaded", function () { | |||
| const faviconUrl = localStorage.getItem("logo"); | |||
| if (faviconUrl) { | |||
| const faviconLink = document.getElementById("favicon"); | |||
| @@ -107,8 +107,15 @@ export default { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| if (start < 1) { | |||
| end += 1 - start; | |||
| start = 1; | |||
| } | |||
| if (end > totalPages.value) { | |||
| start -= end - totalPages.value; | |||
| end = totalPages.value; | |||
| } | |||
| start = Math.max(start, 1); | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| @@ -116,7 +123,6 @@ export default { | |||
| } | |||
| return pages; | |||
| }); | |||
| watch(page, () => { | |||
| getAttributes(); | |||
| }); | |||
| @@ -298,17 +304,24 @@ export default { | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <!-- Previous page --> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <!-- First page and leading dots --> | |||
| <li | |||
| v-if="visiblePages[0] > 1" | |||
| class="page-item" | |||
| @click="page = 1" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <li v-if="visiblePages[0] > 2" class="page-item disabled"> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <!-- Visible pages --> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| @@ -324,23 +337,24 @@ export default { | |||
| </a> | |||
| </li> | |||
| <!-- Trailing dots and last page --> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages - 1" | |||
| class="page-item disabled" | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| <a class="page-link" href="javascript:void(0)"> | |||
| {{ totalPages }} | |||
| </a> | |||
| </li> | |||
| <!-- Next page --> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| @@ -112,18 +112,16 @@ | |||
| <BCol v-if="pageType === 'category' && pannel === 'web'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">صفحه دسته</label> | |||
| <select | |||
| <VueSelect | |||
| style="--vs-min-height: 48px; --vs-border-radius: 8px" | |||
| v-model="selectedCatPage" | |||
| class="form-select" | |||
| :class="{ 'is-invalid': errors.selectedCatPage }" | |||
| aria-label="Default select example" | |||
| :isLoading="categoryPageSelectorLoader" | |||
| :options="formattedCategoriesPages" | |||
| @change="clearError('selectedCatPage')" | |||
| placeholder="انتخاب صفحه دسته" | |||
| > | |||
| <option :value="cat.id" v-for="cat in cats" :key="cat.id"> | |||
| {{ cat.title }} | |||
| </option> | |||
| </select> | |||
| placeholder="دسته ای را انتخاب کنید" | |||
| @search="handleCategoryPageSearch" | |||
| /> | |||
| </div> | |||
| <small v-if="errors.selectedCatPage" class="text-danger"> | |||
| {{ errors.selectedCatPage }} | |||
| @@ -133,22 +131,16 @@ | |||
| <BCol v-if="pageType === 'brand' && pannel === 'web'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">صفحه برند</label> | |||
| <select | |||
| <VueSelect | |||
| style="--vs-min-height: 48px; --vs-border-radius: 8px" | |||
| v-model="selectedBrandPage" | |||
| class="form-select" | |||
| :class="{ 'is-invalid': errors.selectedBrandPage }" | |||
| aria-label="Default select example" | |||
| @change="clearError('selectedBrandPage')" | |||
| placeholder="انتخاب صفحه برند ها" | |||
| > | |||
| <option | |||
| :value="brand.id" | |||
| v-for="brand in brands" | |||
| :key="brand.id" | |||
| > | |||
| {{ brand.title }} | |||
| </option> | |||
| </select> | |||
| :isLoading="brandSelectorLoader" | |||
| :options="formattedBrands" | |||
| @change="clearError(`selectedBrandPage`)" | |||
| placeholder="برندی را انتخاب کنید" | |||
| @search="handleBrandSearch" | |||
| /> | |||
| </div> | |||
| <small v-if="errors.selectedBrandPage" class="text-danger"> | |||
| {{ errors.selectedBrandPage }} | |||
| @@ -190,6 +182,8 @@ | |||
| margin-top: 7px; | |||
| " | |||
| v-model="selectedLandingProduct" | |||
| :isLoading="productSelectorLoader" | |||
| @change="clearError(`selectedLandingProduct`)" | |||
| :options="formattedProducts" | |||
| placeholder="محصولی را انتخاب کنید" | |||
| @search="handleSearch" | |||
| @@ -202,18 +196,16 @@ | |||
| <BCol v-if="landingType === 'cat'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">صفحه کدام دسته</label> | |||
| <select | |||
| class="form-select" | |||
| aria-label="Default select example" | |||
| <VueSelect | |||
| style="--vs-min-height: 48px; --vs-border-radius: 8px" | |||
| v-model="selectedLandingCat" | |||
| @change="clearError('selectedLandingCat')" | |||
| :class="{ 'is-invalid': errors.selectedLandingCat }" | |||
| placeholder="انتخاب صفحه دسته" | |||
| > | |||
| <option :value="cat.id" v-for="cat in cats" :key="cat.id"> | |||
| {{ cat.title }} | |||
| </option> | |||
| </select> | |||
| :isLoading="categorySelectorLoader" | |||
| :options="formattedCategories" | |||
| placeholder="دسته ای را انتخاب کنید" | |||
| @search="handleCategorySearch" | |||
| /> | |||
| </div> | |||
| <small v-if="errors.selectedLandingCat" class="text-danger"> | |||
| {{ errors.selectedLandingCat }} | |||
| @@ -386,7 +378,7 @@ import mainPageBanner from "@/components/modals/helperModals/mainPageBanner.vue" | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import { ref, onMounted, computed } from "vue"; | |||
| import { ref, computed } from "vue"; | |||
| import Layout from "@/layout/custom.vue"; | |||
| export default { | |||
| @@ -402,6 +394,7 @@ export default { | |||
| const pageType = ref(); | |||
| const products = ref([]); | |||
| const cats = ref([]); | |||
| const categoryPages = ref([]); | |||
| const brands = ref([]); | |||
| const landingType = ref(); | |||
| const selectedCatPage = ref(); | |||
| @@ -412,33 +405,16 @@ export default { | |||
| const pannel = ref(); | |||
| const image = ref(); | |||
| const imagePreview = ref(); | |||
| const productSelectorLoader = ref(false); | |||
| const categorySelectorLoader = ref(false); | |||
| const categoryPageSelectorLoader = ref(false); | |||
| const brandSelectorLoader = ref(false); | |||
| const loading = ref(false); | |||
| const errors = ref({}); | |||
| const getCats = () => { | |||
| ApiServiece.get(`admin/categories`) | |||
| .then((resp) => { | |||
| cats.value = resp.data.data; | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| }); | |||
| }; | |||
| const getBrands = () => { | |||
| ApiServiece.get(`admin/brands`) | |||
| .then((resp) => { | |||
| brands.value = resp.data.data; | |||
| console.log(brands.value, "brands"); | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| }); | |||
| }; | |||
| const handleSearch = async (searchTerm) => { | |||
| if (searchTerm.length < 3) return; | |||
| productSelectorLoader.value = true; | |||
| try { | |||
| const response = await ApiServiece.get( | |||
| @@ -446,9 +422,35 @@ export default { | |||
| ); | |||
| products.value = response.data.data; | |||
| console.log(products.value, "products"); | |||
| productSelectorLoader.value = false; | |||
| } catch (error) { | |||
| products.value = []; | |||
| productSelectorLoader.value = false; | |||
| } | |||
| }; | |||
| const formattedBrands = computed(() => | |||
| Array.isArray(brands.value) // ✅ Check if products.value is an array | |||
| ? brands.value.map((brand) => ({ | |||
| value: brand.id, | |||
| label: brand.title, | |||
| })) | |||
| : [] | |||
| ); | |||
| const handleBrandSearch = async (searchTerm) => { | |||
| if (searchTerm.length < 3) return; | |||
| brandSelectorLoader.value = true; | |||
| try { | |||
| const response = await ApiServiece.get( | |||
| `admin/brands?title=${searchTerm}` | |||
| ); | |||
| brands.value = response.data.data; | |||
| brandSelectorLoader.value = false; | |||
| } catch (error) { | |||
| console.error("Error fetching products:", error); | |||
| products.value = []; // ✅ Reset to an empty array on error | |||
| brands.value = []; | |||
| brandSelectorLoader.value = false; | |||
| } | |||
| }; | |||
| @@ -461,6 +463,56 @@ export default { | |||
| : [] | |||
| ); | |||
| const handleCategorySearch = async (searchTerm) => { | |||
| if (searchTerm.length < 3) return; | |||
| categorySelectorLoader.value = true; | |||
| try { | |||
| const response = await ApiServiece.get( | |||
| `admin/categories?title=${searchTerm}` | |||
| ); | |||
| cats.value = response.data.data; | |||
| categorySelectorLoader.value = false; | |||
| } catch (error) { | |||
| cats.value = []; | |||
| categorySelectorLoader.value = false; | |||
| } | |||
| }; | |||
| const formattedCategories = computed(() => | |||
| Array.isArray(cats.value) | |||
| ? cats.value.map((cat) => ({ | |||
| value: cat.id, | |||
| label: cat.title, | |||
| })) | |||
| : [] | |||
| ); | |||
| const handleCategoryPageSearch = async (searchTerm) => { | |||
| if (searchTerm.length < 3) return; | |||
| categoryPageSelectorLoader.value = true; | |||
| try { | |||
| const response = await ApiServiece.get( | |||
| `admin/categories?title=${searchTerm}` | |||
| ); | |||
| categoryPages.value = response.data.data; | |||
| categoryPageSelectorLoader.value = false; | |||
| } catch (error) { | |||
| categoryPages.value = []; | |||
| categoryPageSelectorLoader.value = false; | |||
| } | |||
| }; | |||
| const formattedCategoriesPages = computed(() => | |||
| Array.isArray(categoryPages.value) | |||
| ? categoryPages.value.map((categoryPage) => ({ | |||
| value: categoryPage.id, | |||
| label: categoryPage.title, | |||
| })) | |||
| : [] | |||
| ); | |||
| const handleImageUpload = (event) => { | |||
| const file = event.target.files[0]; | |||
| @@ -515,11 +567,6 @@ export default { | |||
| errors.value[field] = ""; | |||
| }; | |||
| onMounted(() => { | |||
| getCats(); | |||
| getBrands(); | |||
| }); | |||
| const submitForm = () => { | |||
| console.log(errors.value); | |||
| if (!validateForm()) { | |||
| @@ -613,6 +660,7 @@ export default { | |||
| pageType.value = ""; | |||
| landingType.value = ""; | |||
| image.value = null; | |||
| imagePreview.value = null; | |||
| loading.value = false; | |||
| }; | |||
| return { | |||
| @@ -637,6 +685,16 @@ export default { | |||
| loading, | |||
| brands, | |||
| handleSearch, | |||
| productSelectorLoader, | |||
| handleCategorySearch, | |||
| categorySelectorLoader, | |||
| formattedCategories, | |||
| handleCategoryPageSearch, | |||
| formattedCategoriesPages, | |||
| categoryPageSelectorLoader, | |||
| brandSelectorLoader, | |||
| formattedBrands, | |||
| handleBrandSearch, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -78,8 +78,15 @@ export default { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| if (start < 1) { | |||
| end += 1 - start; | |||
| start = 1; | |||
| } | |||
| if (end > totalPages.value) { | |||
| start -= end - totalPages.value; | |||
| end = totalPages.value; | |||
| } | |||
| start = Math.max(start, 1); | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| @@ -363,17 +370,24 @@ export default { | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <!-- Previous page --> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <!-- First page and leading dots --> | |||
| <li | |||
| v-if="visiblePages[0] > 1" | |||
| class="page-item" | |||
| @click="page = 1" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <li v-if="visiblePages[0] > 2" class="page-item disabled"> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <!-- Visible pages --> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| @@ -389,23 +403,24 @@ export default { | |||
| </a> | |||
| </li> | |||
| <!-- Trailing dots and last page --> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages - 1" | |||
| class="page-item disabled" | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| <a class="page-link" href="javascript:void(0)"> | |||
| {{ totalPages }} | |||
| </a> | |||
| </li> | |||
| <!-- Next page --> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| @@ -495,14 +495,15 @@ export default { | |||
| selectedLandingCat.value = data?.category_id; | |||
| selectedLandingProduct.value = data?.product_id; | |||
| pageType.value = data.page_type; | |||
| if (data.page_id) { | |||
| pageType.value = "category"; | |||
| if (data.page_id && pageType.value === 'category') { | |||
| selectedCatPage.value = data?.page_id; | |||
| console.log(data); | |||
| } | |||
| if (!data.page_id) { | |||
| pageType.value = "main_page "; | |||
| if (data.page_id && pageType.value === 'barnd') { | |||
| selectedBrandPage.value = data?.page_id; | |||
| } | |||
| if (selectedLandingProduct.value) { | |||
| landingType.value = "product"; | |||
| @@ -510,7 +511,6 @@ export default { | |||
| if (selectedLandingCat.value) { | |||
| landingType.value = "cat"; | |||
| console.log("Asd"); | |||
| } | |||
| if (!selectedCatPage.value && !selectedLandingProduct.value) { | |||
| @@ -75,8 +75,15 @@ export default { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| if (start < 1) { | |||
| end += 1 - start; | |||
| start = 1; | |||
| } | |||
| if (end > totalPages.value) { | |||
| start -= end - totalPages.value; | |||
| end = totalPages.value; | |||
| } | |||
| start = Math.max(start, 1); | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| @@ -268,17 +275,24 @@ export default { | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <!-- Previous page --> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <!-- First page and leading dots --> | |||
| <li | |||
| v-if="visiblePages[0] > 1" | |||
| class="page-item" | |||
| @click="page = 1" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <li v-if="visiblePages[0] > 2" class="page-item disabled"> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <!-- Visible pages --> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| @@ -294,23 +308,24 @@ export default { | |||
| </a> | |||
| </li> | |||
| <!-- Trailing dots and last page --> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages - 1" | |||
| class="page-item disabled" | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| <a class="page-link" href="javascript:void(0)"> | |||
| {{ totalPages }} | |||
| </a> | |||
| </li> | |||
| <!-- Next page --> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| @@ -75,8 +75,15 @@ export default { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| if (start < 1) { | |||
| end += 1 - start; | |||
| start = 1; | |||
| } | |||
| if (end > totalPages.value) { | |||
| start -= end - totalPages.value; | |||
| end = totalPages.value; | |||
| } | |||
| start = Math.max(start, 1); | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| @@ -84,7 +91,6 @@ export default { | |||
| } | |||
| return pages; | |||
| }); | |||
| const deleteBlog = (id, title) => { | |||
| Swal.fire({ | |||
| text: `می خواهید بلاگ ${title} را حذف کنید؟`, | |||
| @@ -127,8 +133,6 @@ export default { | |||
| } | |||
| } | |||
| watch(page, () => { | |||
| getBlogs(); | |||
| }); | |||
| @@ -175,7 +179,7 @@ export default { | |||
| <div class="col-md-12"> | |||
| <div class="card shadow-sm border-0 rounded"> | |||
| <div | |||
| class="card-header d-flex justify-content-between align-items-center p-3 " | |||
| class="card-header d-flex justify-content-between align-items-center p-3" | |||
| dir="rtl" | |||
| > | |||
| <div class="d-flex align-items-center"> | |||
| @@ -186,14 +190,13 @@ export default { | |||
| class="form-control form-control-sm d-inline-block me-2" | |||
| style="width: 250px; border-radius: 15px" | |||
| /> | |||
| </div> | |||
| <router-link | |||
| to="/addBlog" | |||
| class="btn btn-light text-primary btn-sm px-3" | |||
| > | |||
| افزودن بلاگ | |||
| </router-link> | |||
| <router-link | |||
| to="/addBlog" | |||
| class="btn btn-light text-primary btn-sm px-3" | |||
| > | |||
| افزودن بلاگ | |||
| </router-link> | |||
| </div> | |||
| <div v-if="!filterLoading" class="card-body table-border-style p-0"> | |||
| <div class="table-responsive"> | |||
| @@ -247,77 +250,84 @@ export default { | |||
| </div> | |||
| </BRow> | |||
| <BRow> | |||
| <BCol sm="12"> | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <BCol sm="12"> | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <!-- Previous page --> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <!-- First page and leading dots --> | |||
| <li | |||
| v-if="visiblePages[0] > 1" | |||
| class="page-item" | |||
| @click="page = 1" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="visiblePages[0] > 2" class="page-item disabled"> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| class="page-item" | |||
| :class="{ active: currentPage === n }" | |||
| <!-- Visible pages --> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| class="page-item" | |||
| :class="{ active: currentPage === n }" | |||
| > | |||
| <a | |||
| class="page-link" | |||
| href="javascript:void(0)" | |||
| @click="page = n" | |||
| > | |||
| <a | |||
| class="page-link" | |||
| href="javascript:void(0)" | |||
| @click="page = n" | |||
| > | |||
| {{ n }} | |||
| </a> | |||
| </li> | |||
| {{ n }} | |||
| </a> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| </li> | |||
| <!-- Trailing dots and last page --> | |||
| <li | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages - 1" | |||
| class="page-item disabled" | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)"> | |||
| {{ totalPages }} | |||
| </a> | |||
| </li> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| > | |||
| <span class="page-link" @click="nextPage">بعدی</span> | |||
| </li> | |||
| </ul> | |||
| </nav> | |||
| </div> | |||
| </BCol> | |||
| <BCol sm="4"> | |||
| <div class="ms-0 search-number"> | |||
| <input | |||
| v-model="searchPage" | |||
| type="text" | |||
| class="form-control" | |||
| placeholder="برو به صفحه" | |||
| :max="totalPages" | |||
| min="1" | |||
| @input="handlePageInput" | |||
| /> | |||
| </div> | |||
| </BCol> | |||
| <!-- Next page --> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| > | |||
| <span class="page-link" @click="nextPage">بعدی</span> | |||
| </li> | |||
| </ul> | |||
| </nav> | |||
| </div> | |||
| </BCol> | |||
| <BCol sm="4"> | |||
| <div class="ms-0 search-number"> | |||
| <input | |||
| v-model="searchPage" | |||
| type="text" | |||
| class="form-control" | |||
| placeholder="برو به صفحه" | |||
| :max="totalPages" | |||
| min="1" | |||
| @input="handlePageInput" | |||
| /> | |||
| </div> | |||
| </BCol> | |||
| </BRow> | |||
| </Layout> | |||
| </template> | |||
| @@ -175,6 +175,7 @@ export default { | |||
| VueSelect, | |||
| }, | |||
| setup() { | |||
| const quillInstance = ref(null); | |||
| const blog = ref(); | |||
| const route = useRoute(); | |||
| const loading = ref(false); | |||
| @@ -270,24 +271,29 @@ export default { | |||
| blogCat.value = blog.value.blog_category_id; | |||
| author.value = blog.value.author; | |||
| if (editor.value) { | |||
| const quill = new Quill(editor.value, { | |||
| theme: "snow", | |||
| modules: { | |||
| toolbar: [ | |||
| [{ header: "1" }, { header: "2" }, { font: [] }], | |||
| [{ list: "ordered" }, { list: "bullet" }], | |||
| [{ align: [] }], | |||
| ["bold", "italic", "underline"], | |||
| ["link", "image"], | |||
| [{ script: "sub" }, { script: "super" }], | |||
| [{ direction: "rtl" }], | |||
| ], | |||
| }, | |||
| }); | |||
| quill.root.innerHTML = blog.value.content; | |||
| editorContent.value = quill.root.innerHTML; | |||
| } | |||
| quillInstance.value = new Quill(editor.value, { | |||
| theme: "snow", | |||
| modules: { | |||
| toolbar: [ | |||
| [{ header: "1" }, { header: "2" }, { font: [] }], | |||
| [{ list: "ordered" }, { list: "bullet" }], | |||
| [{ align: [] }], | |||
| ["bold", "italic", "underline"], | |||
| ["link", "image"], | |||
| [{ script: "sub" }, { script: "super" }], | |||
| [{ direction: "rtl" }], | |||
| ], | |||
| }, | |||
| }); | |||
| quillInstance.value.root.innerHTML = blog.value.content; | |||
| editorContent.value = blog.value.content; | |||
| // ✨ Update content on change | |||
| quillInstance.value.on("text-change", () => { | |||
| editorContent.value = quillInstance.value.root.innerHTML; | |||
| }); | |||
| } | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| @@ -37,7 +37,6 @@ export default { | |||
| .format("YYYY/MM/DD"); | |||
| }; | |||
| const handleSearchChange = () => { | |||
| clearTimeout(searchTimeout); | |||
| @@ -50,7 +49,7 @@ export default { | |||
| watch(searchQuery, () => { | |||
| handleSearchChange(); | |||
| }); | |||
| const getBrands = () => { | |||
| filterLoading.value = true; | |||
| ApiServiece.get( | |||
| @@ -153,8 +152,15 @@ export default { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| if (start < 1) { | |||
| end += 1 - start; | |||
| start = 1; | |||
| } | |||
| if (end > totalPages.value) { | |||
| start -= end - totalPages.value; | |||
| end = totalPages.value; | |||
| } | |||
| start = Math.max(start, 1); | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| @@ -163,8 +169,6 @@ export default { | |||
| return pages; | |||
| }); | |||
| onMounted(() => { | |||
| getBrands(); | |||
| }); | |||
| @@ -211,15 +215,14 @@ export default { | |||
| class="form-control form-control-sm d-inline-block me-2" | |||
| style="width: 250px; border-radius: 15px" | |||
| /> | |||
| </div> | |||
| <button | |||
| data-bs-toggle="modal" | |||
| data-bs-target="#addBrand" | |||
| class="btn btn-light text-primary btn-sm px-3" | |||
| > | |||
| افزودن برند | |||
| </button> | |||
| data-bs-toggle="modal" | |||
| data-bs-target="#addBrand" | |||
| class="btn btn-light text-primary btn-sm px-3" | |||
| > | |||
| افزودن برند | |||
| </button> | |||
| </div> | |||
| <div v-if="!filterLoading" class="card-body table-border-style p-0"> | |||
| <div class="table-responsive"> | |||
| @@ -309,17 +312,24 @@ export default { | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <!-- Previous page --> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <!-- First page and leading dots --> | |||
| <li | |||
| v-if="visiblePages[0] > 1" | |||
| class="page-item" | |||
| @click="page = 1" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <li v-if="visiblePages[0] > 2" class="page-item disabled"> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <!-- Visible pages --> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| @@ -335,23 +345,24 @@ export default { | |||
| </a> | |||
| </li> | |||
| <!-- Trailing dots and last page --> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages - 1" | |||
| class="page-item disabled" | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| <a class="page-link" href="javascript:void(0)"> | |||
| {{ totalPages }} | |||
| </a> | |||
| </li> | |||
| <!-- Next page --> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| @@ -61,8 +61,15 @@ export default { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| if (start < 1) { | |||
| end += 1 - start; | |||
| start = 1; | |||
| } | |||
| if (end > totalPages.value) { | |||
| start -= end - totalPages.value; | |||
| end = totalPages.value; | |||
| } | |||
| start = Math.max(start, 1); | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| @@ -259,10 +266,7 @@ export default { | |||
| ></i> | |||
| </td> | |||
| <td>{{ call?.subject }}</td> | |||
| <td | |||
| class="text-center" | |||
| style=" background: transparent" | |||
| > | |||
| <td class="text-center" style="background: transparent"> | |||
| {{ call.text }} | |||
| </td> | |||
| <td> | |||
| @@ -342,15 +346,19 @@ export default { | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <!-- Page numbers with dots logic --> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <!-- First page and leading dots --> | |||
| <li | |||
| v-if="visiblePages[0] > 1" | |||
| class="page-item" | |||
| @click="page = 1" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <li v-if="visiblePages[0] > 2" class="page-item disabled"> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <!-- Page numbers --> | |||
| <!-- Visible pages --> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| @@ -366,21 +374,21 @@ export default { | |||
| </a> | |||
| </li> | |||
| <!-- Trailing dots and last page --> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages - 1" | |||
| class="page-item disabled" | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| <a class="page-link" href="javascript:void(0)"> | |||
| {{ totalPages }} | |||
| </a> | |||
| </li> | |||
| <!-- Next page --> | |||
| @@ -69,8 +69,15 @@ export default { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| if (start < 1) { | |||
| end += 1 - start; | |||
| start = 1; | |||
| } | |||
| if (end > totalPages.value) { | |||
| start -= end - totalPages.value; | |||
| end = totalPages.value; | |||
| } | |||
| start = Math.max(start, 1); | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| @@ -91,6 +98,10 @@ export default { | |||
| } | |||
| } | |||
| watch(page, () => { | |||
| getCats(); | |||
| }); | |||
| const nextPage = () => { | |||
| if (currentPage.value < totalPages.value) { | |||
| page.value++; | |||
| @@ -356,17 +367,24 @@ export default { | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <!-- Previous page --> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <!-- First page and leading dots --> | |||
| <li | |||
| v-if="visiblePages[0] > 1" | |||
| class="page-item" | |||
| @click="page = 1" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <li v-if="visiblePages[0] > 2" class="page-item disabled"> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <!-- Visible pages --> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| @@ -382,23 +400,24 @@ export default { | |||
| </a> | |||
| </li> | |||
| <!-- Trailing dots and last page --> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages - 1" | |||
| class="page-item disabled" | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| <a class="page-link" href="javascript:void(0)"> | |||
| {{ totalPages }} | |||
| </a> | |||
| </li> | |||
| <!-- Next page --> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| @@ -127,8 +127,15 @@ export default { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| if (start < 1) { | |||
| end += 1 - start; | |||
| start = 1; | |||
| } | |||
| if (end > totalPages.value) { | |||
| start -= end - totalPages.value; | |||
| end = totalPages.value; | |||
| } | |||
| start = Math.max(start, 1); | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| @@ -289,7 +296,7 @@ export default { | |||
| handleProductsSearch, | |||
| selectedProduct, | |||
| productSeletorLoader, | |||
| blogSelectorLoader | |||
| blogSelectorLoader, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -498,15 +505,24 @@ export default { | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <!-- Previous page --> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <!-- First page and leading dots --> | |||
| <li | |||
| v-if="visiblePages[0] > 1" | |||
| class="page-item" | |||
| @click="page = 1" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <li v-if="visiblePages[0] > 2" class="page-item disabled"> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <!-- Visible pages --> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| @@ -522,23 +538,24 @@ export default { | |||
| </a> | |||
| </li> | |||
| <!-- Trailing dots and last page --> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages - 1" | |||
| class="page-item disabled" | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| <a class="page-link" href="javascript:void(0)"> | |||
| {{ totalPages }} | |||
| </a> | |||
| </li> | |||
| <!-- Next page --> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| @@ -308,8 +308,7 @@ export default { | |||
| errors.value.selectedCat = "انتخاب دسته برای تخفیف الزامی می باشد"; | |||
| if (!selectedProduct.value && whichPart.value === "product") | |||
| errors.value.selectedProduct = "انتخاب محصول برای تخفیف الزامی می باشد"; | |||
| if (!startDate.value) | |||
| errors.value.startDate = "انتخاب تاریخ اعمال تخفیف الزامی می باشد "; | |||
| if (!whichPart.value) | |||
| errors.value.whichPart = "مشخص کنید تخفیف بر چه بخشی اعمال شود"; | |||
| @@ -86,8 +86,15 @@ export default { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| if (start < 1) { | |||
| end += 1 - start; | |||
| start = 1; | |||
| } | |||
| if (end > totalPages.value) { | |||
| start -= end - totalPages.value; | |||
| end = totalPages.value; | |||
| } | |||
| start = Math.max(start, 1); | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| @@ -387,17 +394,24 @@ export default { | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <!-- Previous page --> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <!-- First page and leading dots --> | |||
| <li | |||
| v-if="visiblePages[0] > 1" | |||
| class="page-item" | |||
| @click="page = 1" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <li v-if="visiblePages[0] > 2" class="page-item disabled"> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <!-- Visible pages --> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| @@ -413,23 +427,24 @@ export default { | |||
| </a> | |||
| </li> | |||
| <!-- Trailing dots and last page --> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages - 1" | |||
| class="page-item disabled" | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| <a class="page-link" href="javascript:void(0)"> | |||
| {{ totalPages }} | |||
| </a> | |||
| </li> | |||
| <!-- Next page --> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| @@ -69,8 +69,15 @@ export default { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| if (start < 1) { | |||
| end += 1 - start; | |||
| start = 1; | |||
| } | |||
| if (end > totalPages.value) { | |||
| start -= end - totalPages.value; | |||
| end = totalPages.value; | |||
| } | |||
| start = Math.max(start, 1); | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| @@ -276,15 +283,24 @@ export default { | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <!-- Previous page --> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <!-- First page and leading dots --> | |||
| <li | |||
| v-if="visiblePages[0] > 1" | |||
| class="page-item" | |||
| @click="page = 1" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <li v-if="visiblePages[0] > 2" class="page-item disabled"> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <!-- Visible pages --> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| @@ -300,23 +316,24 @@ export default { | |||
| </a> | |||
| </li> | |||
| <!-- Trailing dots and last page --> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages - 1" | |||
| class="page-item disabled" | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| <a class="page-link" href="javascript:void(0)"> | |||
| {{ totalPages }} | |||
| </a> | |||
| </li> | |||
| <!-- Next page --> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| @@ -150,8 +150,15 @@ export default { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| if (start < 1) { | |||
| end += 1 - start; | |||
| start = 1; | |||
| } | |||
| if (end > totalPages.value) { | |||
| start -= end - totalPages.value; | |||
| end = totalPages.value; | |||
| } | |||
| start = Math.max(start, 1); | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| @@ -348,17 +355,24 @@ export default { | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <!-- Previous page --> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <!-- First page and leading dots --> | |||
| <li | |||
| v-if="visiblePages[0] > 1" | |||
| class="page-item" | |||
| @click="page = 1" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <li v-if="visiblePages[0] > 2" class="page-item disabled"> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <!-- Visible pages --> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| @@ -374,23 +388,24 @@ export default { | |||
| </a> | |||
| </li> | |||
| <!-- Trailing dots and last page --> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages - 1" | |||
| class="page-item disabled" | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| <a class="page-link" href="javascript:void(0)"> | |||
| {{ totalPages }} | |||
| </a> | |||
| </li> | |||
| <!-- Next page --> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| @@ -4,15 +4,21 @@ import ApiServiece from "@/services/ApiService"; | |||
| import { onMounted, ref, watch, computed } from "vue"; | |||
| import DatePicker from "vue3-persian-datetime-picker"; | |||
| import VueSelect from "vue3-select-component"; | |||
| import showDescription from "@/components/modals/commonModals/showDescription.vue"; | |||
| import moment from "jalali-moment"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import Swal from "sweetalert2"; | |||
| export default { | |||
| name: "PRODUCT-LIST", | |||
| components: { | |||
| Layout, | |||
| VueSelect, | |||
| DatePicker, | |||
| showDescription, | |||
| }, | |||
| setup() { | |||
| const productDescription = ref(""); | |||
| const selectorLoader = ref(false); | |||
| const isLoading = ref(false); | |||
| const date = ref([]); | |||
| @@ -24,8 +30,46 @@ export default { | |||
| const page = ref(1); | |||
| const filterLoading = ref(false); | |||
| const selectedBrand = ref(); | |||
| const allProducts = ref([]); | |||
| const getAllProducts = () => { | |||
| const orders = ref([]); | |||
| const changeStatus = (id, status) => { | |||
| Swal.fire({ | |||
| text: `آیا می خواهید وضعیت سبد خرید را به ${getStatusLabel( | |||
| status | |||
| )} تغییر دهید؟ `, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| cancelButtonColor: "#d33", | |||
| confirmButtonText: "بله!", | |||
| cancelButtonText: "خیر", | |||
| }).then((result) => { | |||
| if (result.isConfirmed) { | |||
| const formData = new FormData(); | |||
| formData.append("status", status); | |||
| ApiServiece.put(`admin/orders/${id}`, formData) | |||
| .then(() => { | |||
| toast.success("تغییر وضعیت سبد خرید با موفقیت انجام شد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| }) | |||
| .then(() => { | |||
| getAllOrders(); | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| toast.error("!مشکلی در تغییر وضعیت سبد خرید پیش آمد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| }); | |||
| } | |||
| }); | |||
| }; | |||
| const descriptionModal = (desc) => { | |||
| productDescription.value = desc; | |||
| }; | |||
| const getAllOrders = () => { | |||
| if (date.value[0]) { | |||
| date.value[0] = moment(date.value[0], "YYYY-MM-DD HH:mm:ss").format( | |||
| "YYYY/MM/DD HH:mm:ss" | |||
| @@ -48,7 +92,7 @@ export default { | |||
| ) | |||
| .then((resp) => { | |||
| filterLoading.value = false; | |||
| allProducts.value = resp.data.data.data; | |||
| orders.value = resp.data.data.data; | |||
| currentPage.value = resp.data.data.current_page; | |||
| totalPages.value = resp.data.data.last_page; | |||
| @@ -64,6 +108,38 @@ export default { | |||
| .format("jYYYY/jMM/jDD HH:mm:ss"); | |||
| }; | |||
| const getStatusLabel = (status) => { | |||
| const statusLabels = { | |||
| waiting: "در انتظار", | |||
| paid: "پرداختشده", | |||
| un_paid: "پرداختنشده", | |||
| approved: "تأییدشده", | |||
| processing: "در حال پردازش", | |||
| shipping: "در حال ارسال", | |||
| delivered: "تحویلشده", | |||
| canceled: "لغوشده", | |||
| in_cart: "در سبد خرید", | |||
| done: "کامل شده", | |||
| }; | |||
| return statusLabels[status] || "نامشخص"; | |||
| }; | |||
| const getStatusClass = (status) => { | |||
| const statusClasses = { | |||
| waiting: "badge-waiting", | |||
| paid: "badge-paid", | |||
| un_paid: "badge-un_paid", | |||
| approved: "badge-approved", | |||
| processing: "badge-processing", | |||
| shipping: "badge-shipping", | |||
| delivered: "badge-delivered", | |||
| canceled: "badge-canceled", | |||
| in_cart: "badge-in-cart", | |||
| done: "badge-in-done", | |||
| }; | |||
| return statusClasses[status] || "badge-secondary"; | |||
| }; | |||
| const getFile = () => { | |||
| isLoading.value = true; | |||
| ApiServiece.post( | |||
| @@ -94,7 +170,7 @@ export default { | |||
| }; | |||
| watch(selectedBrand, () => { | |||
| getAllProducts(); | |||
| getAllOrders(); | |||
| page.value = 1; | |||
| }); | |||
| @@ -108,8 +184,15 @@ export default { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| if (start < 1) { | |||
| end += 1 - start; | |||
| start = 1; | |||
| } | |||
| if (end > totalPages.value) { | |||
| start -= end - totalPages.value; | |||
| end = totalPages.value; | |||
| } | |||
| start = Math.max(start, 1); | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| @@ -131,28 +214,28 @@ export default { | |||
| } | |||
| watch(selectedBrand, () => { | |||
| getAllProducts(); | |||
| getAllOrders(); | |||
| }); | |||
| watch(date, () => { | |||
| getAllProducts(); | |||
| getAllOrders(); | |||
| }); | |||
| watch(page, () => { | |||
| getAllProducts(); | |||
| getAllOrders(); | |||
| }); | |||
| const nextPage = () => { | |||
| if (currentPage.value < totalPages.value) { | |||
| page.value++; | |||
| getAllProducts(); | |||
| getAllOrders(); | |||
| } | |||
| }; | |||
| const prevPage = () => { | |||
| if (currentPage.value > 1) { | |||
| page.value--; | |||
| getAllProducts(); | |||
| getAllOrders(); | |||
| } | |||
| }; | |||
| @@ -185,10 +268,10 @@ export default { | |||
| ); | |||
| onMounted(() => { | |||
| getAllProducts(); | |||
| getAllOrders(); | |||
| }); | |||
| return { | |||
| allProducts, | |||
| orders, | |||
| visiblePages, | |||
| nextPage, | |||
| prevPage, | |||
| @@ -208,6 +291,11 @@ export default { | |||
| handleBrandSearch, | |||
| filterLoading, | |||
| selectorLoader, | |||
| productDescription, | |||
| descriptionModal, | |||
| getStatusLabel, | |||
| getStatusClass, | |||
| changeStatus, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -262,113 +350,249 @@ export default { | |||
| </div> | |||
| <div v-if="!filterLoading"> | |||
| <table class="table table-hover tbl-product" id="pc-dt-simple"> | |||
| <thead> | |||
| <table | |||
| class="table table-hover align-middle text-center" | |||
| id="pc-dt-simple" | |||
| > | |||
| <thead class="table-light"> | |||
| <tr> | |||
| <th class="text-end">#</th> | |||
| <th>جزییات محصول</th> | |||
| <th>تاریخ ایحاد</th> | |||
| <th class="text-end">قیمت عمده</th> | |||
| <th class="text-end">قیمت تک</th> | |||
| <th class="text-end">تعداد سفارش</th> | |||
| <th class="text-end">تعداد ارسال شده</th> | |||
| <th class="text-center">عنوان برند</th> | |||
| <th class="text-center">تصویر برند</th> | |||
| <th class="text-start">جزییات محصول</th> | |||
| <th>توضیحات</th> | |||
| <th>وضعیت</th> | |||
| <th>تاریخ ایجاد</th> | |||
| <th>قیمت عمده</th> | |||
| <th>قیمت تک</th> | |||
| <th>تعداد سفارش</th> | |||
| <th>تعداد ارسال شده</th> | |||
| <th>عنوان برند</th> | |||
| <th>تصویر برند</th> | |||
| <th>عملیات</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| <tr v-for="product in allProducts" :key="product?.id"> | |||
| <td class="text-end">5</td> | |||
| <tr v-for="order in orders" :key="order?.id"> | |||
| <!-- Product Details --> | |||
| <td class="text-start"> | |||
| <div class="d-flex align-items-center gap-3"> | |||
| <img | |||
| :src="order?.product?.image" | |||
| alt="product" | |||
| class="rounded" | |||
| style="width: 50px; height: 50px; object-fit: cover" | |||
| /> | |||
| <div> | |||
| <div class="fw-semibold"> | |||
| {{ order?.product?.title }} | |||
| </div> | |||
| <div class="text-muted small"> | |||
| {{ order.product.description.slice(0, 25) }} | |||
| <span v-if="order.product.description.length > 25" | |||
| >...</span | |||
| > | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </td> | |||
| <!-- Description --> | |||
| <td> | |||
| <BRow> | |||
| <BCol class="col-auto pe-5"> | |||
| <img | |||
| :src="product?.product?.image" | |||
| alt="user-image" | |||
| class="wid-40 rounded product-img" | |||
| /> | |||
| </BCol> | |||
| <BCol> | |||
| <h6 class="mb-1">{{ product?.product?.title }}</h6> | |||
| <p class="text-muted f-12 mb-0"> | |||
| {{ product.product.description.slice(0, 25) | |||
| }}{{ | |||
| product.product.description.length > 25 | |||
| ? "..." | |||
| : "" | |||
| }} | |||
| </p> | |||
| </BCol> | |||
| </BRow> | |||
| <div | |||
| type="button" | |||
| data-bs-target="#showDescription" | |||
| data-bs-toggle="modal" | |||
| @click="descriptionModal(order?.description)" | |||
| class="subject-box" | |||
| > | |||
| <i class="fas fa-comments subject-icon"></i> | |||
| <span class="subject-text"> | |||
| {{ order?.description?.slice(0, 20) | |||
| }}{{ order?.description?.length > 20 ? "..." : "" }} | |||
| </span> | |||
| </div> | |||
| </td> | |||
| <td>{{ convertToJalali(product.created_at) }}</td> | |||
| <td | |||
| v-if="product?.product?.wholesale_price" | |||
| class="text-end" | |||
| > | |||
| {{ | |||
| formatWithCommas(product?.product?.wholesale_price) | |||
| }}تومان | |||
| <!-- Status --> | |||
| <td> | |||
| <span class="badge" :class="getStatusClass(order.status)"> | |||
| {{ getStatusLabel(order.status) }} | |||
| </span> | |||
| </td> | |||
| <td | |||
| v-if="!product?.product?.wholesale_price" | |||
| class="text-end" | |||
| > | |||
| <!-- Created Date --> | |||
| <td>{{ convertToJalali(order.created_at) }}</td> | |||
| <!-- Wholesale Price --> | |||
| <td> | |||
| <span v-if="order?.product?.wholesale_price"> | |||
| {{ formatWithCommas(order?.product?.wholesale_price) }} | |||
| تومان | |||
| </span> | |||
| <i | |||
| class="ph-duotone ph-x-circle text-danger f-24" | |||
| data-bs-toggle="tooltip" | |||
| data-bs-title="danger" | |||
| v-else | |||
| class="ph-duotone ph-x-circle text-danger fs-5" | |||
| title="قیمت نامشخص" | |||
| ></i> | |||
| </td> | |||
| <td v-if="product?.product?.retail_price" class="text-end"> | |||
| {{ formatWithCommas(product?.product?.retail_price) }} | |||
| </td> | |||
| <td v-if="!product?.product?.retail_price" class="text-end"> | |||
| <!-- Retail Price --> | |||
| <td> | |||
| <span v-if="order?.product?.retail_price"> | |||
| {{ formatWithCommas(order?.product?.retail_price) }} | |||
| </span> | |||
| <i | |||
| class="ph-duotone ph-x-circle text-danger f-24" | |||
| data-bs-toggle="tooltip" | |||
| data-bs-title="danger" | |||
| v-else | |||
| class="ph-duotone ph-x-circle text-danger fs-5" | |||
| title="قیمت نامشخص" | |||
| ></i> | |||
| </td> | |||
| <td class="text-end">{{ product.count }}</td> | |||
| <td class="text-end">{{ product.send_count }}</td> | |||
| <td class="text-center"> | |||
| {{ product.product?.brand?.title }} | |||
| </td> | |||
| <td class="text-center"> | |||
| <!-- Order Count --> | |||
| <td>{{ order.count }}</td> | |||
| <!-- Sent Count --> | |||
| <td>{{ order.send_count }}</td> | |||
| <!-- Brand Title --> | |||
| <td>{{ order.product?.brand?.title }}</td> | |||
| <!-- Brand Image --> | |||
| <td> | |||
| <img | |||
| :src="product.product?.brand?.image" | |||
| alt="user-image" | |||
| class="wid-40 brand-img" | |||
| :src="order.product?.brand?.image" | |||
| alt="brand" | |||
| class="rounded" | |||
| style="width: 40px; height: 40px; object-fit: cover" | |||
| /> | |||
| </td> | |||
| <!-- Operations --> | |||
| <td> | |||
| <router-link | |||
| :to="`/singleOrder/${order?.id}`" | |||
| class="btn btn-sm btn-outline-primary me-1" | |||
| > | |||
| مشاهده و ویرایش | |||
| </router-link> | |||
| <button | |||
| class="btn btn-sm btn-outline-warning dropdown-toggle me-1" | |||
| type="button" | |||
| id="dropdownMenuButton" | |||
| data-bs-toggle="dropdown" | |||
| aria-expanded="false" | |||
| > | |||
| ویرایش وضعیت | |||
| </button> | |||
| <ul | |||
| class="dropdown-menu" | |||
| aria-labelledby="dropdownMenuButton" | |||
| style="cursor: pointer" | |||
| > | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus(order?.id, 'waiting')" | |||
| > | |||
| <span class="badge badge-waiting">در انتظار</span> | |||
| </a> | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus(order?.id, 'paid')" | |||
| > | |||
| <span class="badge badge-paid">پرداختشده</span> | |||
| </a> | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus(order?.id, 'un_paid')" | |||
| > | |||
| <span class="badge badge-un_paid">پرداختنشده</span> | |||
| </a> | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus(order?.id, 'approved')" | |||
| > | |||
| <span class="badge badge-approved">تأییدشده</span> | |||
| </a> | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus(order?.id, 'processing')" | |||
| > | |||
| <span class="badge badge-processing" | |||
| >در حال پردازش</span | |||
| > | |||
| </a> | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus(order?.id, 'shipping')" | |||
| > | |||
| <span class="badge badge-shipping" | |||
| >در حال ارسال</span | |||
| > | |||
| </a> | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus(order?.id, 'delivered')" | |||
| > | |||
| <span class="badge badge-delivered">تحویلشده</span> | |||
| </a> | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus(order?.id, 'canceled')" | |||
| > | |||
| <span class="badge badge-canceled">لغوشده</span> | |||
| </a> | |||
| </li> | |||
| </ul> | |||
| </td> | |||
| </tr> | |||
| </tbody> | |||
| </table> | |||
| </div> | |||
| <div | |||
| v-else | |||
| class="filter-loader card table-card user-profile-list" | |||
| class="filter-loader card table-card user-profile-list" | |||
| ></div> | |||
| </BCardBody> | |||
| </BCard> | |||
| </BCol> | |||
| </BRow> | |||
| <showDescription :desc="productDescription" /> | |||
| <BRow> | |||
| <BCol sm="12"> | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <!-- Previous page --> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <!-- First page and leading dots --> | |||
| <li | |||
| v-if="visiblePages[0] > 1" | |||
| class="page-item" | |||
| @click="page = 1" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <li v-if="visiblePages[0] > 2" class="page-item disabled"> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <!-- Visible pages --> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| @@ -384,23 +608,24 @@ export default { | |||
| </a> | |||
| </li> | |||
| <!-- Trailing dots and last page --> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages - 1" | |||
| class="page-item disabled" | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| <a class="page-link" href="javascript:void(0)"> | |||
| {{ totalPages }} | |||
| </a> | |||
| </li> | |||
| <!-- Next page --> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| @@ -488,4 +713,75 @@ export default { | |||
| .custom-datepicker { | |||
| min-width: 340px; | |||
| } | |||
| .subject-box { | |||
| padding: 8px 14px; | |||
| background: linear-gradient(135deg, #fff3e0, #ffe0b2); | |||
| color: #ef6c00; | |||
| font-weight: 600; | |||
| border-radius: 10px; | |||
| box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1); | |||
| display: inline-flex; | |||
| align-items: center; | |||
| gap: 8px; | |||
| transition: transform 0.2s ease, box-shadow 0.2s ease; | |||
| } | |||
| .subject-box i { | |||
| color: #ef6c00; | |||
| font-size: 1rem; | |||
| } | |||
| .subject-box:hover { | |||
| transform: translateY(-2px); | |||
| box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.15); | |||
| } | |||
| .badge { | |||
| display: inline-block; | |||
| padding: 5px 10px; | |||
| border-radius: 12px; | |||
| font-size: 0.85rem; | |||
| font-weight: 500; | |||
| text-transform: capitalize; | |||
| color: white; | |||
| } | |||
| .badge-waiting { | |||
| background-color: #ffc107; | |||
| } | |||
| .badge-paid { | |||
| background-color: #28a745; | |||
| } | |||
| .badge-un_paid { | |||
| background-color: #dc3545; | |||
| } | |||
| .badge-approved { | |||
| background-color: #17a2b8; | |||
| } | |||
| .badge-processing { | |||
| background-color: #007bff; | |||
| } | |||
| .badge-shipping { | |||
| background-color: #6f42c1; | |||
| } | |||
| .badge-delivered { | |||
| background-color: #20c997; | |||
| } | |||
| .badge-canceled { | |||
| background-color: #6c757d; | |||
| } | |||
| .badge-in-cart { | |||
| background-color: #0620c6; /* You can choose any color you prefer */ | |||
| } | |||
| .badge-in-done { | |||
| background-color: #a2ca2c; /* You can choose any color you prefer */ | |||
| } | |||
| .filter-loader { | |||
| border: 4px solid rgba(0, 123, 255, 0.3); | |||
| border-top: 4px solid #007bff; | |||
| border-radius: 50%; | |||
| width: 40px; | |||
| height: 40px; | |||
| animation: spin 1s linear infinite; | |||
| margin: 20px auto; | |||
| } | |||
| </style> | |||
| @@ -19,7 +19,6 @@ export default { | |||
| const totalPages = ref(1); | |||
| const paginate = ref(20); | |||
| const page = ref(1); | |||
| const filterLoading = ref(false); | |||
| const searchQuery = ref(""); | |||
| const orders = ref(); | |||
| @@ -29,6 +28,34 @@ export default { | |||
| .locale("fa") | |||
| .format("YYYY/MM/DD"); | |||
| }; | |||
| const loadingId = ref(null); | |||
| const getExport = (id) => { | |||
| loadingId.value = id; | |||
| ApiServiece.post(`admin/orders/${id}/export`, { responseType: "blob" }) | |||
| .then((resp) => { | |||
| const excelBlob = resp.data; | |||
| const url = window.URL.createObjectURL(excelBlob); | |||
| const link = document.createElement("a"); | |||
| link.href = url; | |||
| link.setAttribute("download", "order-items-export.xlsx"); | |||
| document.body.appendChild(link); | |||
| link.click(); | |||
| window.URL.revokeObjectURL(url); | |||
| document.body.removeChild(link); | |||
| loadingId.value = null; | |||
| }) | |||
| .catch((err) => { | |||
| console.error(err, "export err"); | |||
| loadingId.value = null; | |||
| }); | |||
| }; | |||
| watch(searchQuery, () => { | |||
| getOrders(); | |||
| page.value = 1; | |||
| @@ -62,8 +89,15 @@ export default { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| if (start < 1) { | |||
| end += 1 - start; | |||
| start = 1; | |||
| } | |||
| if (end > totalPages.value) { | |||
| start -= end - totalPages.value; | |||
| end = totalPages.value; | |||
| } | |||
| start = Math.max(start, 1); | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| @@ -227,6 +261,8 @@ export default { | |||
| getStatusClass, | |||
| getStatusLabel, | |||
| selectedStatus, | |||
| getExport, | |||
| loadingId, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -272,7 +308,6 @@ export default { | |||
| <table class="table table-hover table-bordered m-0" dir="rtl"> | |||
| <thead class="table-light"> | |||
| <tr> | |||
| <th>وضعیت</th> | |||
| <th>قیمت</th> | |||
| <th>کاربر</th> | |||
| @@ -283,7 +318,6 @@ export default { | |||
| </thead> | |||
| <tbody> | |||
| <tr v-for="order in orders" :key="order.id"> | |||
| <td> | |||
| <span class="badge" :class="getStatusClass(order.status)"> | |||
| {{ getStatusLabel(order.status) }} | |||
| @@ -313,6 +347,28 @@ export default { | |||
| > | |||
| ویرایش وضعیت | |||
| </button> | |||
| <button | |||
| class="btn btn-sm me-1" | |||
| :class=" | |||
| loadingId === order.id | |||
| ? 'btn-outline-secondary' | |||
| : 'btn-outline-success' | |||
| " | |||
| type="button" | |||
| :disabled="loadingId === order.id" | |||
| @click="getExport(order.id)" | |||
| > | |||
| <span v-if="loadingId === order.id"> | |||
| <span | |||
| class="spinner-border spinner-border-sm me-1" | |||
| role="status" | |||
| aria-hidden="true" | |||
| ></span> | |||
| در حال خروجی... | |||
| </span> | |||
| <span v-else> خروجی </span> | |||
| </button> | |||
| <ul | |||
| class="dropdown-menu" | |||
| aria-labelledby="dropdownMenuButton" | |||
| @@ -417,15 +473,19 @@ export default { | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <!-- Page numbers with dots logic --> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <!-- First page and leading dots --> | |||
| <li | |||
| v-if="visiblePages[0] > 1" | |||
| class="page-item" | |||
| @click="page = 1" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <li v-if="visiblePages[0] > 2" class="page-item disabled"> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <!-- Page numbers --> | |||
| <!-- Visible pages --> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| @@ -441,21 +501,21 @@ export default { | |||
| </a> | |||
| </li> | |||
| <!-- Trailing dots and last page --> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages - 1" | |||
| class="page-item disabled" | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| <a class="page-link" href="javascript:void(0)"> | |||
| {{ totalPages }} | |||
| </a> | |||
| </li> | |||
| <!-- Next page --> | |||
| @@ -48,8 +48,17 @@ export default { | |||
| const getOrder = () => { | |||
| ApiServiece.get(`admin/orders/${route.params.id}`) | |||
| .then((resp) => { | |||
| order.value = resp.data.data; | |||
| console.log(order.value, "order"); | |||
| const rawOrder = resp.data.data; | |||
| if (Array.isArray(rawOrder.order_items)) { | |||
| rawOrder.order_items = rawOrder.order_items.map((item) => ({ | |||
| ...item, | |||
| description: item.description ?? "", | |||
| })); | |||
| } | |||
| order.value = rawOrder; | |||
| }) | |||
| .catch((error) => { | |||
| console.error("Failed to fetch order details:", error); | |||
| @@ -58,7 +67,7 @@ export default { | |||
| const updateShippedCount = (item) => { | |||
| const formData = new FormData(); | |||
| formData.append("send_count", item.send_count); | |||
| formData.append("send_count", item.send_count || 0); | |||
| ApiServiece.put(`admin/orders/order-items/${item.id}`, formData) | |||
| .then(() => { | |||
| console.log("Shipped quantity updated successfully."); | |||
| @@ -93,9 +102,6 @@ export default { | |||
| console.error("Failed to update status:", error); | |||
| }); | |||
| }; | |||
| function formatWithCommas(number) { | |||
| return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); | |||
| } | |||
| onMounted(() => { | |||
| getOrder(); | |||
| @@ -109,7 +115,6 @@ export default { | |||
| updateShippedCount, | |||
| updateNote, | |||
| updateStatus, | |||
| formatWithCommas, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -126,43 +131,53 @@ export default { | |||
| <h5 class="mb-0">جزئیات سفارش</h5> | |||
| </BCardHeader> | |||
| <BCardBody v-if="order"> | |||
| <div class="mb-4"> | |||
| <BRow class="mb-2"> | |||
| <BCol md="6" | |||
| ><strong>نام مشتری:</strong> | |||
| {{ order?.user || "بدون نام" }}</BCol | |||
| > | |||
| <div class="p-3 mb-4 border rounded"> | |||
| <BRow class="mb-3"> | |||
| <BCol md="6"> | |||
| <strong class="me-2">نام مشتری:</strong> | |||
| <span>{{ order?.user?.name || "بدون نام" }}</span> | |||
| </BCol> | |||
| </BRow> | |||
| <BRow class="mb-2"> | |||
| <BCol md="6" | |||
| ><strong>آدرس:</strong> | |||
| {{ order?.user_address || "بدون آدرس" }}</BCol | |||
| > | |||
| <BRow class="mb-3"> | |||
| <BCol md="6"> | |||
| <strong class="me-2">آدرس:</strong> | |||
| <span>{{ order?.user_address?.address || "بدون آدرس" }}</span> | |||
| </BCol> | |||
| <BCol md="6"> | |||
| <strong class="me-2">شهر:</strong> | |||
| <span>{{ order?.user_address?.city || "بدون شهر" }}</span> | |||
| </BCol> | |||
| </BRow> | |||
| <BRow class="mb-2"> | |||
| <BCol md="6" | |||
| ><strong>هزینه ارسال:</strong> | |||
| {{ formatWithCommas(order.shipping_price) }} تومان</BCol | |||
| > | |||
| <BCol md="6" | |||
| ><strong>تخفیف:</strong> | |||
| {{ formatWithCommas(order.discount) }} تومان</BCol | |||
| > | |||
| <BRow class="mb-3"> | |||
| <BCol md="6"> | |||
| <strong class="me-2">هزینه ارسال:</strong> | |||
| <span> | |||
| {{ | |||
| order?.shipping_price != null | |||
| ? order.shipping_price + " تومان" | |||
| : "بدون قیمت" | |||
| }} | |||
| </span> | |||
| </BCol> | |||
| <BCol md="6"> | |||
| <strong class="me-2">تخفیف:</strong> | |||
| <span> | |||
| {{ | |||
| order?.discount != null | |||
| ? order.discount + " تومان" | |||
| : "بدون تخفیف" | |||
| }} | |||
| </span> | |||
| </BCol> | |||
| </BRow> | |||
| <BRow class="mb-2"> | |||
| <BCol md="6" | |||
| ><strong>قیمت کل:</strong> | |||
| {{ | |||
| formatWithCommas( | |||
| order.total_price + order.shipping_price - order.discount | |||
| ) | |||
| }} | |||
| تومان | |||
| <BRow> | |||
| <BCol md="6"> | |||
| <strong class="me-2">رنگ سفارش:</strong> | |||
| <span>{{ order.color || "---" }}</span> | |||
| </BCol> | |||
| <BCol md="6" | |||
| ><strong>رنگ سفارش:</strong> {{ order.color || "---" }}</BCol | |||
| > | |||
| </BRow> | |||
| </div> | |||
| @@ -187,7 +202,7 @@ export default { | |||
| > | |||
| <!-- Price formatting --> | |||
| <template #cell(price)="data"> | |||
| {{ formatWithCommas(data.item.price) }} تومان | |||
| {{ data.item.price }} تومان | |||
| </template> | |||
| <template #cell(title)="data"> | |||
| @@ -292,6 +292,7 @@ | |||
| :reduce="(option) => option.value" | |||
| :options="formattedCategories" | |||
| placeholder="دسته ای را انتخاب کنید" | |||
| @search="handleSearch" | |||
| /> | |||
| </div> | |||
| <small v-if="errors.selectedCat" class="text-danger"> | |||
| @@ -327,7 +328,6 @@ | |||
| md="6" | |||
| lg="4" | |||
| > | |||
| <div class="card shadow-sm"> | |||
| <div class="card-body"> | |||
| <!-- Card Header with Delete Icon --> | |||
| @@ -394,7 +394,11 @@ | |||
| <button | |||
| class="btn btn-primary" | |||
| @click=" | |||
| editAttribute(attrebute.id, attrebute.value , attrebute.attribute_value_id) | |||
| editAttribute( | |||
| attrebute.id, | |||
| attrebute.value, | |||
| attrebute.attribute_value_id | |||
| ) | |||
| " | |||
| > | |||
| ویرایش | |||
| @@ -896,6 +900,21 @@ export default { | |||
| : [] | |||
| ); | |||
| const handleSearch = async (searchTerm) => { | |||
| if (searchTerm.length < 3) return; | |||
| categorySelectorLoader.value = true; | |||
| try { | |||
| const response = await ApiServiece.get( | |||
| `admin/categories?title=${searchTerm}` | |||
| ); | |||
| categories.value = response.data.data; | |||
| categorySelectorLoader.value = false; | |||
| } catch (error) { | |||
| categorySelectorLoader.value = false; | |||
| categories.value = []; | |||
| } | |||
| }; | |||
| const formattedBrands = computed(() => | |||
| Array.isArray(brands.value) | |||
| ? brands.value.map((brand) => ({ | |||
| @@ -905,6 +924,21 @@ export default { | |||
| : [] | |||
| ); | |||
| const handleBrandSearch = async (searchTerm) => { | |||
| if (searchTerm.length < 3) return; | |||
| brandSelectorLoader.value = true; | |||
| try { | |||
| const response = await ApiServiece.get( | |||
| `admin/brands?title=${searchTerm}` | |||
| ); | |||
| brands.value = response.data.data; | |||
| brandSelectorLoader.value = false; | |||
| } catch (error) { | |||
| brandSelectorLoader.value = false; | |||
| brands.value = []; | |||
| } | |||
| }; | |||
| watch(selectedCat, () => { | |||
| ApiServiece.get(`admin/attributes?category_id=${selectedCat.value}`) | |||
| .then((resp) => { | |||
| @@ -920,7 +954,7 @@ export default { | |||
| ApiServiece.get(`admin/attributes?category_id=${selectedCat.value}`) | |||
| .then((resp) => { | |||
| identities.value = resp.data.data; | |||
| console.log(identities.value , "identities"); | |||
| console.log(identities.value, "identities"); | |||
| }) | |||
| .then(() => { | |||
| localIdentitiesIds.value = localIdentities.value.map( | |||
| @@ -1303,7 +1337,7 @@ export default { | |||
| }); | |||
| }; | |||
| const editAttribute = (id, inventory , attrebuteValueId) => { | |||
| const editAttribute = (id, inventory, attrebuteValueId) => { | |||
| Swal.fire({ | |||
| text: "آیا برای ویرایش ویژگی اطمینان دارید؟", | |||
| icon: "warning", | |||
| @@ -1616,6 +1650,8 @@ export default { | |||
| formattedCategories, | |||
| formattedBrands, | |||
| brandSelectorLoader, | |||
| handleSearch, | |||
| handleBrandSearch, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -96,8 +96,15 @@ export default { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| if (start < 1) { | |||
| end += 1 - start; | |||
| start = 1; | |||
| } | |||
| if (end > totalPages.value) { | |||
| start -= end - totalPages.value; | |||
| end = totalPages.value; | |||
| } | |||
| start = Math.max(start, 1); | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| @@ -105,7 +112,6 @@ export default { | |||
| } | |||
| return pages; | |||
| }); | |||
| const deleteProduct = (id, title) => { | |||
| Swal.fire({ | |||
| text: `می خواهید محصول ${title} را حذف کنید؟`, | |||
| @@ -473,17 +479,24 @@ export default { | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <!-- Previous page --> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <!-- First page and leading dots --> | |||
| <li | |||
| v-if="visiblePages[0] > 1" | |||
| class="page-item" | |||
| @click="page = 1" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <li v-if="visiblePages[0] > 2" class="page-item disabled"> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <!-- Visible pages --> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| @@ -499,23 +512,24 @@ export default { | |||
| </a> | |||
| </li> | |||
| <!-- Trailing dots and last page --> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages - 1" | |||
| class="page-item disabled" | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| <a class="page-link" href="javascript:void(0)"> | |||
| {{ totalPages }} | |||
| </a> | |||
| </li> | |||
| <!-- Next page --> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| @@ -9,6 +9,7 @@ import Quill from "quill"; | |||
| import "quill/dist/quill.snow.css"; | |||
| import ApiService from "@/services/ApiService"; | |||
| import DatePicker from "vue3-persian-datetime-picker"; | |||
| const isFirstLoad = ref(true); | |||
| const settings = ref([]); | |||
| const editors = ref({}); | |||
| @@ -18,17 +19,20 @@ const getSettings = async () => { | |||
| try { | |||
| const response = await ApiService.get("admin/settings"); | |||
| settings.value = response.data.data; | |||
| settings.value.forEach((setting) => { | |||
| if (setting.type === "image" && setting.value) { | |||
| setting.preview = setting.value; | |||
| localStorage.setItem("logo" , setting.value) | |||
| localStorage.setItem("logo", setting.value); | |||
| } | |||
| loading.value[setting.id] = false; | |||
| }); | |||
| await nextTick(); | |||
| initQuillEditors(); | |||
| if (isFirstLoad.value) { | |||
| initQuillEditors(); | |||
| isFirstLoad.value = false; | |||
| } | |||
| } catch (error) { | |||
| console.error("Error fetching settings:", error); | |||
| } | |||
| @@ -36,7 +40,7 @@ const getSettings = async () => { | |||
| const initQuillEditors = () => { | |||
| settings.value.forEach((setting) => { | |||
| if (setting.type === "longtext") { | |||
| if (setting.type === "longtext" && !editors.value[setting.id]) { | |||
| const quillContainer = document.getElementById(`editor-${setting.id}`); | |||
| if (quillContainer) { | |||
| const quill = new Quill(quillContainer, { | |||
| @@ -47,7 +51,7 @@ const initQuillEditors = () => { | |||
| [{ list: "ordered" }, { list: "bullet" }], | |||
| [{ align: [] }], | |||
| ["bold", "italic", "underline"], | |||
| ["link", "image"], | |||
| ["link"], // Removed "image" from here | |||
| [{ script: "sub" }, { script: "super" }], | |||
| [{ direction: "rtl" }], | |||
| ], | |||
| @@ -43,7 +43,7 @@ export default { | |||
| `admin/users?name=${searchQuery.value || ""}&paginate=${ | |||
| paginate.value || 10 | |||
| }&page=${page.value || 1}&role=${ | |||
| selectedRole.value || "admin" | |||
| selectedRole.value || "" | |||
| }&trashed=${selectedStatus.value || ""}` | |||
| ) | |||
| .then((resp) => { | |||
| @@ -82,8 +82,15 @@ export default { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| if (start < 1) { | |||
| end += 1 - start; | |||
| start = 1; | |||
| } | |||
| if (end > totalPages.value) { | |||
| start -= end - totalPages.value; | |||
| end = totalPages.value; | |||
| } | |||
| start = Math.max(start, 1); | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| @@ -402,17 +409,24 @@ export default { | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <!-- Previous page --> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <!-- First page and leading dots --> | |||
| <li | |||
| v-if="visiblePages[0] > 1" | |||
| class="page-item" | |||
| @click="page = 1" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <li v-if="visiblePages[0] > 2" class="page-item disabled"> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <!-- Visible pages --> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| @@ -428,23 +442,24 @@ export default { | |||
| </a> | |||
| </li> | |||
| <!-- Trailing dots and last page --> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages - 1" | |||
| class="page-item disabled" | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| <a class="page-link" href="javascript:void(0)"> | |||
| {{ totalPages }} | |||
| </a> | |||
| </li> | |||
| <!-- Next page --> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||