| @@ -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"); | const faviconUrl = localStorage.getItem("logo"); | ||||
| if (faviconUrl) { | if (faviconUrl) { | ||||
| const faviconLink = document.getElementById("favicon"); | const faviconLink = document.getElementById("favicon"); | ||||
| @@ -107,8 +107,15 @@ export default { | |||||
| let start = currentPage.value - 2; | let start = currentPage.value - 2; | ||||
| let end = 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++) { | for (let i = start; i <= end; i++) { | ||||
| pages.push(i); | pages.push(i); | ||||
| @@ -116,7 +123,6 @@ export default { | |||||
| } | } | ||||
| return pages; | return pages; | ||||
| }); | }); | ||||
| watch(page, () => { | watch(page, () => { | ||||
| getAttributes(); | getAttributes(); | ||||
| }); | }); | ||||
| @@ -298,17 +304,24 @@ export default { | |||||
| <div class="d-flex justify-content-center"> | <div class="d-flex justify-content-center"> | ||||
| <nav aria-label="Page navigation"> | <nav aria-label="Page navigation"> | ||||
| <ul class="pagination"> | <ul class="pagination"> | ||||
| <!-- Previous page --> | |||||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | <li class="page-item" :class="{ disabled: currentPage === 1 }"> | ||||
| <span class="page-link" @click="prevPage">قبلی</span> | <span class="page-link" @click="prevPage">قبلی</span> | ||||
| </li> | </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> | <a class="page-link" href="javascript:void(0)">1</a> | ||||
| </li> | </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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <!-- Visible pages --> | |||||
| <li | <li | ||||
| v-for="n in visiblePages" | v-for="n in visiblePages" | ||||
| :key="n" | :key="n" | ||||
| @@ -324,23 +337,24 @@ export default { | |||||
| </a> | </a> | ||||
| </li> | </li> | ||||
| <!-- Trailing dots and last page --> | |||||
| <li | <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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <li | <li | ||||
| v-if="currentPage < totalPages - 1" | |||||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||||
| class="page-item" | class="page-item" | ||||
| @click="page = totalPages" | @click="page = totalPages" | ||||
| > | > | ||||
| <a class="page-link" href="javascript:void(0)">{{ | |||||
| totalPages | |||||
| }}</a> | |||||
| <a class="page-link" href="javascript:void(0)"> | |||||
| {{ totalPages }} | |||||
| </a> | |||||
| </li> | </li> | ||||
| <!-- Next page --> | |||||
| <li | <li | ||||
| class="page-item" | class="page-item" | ||||
| :class="{ disabled: currentPage === totalPages }" | :class="{ disabled: currentPage === totalPages }" | ||||
| @@ -112,18 +112,16 @@ | |||||
| <BCol v-if="pageType === 'category' && pannel === 'web'" md="6"> | <BCol v-if="pageType === 'category' && pannel === 'web'" md="6"> | ||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label class="form-label">صفحه دسته</label> | <label class="form-label">صفحه دسته</label> | ||||
| <select | |||||
| <VueSelect | |||||
| style="--vs-min-height: 48px; --vs-border-radius: 8px" | |||||
| v-model="selectedCatPage" | v-model="selectedCatPage" | ||||
| class="form-select" | |||||
| :class="{ 'is-invalid': errors.selectedCatPage }" | |||||
| aria-label="Default select example" | |||||
| :isLoading="categoryPageSelectorLoader" | |||||
| :options="formattedCategoriesPages" | |||||
| @change="clearError('selectedCatPage')" | @change="clearError('selectedCatPage')" | ||||
| placeholder="انتخاب صفحه دسته" | |||||
| > | |||||
| <option :value="cat.id" v-for="cat in cats" :key="cat.id"> | |||||
| {{ cat.title }} | |||||
| </option> | |||||
| </select> | |||||
| placeholder="دسته ای را انتخاب کنید" | |||||
| @search="handleCategoryPageSearch" | |||||
| /> | |||||
| </div> | </div> | ||||
| <small v-if="errors.selectedCatPage" class="text-danger"> | <small v-if="errors.selectedCatPage" class="text-danger"> | ||||
| {{ errors.selectedCatPage }} | {{ errors.selectedCatPage }} | ||||
| @@ -133,22 +131,16 @@ | |||||
| <BCol v-if="pageType === 'brand' && pannel === 'web'" md="6"> | <BCol v-if="pageType === 'brand' && pannel === 'web'" md="6"> | ||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label class="form-label">صفحه برند</label> | <label class="form-label">صفحه برند</label> | ||||
| <select | |||||
| <VueSelect | |||||
| style="--vs-min-height: 48px; --vs-border-radius: 8px" | |||||
| v-model="selectedBrandPage" | 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> | </div> | ||||
| <small v-if="errors.selectedBrandPage" class="text-danger"> | <small v-if="errors.selectedBrandPage" class="text-danger"> | ||||
| {{ errors.selectedBrandPage }} | {{ errors.selectedBrandPage }} | ||||
| @@ -190,6 +182,8 @@ | |||||
| margin-top: 7px; | margin-top: 7px; | ||||
| " | " | ||||
| v-model="selectedLandingProduct" | v-model="selectedLandingProduct" | ||||
| :isLoading="productSelectorLoader" | |||||
| @change="clearError(`selectedLandingProduct`)" | |||||
| :options="formattedProducts" | :options="formattedProducts" | ||||
| placeholder="محصولی را انتخاب کنید" | placeholder="محصولی را انتخاب کنید" | ||||
| @search="handleSearch" | @search="handleSearch" | ||||
| @@ -202,18 +196,16 @@ | |||||
| <BCol v-if="landingType === 'cat'" md="6"> | <BCol v-if="landingType === 'cat'" md="6"> | ||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label class="form-label">صفحه کدام دسته</label> | <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" | v-model="selectedLandingCat" | ||||
| @change="clearError('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> | </div> | ||||
| <small v-if="errors.selectedLandingCat" class="text-danger"> | <small v-if="errors.selectedLandingCat" class="text-danger"> | ||||
| {{ errors.selectedLandingCat }} | {{ errors.selectedLandingCat }} | ||||
| @@ -386,7 +378,7 @@ import mainPageBanner from "@/components/modals/helperModals/mainPageBanner.vue" | |||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| import { ref, onMounted, computed } from "vue"; | |||||
| import { ref, computed } from "vue"; | |||||
| import Layout from "@/layout/custom.vue"; | import Layout from "@/layout/custom.vue"; | ||||
| export default { | export default { | ||||
| @@ -402,6 +394,7 @@ export default { | |||||
| const pageType = ref(); | const pageType = ref(); | ||||
| const products = ref([]); | const products = ref([]); | ||||
| const cats = ref([]); | const cats = ref([]); | ||||
| const categoryPages = ref([]); | |||||
| const brands = ref([]); | const brands = ref([]); | ||||
| const landingType = ref(); | const landingType = ref(); | ||||
| const selectedCatPage = ref(); | const selectedCatPage = ref(); | ||||
| @@ -412,33 +405,16 @@ export default { | |||||
| const pannel = ref(); | const pannel = ref(); | ||||
| const image = ref(); | const image = ref(); | ||||
| const imagePreview = 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 loading = ref(false); | ||||
| const errors = ref({}); | 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) => { | const handleSearch = async (searchTerm) => { | ||||
| if (searchTerm.length < 3) return; | if (searchTerm.length < 3) return; | ||||
| productSelectorLoader.value = true; | |||||
| try { | try { | ||||
| const response = await ApiServiece.get( | const response = await ApiServiece.get( | ||||
| @@ -446,9 +422,35 @@ export default { | |||||
| ); | ); | ||||
| products.value = response.data.data; | products.value = response.data.data; | ||||
| console.log(products.value, "products"); | 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) { | } 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 handleImageUpload = (event) => { | ||||
| const file = event.target.files[0]; | const file = event.target.files[0]; | ||||
| @@ -515,11 +567,6 @@ export default { | |||||
| errors.value[field] = ""; | errors.value[field] = ""; | ||||
| }; | }; | ||||
| onMounted(() => { | |||||
| getCats(); | |||||
| getBrands(); | |||||
| }); | |||||
| const submitForm = () => { | const submitForm = () => { | ||||
| console.log(errors.value); | console.log(errors.value); | ||||
| if (!validateForm()) { | if (!validateForm()) { | ||||
| @@ -613,6 +660,7 @@ export default { | |||||
| pageType.value = ""; | pageType.value = ""; | ||||
| landingType.value = ""; | landingType.value = ""; | ||||
| image.value = null; | image.value = null; | ||||
| imagePreview.value = null; | |||||
| loading.value = false; | loading.value = false; | ||||
| }; | }; | ||||
| return { | return { | ||||
| @@ -637,6 +685,16 @@ export default { | |||||
| loading, | loading, | ||||
| brands, | brands, | ||||
| handleSearch, | handleSearch, | ||||
| productSelectorLoader, | |||||
| handleCategorySearch, | |||||
| categorySelectorLoader, | |||||
| formattedCategories, | |||||
| handleCategoryPageSearch, | |||||
| formattedCategoriesPages, | |||||
| categoryPageSelectorLoader, | |||||
| brandSelectorLoader, | |||||
| formattedBrands, | |||||
| handleBrandSearch, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -78,8 +78,15 @@ export default { | |||||
| let start = currentPage.value - 2; | let start = currentPage.value - 2; | ||||
| let end = 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++) { | for (let i = start; i <= end; i++) { | ||||
| pages.push(i); | pages.push(i); | ||||
| @@ -363,17 +370,24 @@ export default { | |||||
| <div class="d-flex justify-content-center"> | <div class="d-flex justify-content-center"> | ||||
| <nav aria-label="Page navigation"> | <nav aria-label="Page navigation"> | ||||
| <ul class="pagination"> | <ul class="pagination"> | ||||
| <!-- Previous page --> | |||||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | <li class="page-item" :class="{ disabled: currentPage === 1 }"> | ||||
| <span class="page-link" @click="prevPage">قبلی</span> | <span class="page-link" @click="prevPage">قبلی</span> | ||||
| </li> | </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> | <a class="page-link" href="javascript:void(0)">1</a> | ||||
| </li> | </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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <!-- Visible pages --> | |||||
| <li | <li | ||||
| v-for="n in visiblePages" | v-for="n in visiblePages" | ||||
| :key="n" | :key="n" | ||||
| @@ -389,23 +403,24 @@ export default { | |||||
| </a> | </a> | ||||
| </li> | </li> | ||||
| <!-- Trailing dots and last page --> | |||||
| <li | <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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <li | <li | ||||
| v-if="currentPage < totalPages - 1" | |||||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||||
| class="page-item" | class="page-item" | ||||
| @click="page = totalPages" | @click="page = totalPages" | ||||
| > | > | ||||
| <a class="page-link" href="javascript:void(0)">{{ | |||||
| totalPages | |||||
| }}</a> | |||||
| <a class="page-link" href="javascript:void(0)"> | |||||
| {{ totalPages }} | |||||
| </a> | |||||
| </li> | </li> | ||||
| <!-- Next page --> | |||||
| <li | <li | ||||
| class="page-item" | class="page-item" | ||||
| :class="{ disabled: currentPage === totalPages }" | :class="{ disabled: currentPage === totalPages }" | ||||
| @@ -495,14 +495,15 @@ export default { | |||||
| selectedLandingCat.value = data?.category_id; | selectedLandingCat.value = data?.category_id; | ||||
| selectedLandingProduct.value = data?.product_id; | selectedLandingProduct.value = data?.product_id; | ||||
| pageType.value = data.page_type; | pageType.value = data.page_type; | ||||
| if (data.page_id) { | |||||
| pageType.value = "category"; | |||||
| if (data.page_id && pageType.value === 'category') { | |||||
| selectedCatPage.value = data?.page_id; | 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) { | if (selectedLandingProduct.value) { | ||||
| landingType.value = "product"; | landingType.value = "product"; | ||||
| @@ -510,7 +511,6 @@ export default { | |||||
| if (selectedLandingCat.value) { | if (selectedLandingCat.value) { | ||||
| landingType.value = "cat"; | landingType.value = "cat"; | ||||
| console.log("Asd"); | |||||
| } | } | ||||
| if (!selectedCatPage.value && !selectedLandingProduct.value) { | if (!selectedCatPage.value && !selectedLandingProduct.value) { | ||||
| @@ -75,8 +75,15 @@ export default { | |||||
| let start = currentPage.value - 2; | let start = currentPage.value - 2; | ||||
| let end = 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++) { | for (let i = start; i <= end; i++) { | ||||
| pages.push(i); | pages.push(i); | ||||
| @@ -268,17 +275,24 @@ export default { | |||||
| <div class="d-flex justify-content-center"> | <div class="d-flex justify-content-center"> | ||||
| <nav aria-label="Page navigation"> | <nav aria-label="Page navigation"> | ||||
| <ul class="pagination"> | <ul class="pagination"> | ||||
| <!-- Previous page --> | |||||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | <li class="page-item" :class="{ disabled: currentPage === 1 }"> | ||||
| <span class="page-link" @click="prevPage">قبلی</span> | <span class="page-link" @click="prevPage">قبلی</span> | ||||
| </li> | </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> | <a class="page-link" href="javascript:void(0)">1</a> | ||||
| </li> | </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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <!-- Visible pages --> | |||||
| <li | <li | ||||
| v-for="n in visiblePages" | v-for="n in visiblePages" | ||||
| :key="n" | :key="n" | ||||
| @@ -294,23 +308,24 @@ export default { | |||||
| </a> | </a> | ||||
| </li> | </li> | ||||
| <!-- Trailing dots and last page --> | |||||
| <li | <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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <li | <li | ||||
| v-if="currentPage < totalPages - 1" | |||||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||||
| class="page-item" | class="page-item" | ||||
| @click="page = totalPages" | @click="page = totalPages" | ||||
| > | > | ||||
| <a class="page-link" href="javascript:void(0)">{{ | |||||
| totalPages | |||||
| }}</a> | |||||
| <a class="page-link" href="javascript:void(0)"> | |||||
| {{ totalPages }} | |||||
| </a> | |||||
| </li> | </li> | ||||
| <!-- Next page --> | |||||
| <li | <li | ||||
| class="page-item" | class="page-item" | ||||
| :class="{ disabled: currentPage === totalPages }" | :class="{ disabled: currentPage === totalPages }" | ||||
| @@ -75,8 +75,15 @@ export default { | |||||
| let start = currentPage.value - 2; | let start = currentPage.value - 2; | ||||
| let end = 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++) { | for (let i = start; i <= end; i++) { | ||||
| pages.push(i); | pages.push(i); | ||||
| @@ -84,7 +91,6 @@ export default { | |||||
| } | } | ||||
| return pages; | return pages; | ||||
| }); | }); | ||||
| const deleteBlog = (id, title) => { | const deleteBlog = (id, title) => { | ||||
| Swal.fire({ | Swal.fire({ | ||||
| text: `می خواهید بلاگ ${title} را حذف کنید؟`, | text: `می خواهید بلاگ ${title} را حذف کنید؟`, | ||||
| @@ -127,8 +133,6 @@ export default { | |||||
| } | } | ||||
| } | } | ||||
| watch(page, () => { | watch(page, () => { | ||||
| getBlogs(); | getBlogs(); | ||||
| }); | }); | ||||
| @@ -175,7 +179,7 @@ export default { | |||||
| <div class="col-md-12"> | <div class="col-md-12"> | ||||
| <div class="card shadow-sm border-0 rounded"> | <div class="card shadow-sm border-0 rounded"> | ||||
| <div | <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" | dir="rtl" | ||||
| > | > | ||||
| <div class="d-flex align-items-center"> | <div class="d-flex align-items-center"> | ||||
| @@ -186,14 +190,13 @@ export default { | |||||
| class="form-control form-control-sm d-inline-block me-2" | class="form-control form-control-sm d-inline-block me-2" | ||||
| style="width: 250px; border-radius: 15px" | style="width: 250px; border-radius: 15px" | ||||
| /> | /> | ||||
| </div> | </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> | ||||
| <div v-if="!filterLoading" class="card-body table-border-style p-0"> | <div v-if="!filterLoading" class="card-body table-border-style p-0"> | ||||
| <div class="table-responsive"> | <div class="table-responsive"> | ||||
| @@ -247,77 +250,84 @@ export default { | |||||
| </div> | </div> | ||||
| </BRow> | </BRow> | ||||
| <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> | </BRow> | ||||
| </Layout> | </Layout> | ||||
| </template> | </template> | ||||
| @@ -175,6 +175,7 @@ export default { | |||||
| VueSelect, | VueSelect, | ||||
| }, | }, | ||||
| setup() { | setup() { | ||||
| const quillInstance = ref(null); | |||||
| const blog = ref(); | const blog = ref(); | ||||
| const route = useRoute(); | const route = useRoute(); | ||||
| const loading = ref(false); | const loading = ref(false); | ||||
| @@ -270,24 +271,29 @@ export default { | |||||
| blogCat.value = blog.value.blog_category_id; | blogCat.value = blog.value.blog_category_id; | ||||
| author.value = blog.value.author; | author.value = blog.value.author; | ||||
| if (editor.value) { | 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) => { | .catch((err) => { | ||||
| console.log(err); | console.log(err); | ||||
| @@ -37,7 +37,6 @@ export default { | |||||
| .format("YYYY/MM/DD"); | .format("YYYY/MM/DD"); | ||||
| }; | }; | ||||
| const handleSearchChange = () => { | const handleSearchChange = () => { | ||||
| clearTimeout(searchTimeout); | clearTimeout(searchTimeout); | ||||
| @@ -50,7 +49,7 @@ export default { | |||||
| watch(searchQuery, () => { | watch(searchQuery, () => { | ||||
| handleSearchChange(); | handleSearchChange(); | ||||
| }); | }); | ||||
| const getBrands = () => { | const getBrands = () => { | ||||
| filterLoading.value = true; | filterLoading.value = true; | ||||
| ApiServiece.get( | ApiServiece.get( | ||||
| @@ -153,8 +152,15 @@ export default { | |||||
| let start = currentPage.value - 2; | let start = currentPage.value - 2; | ||||
| let end = 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++) { | for (let i = start; i <= end; i++) { | ||||
| pages.push(i); | pages.push(i); | ||||
| @@ -163,8 +169,6 @@ export default { | |||||
| return pages; | return pages; | ||||
| }); | }); | ||||
| onMounted(() => { | onMounted(() => { | ||||
| getBrands(); | getBrands(); | ||||
| }); | }); | ||||
| @@ -211,15 +215,14 @@ export default { | |||||
| class="form-control form-control-sm d-inline-block me-2" | class="form-control form-control-sm d-inline-block me-2" | ||||
| style="width: 250px; border-radius: 15px" | style="width: 250px; border-radius: 15px" | ||||
| /> | /> | ||||
| </div> | </div> | ||||
| <button | <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> | ||||
| <div v-if="!filterLoading" class="card-body table-border-style p-0"> | <div v-if="!filterLoading" class="card-body table-border-style p-0"> | ||||
| <div class="table-responsive"> | <div class="table-responsive"> | ||||
| @@ -309,17 +312,24 @@ export default { | |||||
| <div class="d-flex justify-content-center"> | <div class="d-flex justify-content-center"> | ||||
| <nav aria-label="Page navigation"> | <nav aria-label="Page navigation"> | ||||
| <ul class="pagination"> | <ul class="pagination"> | ||||
| <!-- Previous page --> | |||||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | <li class="page-item" :class="{ disabled: currentPage === 1 }"> | ||||
| <span class="page-link" @click="prevPage">قبلی</span> | <span class="page-link" @click="prevPage">قبلی</span> | ||||
| </li> | </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> | <a class="page-link" href="javascript:void(0)">1</a> | ||||
| </li> | </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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <!-- Visible pages --> | |||||
| <li | <li | ||||
| v-for="n in visiblePages" | v-for="n in visiblePages" | ||||
| :key="n" | :key="n" | ||||
| @@ -335,23 +345,24 @@ export default { | |||||
| </a> | </a> | ||||
| </li> | </li> | ||||
| <!-- Trailing dots and last page --> | |||||
| <li | <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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <li | <li | ||||
| v-if="currentPage < totalPages - 1" | |||||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||||
| class="page-item" | class="page-item" | ||||
| @click="page = totalPages" | @click="page = totalPages" | ||||
| > | > | ||||
| <a class="page-link" href="javascript:void(0)">{{ | |||||
| totalPages | |||||
| }}</a> | |||||
| <a class="page-link" href="javascript:void(0)"> | |||||
| {{ totalPages }} | |||||
| </a> | |||||
| </li> | </li> | ||||
| <!-- Next page --> | |||||
| <li | <li | ||||
| class="page-item" | class="page-item" | ||||
| :class="{ disabled: currentPage === totalPages }" | :class="{ disabled: currentPage === totalPages }" | ||||
| @@ -61,8 +61,15 @@ export default { | |||||
| let start = currentPage.value - 2; | let start = currentPage.value - 2; | ||||
| let end = 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++) { | for (let i = start; i <= end; i++) { | ||||
| pages.push(i); | pages.push(i); | ||||
| @@ -259,10 +266,7 @@ export default { | |||||
| ></i> | ></i> | ||||
| </td> | </td> | ||||
| <td>{{ call?.subject }}</td> | <td>{{ call?.subject }}</td> | ||||
| <td | |||||
| class="text-center" | |||||
| style=" background: transparent" | |||||
| > | |||||
| <td class="text-center" style="background: transparent"> | |||||
| {{ call.text }} | {{ call.text }} | ||||
| </td> | </td> | ||||
| <td> | <td> | ||||
| @@ -342,15 +346,19 @@ export default { | |||||
| <span class="page-link" @click="prevPage">قبلی</span> | <span class="page-link" @click="prevPage">قبلی</span> | ||||
| </li> | </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> | <a class="page-link" href="javascript:void(0)">1</a> | ||||
| </li> | </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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <!-- Page numbers --> | |||||
| <!-- Visible pages --> | |||||
| <li | <li | ||||
| v-for="n in visiblePages" | v-for="n in visiblePages" | ||||
| :key="n" | :key="n" | ||||
| @@ -366,21 +374,21 @@ export default { | |||||
| </a> | </a> | ||||
| </li> | </li> | ||||
| <!-- Trailing dots and last page --> | |||||
| <li | <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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <li | <li | ||||
| v-if="currentPage < totalPages - 1" | |||||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||||
| class="page-item" | class="page-item" | ||||
| @click="page = totalPages" | @click="page = totalPages" | ||||
| > | > | ||||
| <a class="page-link" href="javascript:void(0)">{{ | |||||
| totalPages | |||||
| }}</a> | |||||
| <a class="page-link" href="javascript:void(0)"> | |||||
| {{ totalPages }} | |||||
| </a> | |||||
| </li> | </li> | ||||
| <!-- Next page --> | <!-- Next page --> | ||||
| @@ -69,8 +69,15 @@ export default { | |||||
| let start = currentPage.value - 2; | let start = currentPage.value - 2; | ||||
| let end = 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++) { | for (let i = start; i <= end; i++) { | ||||
| pages.push(i); | pages.push(i); | ||||
| @@ -91,6 +98,10 @@ export default { | |||||
| } | } | ||||
| } | } | ||||
| watch(page, () => { | |||||
| getCats(); | |||||
| }); | |||||
| const nextPage = () => { | const nextPage = () => { | ||||
| if (currentPage.value < totalPages.value) { | if (currentPage.value < totalPages.value) { | ||||
| page.value++; | page.value++; | ||||
| @@ -356,17 +367,24 @@ export default { | |||||
| <div class="d-flex justify-content-center"> | <div class="d-flex justify-content-center"> | ||||
| <nav aria-label="Page navigation"> | <nav aria-label="Page navigation"> | ||||
| <ul class="pagination"> | <ul class="pagination"> | ||||
| <!-- Previous page --> | |||||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | <li class="page-item" :class="{ disabled: currentPage === 1 }"> | ||||
| <span class="page-link" @click="prevPage">قبلی</span> | <span class="page-link" @click="prevPage">قبلی</span> | ||||
| </li> | </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> | <a class="page-link" href="javascript:void(0)">1</a> | ||||
| </li> | </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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <!-- Visible pages --> | |||||
| <li | <li | ||||
| v-for="n in visiblePages" | v-for="n in visiblePages" | ||||
| :key="n" | :key="n" | ||||
| @@ -382,23 +400,24 @@ export default { | |||||
| </a> | </a> | ||||
| </li> | </li> | ||||
| <!-- Trailing dots and last page --> | |||||
| <li | <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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <li | <li | ||||
| v-if="currentPage < totalPages - 1" | |||||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||||
| class="page-item" | class="page-item" | ||||
| @click="page = totalPages" | @click="page = totalPages" | ||||
| > | > | ||||
| <a class="page-link" href="javascript:void(0)">{{ | |||||
| totalPages | |||||
| }}</a> | |||||
| <a class="page-link" href="javascript:void(0)"> | |||||
| {{ totalPages }} | |||||
| </a> | |||||
| </li> | </li> | ||||
| <!-- Next page --> | |||||
| <li | <li | ||||
| class="page-item" | class="page-item" | ||||
| :class="{ disabled: currentPage === totalPages }" | :class="{ disabled: currentPage === totalPages }" | ||||
| @@ -127,8 +127,15 @@ export default { | |||||
| let start = currentPage.value - 2; | let start = currentPage.value - 2; | ||||
| let end = 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++) { | for (let i = start; i <= end; i++) { | ||||
| pages.push(i); | pages.push(i); | ||||
| @@ -289,7 +296,7 @@ export default { | |||||
| handleProductsSearch, | handleProductsSearch, | ||||
| selectedProduct, | selectedProduct, | ||||
| productSeletorLoader, | productSeletorLoader, | ||||
| blogSelectorLoader | |||||
| blogSelectorLoader, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -498,15 +505,24 @@ export default { | |||||
| <div class="d-flex justify-content-center"> | <div class="d-flex justify-content-center"> | ||||
| <nav aria-label="Page navigation"> | <nav aria-label="Page navigation"> | ||||
| <ul class="pagination"> | <ul class="pagination"> | ||||
| <!-- Previous page --> | |||||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | <li class="page-item" :class="{ disabled: currentPage === 1 }"> | ||||
| <span class="page-link" @click="prevPage">قبلی</span> | <span class="page-link" @click="prevPage">قبلی</span> | ||||
| </li> | </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> | <a class="page-link" href="javascript:void(0)">1</a> | ||||
| </li> | </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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <!-- Visible pages --> | |||||
| <li | <li | ||||
| v-for="n in visiblePages" | v-for="n in visiblePages" | ||||
| :key="n" | :key="n" | ||||
| @@ -522,23 +538,24 @@ export default { | |||||
| </a> | </a> | ||||
| </li> | </li> | ||||
| <!-- Trailing dots and last page --> | |||||
| <li | <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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <li | <li | ||||
| v-if="currentPage < totalPages - 1" | |||||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||||
| class="page-item" | class="page-item" | ||||
| @click="page = totalPages" | @click="page = totalPages" | ||||
| > | > | ||||
| <a class="page-link" href="javascript:void(0)">{{ | |||||
| totalPages | |||||
| }}</a> | |||||
| <a class="page-link" href="javascript:void(0)"> | |||||
| {{ totalPages }} | |||||
| </a> | |||||
| </li> | </li> | ||||
| <!-- Next page --> | |||||
| <li | <li | ||||
| class="page-item" | class="page-item" | ||||
| :class="{ disabled: currentPage === totalPages }" | :class="{ disabled: currentPage === totalPages }" | ||||
| @@ -308,8 +308,7 @@ export default { | |||||
| errors.value.selectedCat = "انتخاب دسته برای تخفیف الزامی می باشد"; | errors.value.selectedCat = "انتخاب دسته برای تخفیف الزامی می باشد"; | ||||
| if (!selectedProduct.value && whichPart.value === "product") | if (!selectedProduct.value && whichPart.value === "product") | ||||
| errors.value.selectedProduct = "انتخاب محصول برای تخفیف الزامی می باشد"; | errors.value.selectedProduct = "انتخاب محصول برای تخفیف الزامی می باشد"; | ||||
| if (!startDate.value) | |||||
| errors.value.startDate = "انتخاب تاریخ اعمال تخفیف الزامی می باشد "; | |||||
| if (!whichPart.value) | if (!whichPart.value) | ||||
| errors.value.whichPart = "مشخص کنید تخفیف بر چه بخشی اعمال شود"; | errors.value.whichPart = "مشخص کنید تخفیف بر چه بخشی اعمال شود"; | ||||
| @@ -86,8 +86,15 @@ export default { | |||||
| let start = currentPage.value - 2; | let start = currentPage.value - 2; | ||||
| let end = 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++) { | for (let i = start; i <= end; i++) { | ||||
| pages.push(i); | pages.push(i); | ||||
| @@ -387,17 +394,24 @@ export default { | |||||
| <div class="d-flex justify-content-center"> | <div class="d-flex justify-content-center"> | ||||
| <nav aria-label="Page navigation"> | <nav aria-label="Page navigation"> | ||||
| <ul class="pagination"> | <ul class="pagination"> | ||||
| <!-- Previous page --> | |||||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | <li class="page-item" :class="{ disabled: currentPage === 1 }"> | ||||
| <span class="page-link" @click="prevPage">قبلی</span> | <span class="page-link" @click="prevPage">قبلی</span> | ||||
| </li> | </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> | <a class="page-link" href="javascript:void(0)">1</a> | ||||
| </li> | </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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <!-- Visible pages --> | |||||
| <li | <li | ||||
| v-for="n in visiblePages" | v-for="n in visiblePages" | ||||
| :key="n" | :key="n" | ||||
| @@ -413,23 +427,24 @@ export default { | |||||
| </a> | </a> | ||||
| </li> | </li> | ||||
| <!-- Trailing dots and last page --> | |||||
| <li | <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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <li | <li | ||||
| v-if="currentPage < totalPages - 1" | |||||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||||
| class="page-item" | class="page-item" | ||||
| @click="page = totalPages" | @click="page = totalPages" | ||||
| > | > | ||||
| <a class="page-link" href="javascript:void(0)">{{ | |||||
| totalPages | |||||
| }}</a> | |||||
| <a class="page-link" href="javascript:void(0)"> | |||||
| {{ totalPages }} | |||||
| </a> | |||||
| </li> | </li> | ||||
| <!-- Next page --> | |||||
| <li | <li | ||||
| class="page-item" | class="page-item" | ||||
| :class="{ disabled: currentPage === totalPages }" | :class="{ disabled: currentPage === totalPages }" | ||||
| @@ -69,8 +69,15 @@ export default { | |||||
| let start = currentPage.value - 2; | let start = currentPage.value - 2; | ||||
| let end = 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++) { | for (let i = start; i <= end; i++) { | ||||
| pages.push(i); | pages.push(i); | ||||
| @@ -276,15 +283,24 @@ export default { | |||||
| <div class="d-flex justify-content-center"> | <div class="d-flex justify-content-center"> | ||||
| <nav aria-label="Page navigation"> | <nav aria-label="Page navigation"> | ||||
| <ul class="pagination"> | <ul class="pagination"> | ||||
| <!-- Previous page --> | |||||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | <li class="page-item" :class="{ disabled: currentPage === 1 }"> | ||||
| <span class="page-link" @click="prevPage">قبلی</span> | <span class="page-link" @click="prevPage">قبلی</span> | ||||
| </li> | </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> | <a class="page-link" href="javascript:void(0)">1</a> | ||||
| </li> | </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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <!-- Visible pages --> | |||||
| <li | <li | ||||
| v-for="n in visiblePages" | v-for="n in visiblePages" | ||||
| :key="n" | :key="n" | ||||
| @@ -300,23 +316,24 @@ export default { | |||||
| </a> | </a> | ||||
| </li> | </li> | ||||
| <!-- Trailing dots and last page --> | |||||
| <li | <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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <li | <li | ||||
| v-if="currentPage < totalPages - 1" | |||||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||||
| class="page-item" | class="page-item" | ||||
| @click="page = totalPages" | @click="page = totalPages" | ||||
| > | > | ||||
| <a class="page-link" href="javascript:void(0)">{{ | |||||
| totalPages | |||||
| }}</a> | |||||
| <a class="page-link" href="javascript:void(0)"> | |||||
| {{ totalPages }} | |||||
| </a> | |||||
| </li> | </li> | ||||
| <!-- Next page --> | |||||
| <li | <li | ||||
| class="page-item" | class="page-item" | ||||
| :class="{ disabled: currentPage === totalPages }" | :class="{ disabled: currentPage === totalPages }" | ||||
| @@ -150,8 +150,15 @@ export default { | |||||
| let start = currentPage.value - 2; | let start = currentPage.value - 2; | ||||
| let end = 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++) { | for (let i = start; i <= end; i++) { | ||||
| pages.push(i); | pages.push(i); | ||||
| @@ -348,17 +355,24 @@ export default { | |||||
| <div class="d-flex justify-content-center"> | <div class="d-flex justify-content-center"> | ||||
| <nav aria-label="Page navigation"> | <nav aria-label="Page navigation"> | ||||
| <ul class="pagination"> | <ul class="pagination"> | ||||
| <!-- Previous page --> | |||||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | <li class="page-item" :class="{ disabled: currentPage === 1 }"> | ||||
| <span class="page-link" @click="prevPage">قبلی</span> | <span class="page-link" @click="prevPage">قبلی</span> | ||||
| </li> | </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> | <a class="page-link" href="javascript:void(0)">1</a> | ||||
| </li> | </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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <!-- Visible pages --> | |||||
| <li | <li | ||||
| v-for="n in visiblePages" | v-for="n in visiblePages" | ||||
| :key="n" | :key="n" | ||||
| @@ -374,23 +388,24 @@ export default { | |||||
| </a> | </a> | ||||
| </li> | </li> | ||||
| <!-- Trailing dots and last page --> | |||||
| <li | <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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <li | <li | ||||
| v-if="currentPage < totalPages - 1" | |||||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||||
| class="page-item" | class="page-item" | ||||
| @click="page = totalPages" | @click="page = totalPages" | ||||
| > | > | ||||
| <a class="page-link" href="javascript:void(0)">{{ | |||||
| totalPages | |||||
| }}</a> | |||||
| <a class="page-link" href="javascript:void(0)"> | |||||
| {{ totalPages }} | |||||
| </a> | |||||
| </li> | </li> | ||||
| <!-- Next page --> | |||||
| <li | <li | ||||
| class="page-item" | class="page-item" | ||||
| :class="{ disabled: currentPage === totalPages }" | :class="{ disabled: currentPage === totalPages }" | ||||
| @@ -4,15 +4,21 @@ import ApiServiece from "@/services/ApiService"; | |||||
| import { onMounted, ref, watch, computed } from "vue"; | import { onMounted, ref, watch, computed } from "vue"; | ||||
| import DatePicker from "vue3-persian-datetime-picker"; | import DatePicker from "vue3-persian-datetime-picker"; | ||||
| import VueSelect from "vue3-select-component"; | import VueSelect from "vue3-select-component"; | ||||
| import showDescription from "@/components/modals/commonModals/showDescription.vue"; | |||||
| import moment from "jalali-moment"; | import moment from "jalali-moment"; | ||||
| import { toast } from "vue3-toastify"; | |||||
| import "vue3-toastify/dist/index.css"; | |||||
| import Swal from "sweetalert2"; | |||||
| export default { | export default { | ||||
| name: "PRODUCT-LIST", | name: "PRODUCT-LIST", | ||||
| components: { | components: { | ||||
| Layout, | Layout, | ||||
| VueSelect, | VueSelect, | ||||
| DatePicker, | DatePicker, | ||||
| showDescription, | |||||
| }, | }, | ||||
| setup() { | setup() { | ||||
| const productDescription = ref(""); | |||||
| const selectorLoader = ref(false); | const selectorLoader = ref(false); | ||||
| const isLoading = ref(false); | const isLoading = ref(false); | ||||
| const date = ref([]); | const date = ref([]); | ||||
| @@ -24,8 +30,46 @@ export default { | |||||
| const page = ref(1); | const page = ref(1); | ||||
| const filterLoading = ref(false); | const filterLoading = ref(false); | ||||
| const selectedBrand = ref(); | 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]) { | if (date.value[0]) { | ||||
| date.value[0] = moment(date.value[0], "YYYY-MM-DD HH:mm:ss").format( | date.value[0] = moment(date.value[0], "YYYY-MM-DD HH:mm:ss").format( | ||||
| "YYYY/MM/DD HH:mm:ss" | "YYYY/MM/DD HH:mm:ss" | ||||
| @@ -48,7 +92,7 @@ export default { | |||||
| ) | ) | ||||
| .then((resp) => { | .then((resp) => { | ||||
| filterLoading.value = false; | filterLoading.value = false; | ||||
| allProducts.value = resp.data.data.data; | |||||
| orders.value = resp.data.data.data; | |||||
| currentPage.value = resp.data.data.current_page; | currentPage.value = resp.data.data.current_page; | ||||
| totalPages.value = resp.data.data.last_page; | totalPages.value = resp.data.data.last_page; | ||||
| @@ -64,6 +108,38 @@ export default { | |||||
| .format("jYYYY/jMM/jDD HH:mm:ss"); | .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 = () => { | const getFile = () => { | ||||
| isLoading.value = true; | isLoading.value = true; | ||||
| ApiServiece.post( | ApiServiece.post( | ||||
| @@ -94,7 +170,7 @@ export default { | |||||
| }; | }; | ||||
| watch(selectedBrand, () => { | watch(selectedBrand, () => { | ||||
| getAllProducts(); | |||||
| getAllOrders(); | |||||
| page.value = 1; | page.value = 1; | ||||
| }); | }); | ||||
| @@ -108,8 +184,15 @@ export default { | |||||
| let start = currentPage.value - 2; | let start = currentPage.value - 2; | ||||
| let end = 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++) { | for (let i = start; i <= end; i++) { | ||||
| pages.push(i); | pages.push(i); | ||||
| @@ -131,28 +214,28 @@ export default { | |||||
| } | } | ||||
| watch(selectedBrand, () => { | watch(selectedBrand, () => { | ||||
| getAllProducts(); | |||||
| getAllOrders(); | |||||
| }); | }); | ||||
| watch(date, () => { | watch(date, () => { | ||||
| getAllProducts(); | |||||
| getAllOrders(); | |||||
| }); | }); | ||||
| watch(page, () => { | watch(page, () => { | ||||
| getAllProducts(); | |||||
| getAllOrders(); | |||||
| }); | }); | ||||
| const nextPage = () => { | const nextPage = () => { | ||||
| if (currentPage.value < totalPages.value) { | if (currentPage.value < totalPages.value) { | ||||
| page.value++; | page.value++; | ||||
| getAllProducts(); | |||||
| getAllOrders(); | |||||
| } | } | ||||
| }; | }; | ||||
| const prevPage = () => { | const prevPage = () => { | ||||
| if (currentPage.value > 1) { | if (currentPage.value > 1) { | ||||
| page.value--; | page.value--; | ||||
| getAllProducts(); | |||||
| getAllOrders(); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -185,10 +268,10 @@ export default { | |||||
| ); | ); | ||||
| onMounted(() => { | onMounted(() => { | ||||
| getAllProducts(); | |||||
| getAllOrders(); | |||||
| }); | }); | ||||
| return { | return { | ||||
| allProducts, | |||||
| orders, | |||||
| visiblePages, | visiblePages, | ||||
| nextPage, | nextPage, | ||||
| prevPage, | prevPage, | ||||
| @@ -208,6 +291,11 @@ export default { | |||||
| handleBrandSearch, | handleBrandSearch, | ||||
| filterLoading, | filterLoading, | ||||
| selectorLoader, | selectorLoader, | ||||
| productDescription, | |||||
| descriptionModal, | |||||
| getStatusLabel, | |||||
| getStatusClass, | |||||
| changeStatus, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -262,113 +350,249 @@ export default { | |||||
| </div> | </div> | ||||
| <div v-if="!filterLoading"> | <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> | <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> | </tr> | ||||
| </thead> | </thead> | ||||
| <tbody> | <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> | <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> | ||||
| <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> | ||||
| <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 | <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> | ></i> | ||||
| </td> | </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 | <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> | ></i> | ||||
| </td> | </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 | <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> | </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> | </tr> | ||||
| </tbody> | </tbody> | ||||
| </table> | </table> | ||||
| </div> | </div> | ||||
| <div | <div | ||||
| v-else | v-else | ||||
| class="filter-loader card table-card user-profile-list" | |||||
| class="filter-loader card table-card user-profile-list" | |||||
| ></div> | ></div> | ||||
| </BCardBody> | </BCardBody> | ||||
| </BCard> | </BCard> | ||||
| </BCol> | </BCol> | ||||
| </BRow> | </BRow> | ||||
| <showDescription :desc="productDescription" /> | |||||
| <BRow> | <BRow> | ||||
| <BCol sm="12"> | <BCol sm="12"> | ||||
| <div class="d-flex justify-content-center"> | <div class="d-flex justify-content-center"> | ||||
| <nav aria-label="Page navigation"> | <nav aria-label="Page navigation"> | ||||
| <ul class="pagination"> | <ul class="pagination"> | ||||
| <!-- Previous page --> | |||||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | <li class="page-item" :class="{ disabled: currentPage === 1 }"> | ||||
| <span class="page-link" @click="prevPage">قبلی</span> | <span class="page-link" @click="prevPage">قبلی</span> | ||||
| </li> | </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> | <a class="page-link" href="javascript:void(0)">1</a> | ||||
| </li> | </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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <!-- Visible pages --> | |||||
| <li | <li | ||||
| v-for="n in visiblePages" | v-for="n in visiblePages" | ||||
| :key="n" | :key="n" | ||||
| @@ -384,23 +608,24 @@ export default { | |||||
| </a> | </a> | ||||
| </li> | </li> | ||||
| <!-- Trailing dots and last page --> | |||||
| <li | <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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <li | <li | ||||
| v-if="currentPage < totalPages - 1" | |||||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||||
| class="page-item" | class="page-item" | ||||
| @click="page = totalPages" | @click="page = totalPages" | ||||
| > | > | ||||
| <a class="page-link" href="javascript:void(0)">{{ | |||||
| totalPages | |||||
| }}</a> | |||||
| <a class="page-link" href="javascript:void(0)"> | |||||
| {{ totalPages }} | |||||
| </a> | |||||
| </li> | </li> | ||||
| <!-- Next page --> | |||||
| <li | <li | ||||
| class="page-item" | class="page-item" | ||||
| :class="{ disabled: currentPage === totalPages }" | :class="{ disabled: currentPage === totalPages }" | ||||
| @@ -488,4 +713,75 @@ export default { | |||||
| .custom-datepicker { | .custom-datepicker { | ||||
| min-width: 340px; | 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> | </style> | ||||
| @@ -19,7 +19,6 @@ export default { | |||||
| const totalPages = ref(1); | const totalPages = ref(1); | ||||
| const paginate = ref(20); | const paginate = ref(20); | ||||
| const page = ref(1); | const page = ref(1); | ||||
| const filterLoading = ref(false); | const filterLoading = ref(false); | ||||
| const searchQuery = ref(""); | const searchQuery = ref(""); | ||||
| const orders = ref(); | const orders = ref(); | ||||
| @@ -29,6 +28,34 @@ export default { | |||||
| .locale("fa") | .locale("fa") | ||||
| .format("YYYY/MM/DD"); | .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, () => { | watch(searchQuery, () => { | ||||
| getOrders(); | getOrders(); | ||||
| page.value = 1; | page.value = 1; | ||||
| @@ -62,8 +89,15 @@ export default { | |||||
| let start = currentPage.value - 2; | let start = currentPage.value - 2; | ||||
| let end = 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++) { | for (let i = start; i <= end; i++) { | ||||
| pages.push(i); | pages.push(i); | ||||
| @@ -227,6 +261,8 @@ export default { | |||||
| getStatusClass, | getStatusClass, | ||||
| getStatusLabel, | getStatusLabel, | ||||
| selectedStatus, | selectedStatus, | ||||
| getExport, | |||||
| loadingId, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -272,7 +308,6 @@ export default { | |||||
| <table class="table table-hover table-bordered m-0" dir="rtl"> | <table class="table table-hover table-bordered m-0" dir="rtl"> | ||||
| <thead class="table-light"> | <thead class="table-light"> | ||||
| <tr> | <tr> | ||||
| <th>وضعیت</th> | <th>وضعیت</th> | ||||
| <th>قیمت</th> | <th>قیمت</th> | ||||
| <th>کاربر</th> | <th>کاربر</th> | ||||
| @@ -283,7 +318,6 @@ export default { | |||||
| </thead> | </thead> | ||||
| <tbody> | <tbody> | ||||
| <tr v-for="order in orders" :key="order.id"> | <tr v-for="order in orders" :key="order.id"> | ||||
| <td> | <td> | ||||
| <span class="badge" :class="getStatusClass(order.status)"> | <span class="badge" :class="getStatusClass(order.status)"> | ||||
| {{ getStatusLabel(order.status) }} | {{ getStatusLabel(order.status) }} | ||||
| @@ -313,6 +347,28 @@ export default { | |||||
| > | > | ||||
| ویرایش وضعیت | ویرایش وضعیت | ||||
| </button> | </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 | <ul | ||||
| class="dropdown-menu" | class="dropdown-menu" | ||||
| aria-labelledby="dropdownMenuButton" | aria-labelledby="dropdownMenuButton" | ||||
| @@ -417,15 +473,19 @@ export default { | |||||
| <span class="page-link" @click="prevPage">قبلی</span> | <span class="page-link" @click="prevPage">قبلی</span> | ||||
| </li> | </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> | <a class="page-link" href="javascript:void(0)">1</a> | ||||
| </li> | </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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <!-- Page numbers --> | |||||
| <!-- Visible pages --> | |||||
| <li | <li | ||||
| v-for="n in visiblePages" | v-for="n in visiblePages" | ||||
| :key="n" | :key="n" | ||||
| @@ -441,21 +501,21 @@ export default { | |||||
| </a> | </a> | ||||
| </li> | </li> | ||||
| <!-- Trailing dots and last page --> | |||||
| <li | <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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <li | <li | ||||
| v-if="currentPage < totalPages - 1" | |||||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||||
| class="page-item" | class="page-item" | ||||
| @click="page = totalPages" | @click="page = totalPages" | ||||
| > | > | ||||
| <a class="page-link" href="javascript:void(0)">{{ | |||||
| totalPages | |||||
| }}</a> | |||||
| <a class="page-link" href="javascript:void(0)"> | |||||
| {{ totalPages }} | |||||
| </a> | |||||
| </li> | </li> | ||||
| <!-- Next page --> | <!-- Next page --> | ||||
| @@ -48,8 +48,17 @@ export default { | |||||
| const getOrder = () => { | const getOrder = () => { | ||||
| ApiServiece.get(`admin/orders/${route.params.id}`) | ApiServiece.get(`admin/orders/${route.params.id}`) | ||||
| .then((resp) => { | .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) => { | .catch((error) => { | ||||
| console.error("Failed to fetch order details:", error); | console.error("Failed to fetch order details:", error); | ||||
| @@ -58,7 +67,7 @@ export default { | |||||
| const updateShippedCount = (item) => { | const updateShippedCount = (item) => { | ||||
| const formData = new FormData(); | 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) | ApiServiece.put(`admin/orders/order-items/${item.id}`, formData) | ||||
| .then(() => { | .then(() => { | ||||
| console.log("Shipped quantity updated successfully."); | console.log("Shipped quantity updated successfully."); | ||||
| @@ -93,9 +102,6 @@ export default { | |||||
| console.error("Failed to update status:", error); | console.error("Failed to update status:", error); | ||||
| }); | }); | ||||
| }; | }; | ||||
| function formatWithCommas(number) { | |||||
| return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); | |||||
| } | |||||
| onMounted(() => { | onMounted(() => { | ||||
| getOrder(); | getOrder(); | ||||
| @@ -109,7 +115,6 @@ export default { | |||||
| updateShippedCount, | updateShippedCount, | ||||
| updateNote, | updateNote, | ||||
| updateStatus, | updateStatus, | ||||
| formatWithCommas, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -126,43 +131,53 @@ export default { | |||||
| <h5 class="mb-0">جزئیات سفارش</h5> | <h5 class="mb-0">جزئیات سفارش</h5> | ||||
| </BCardHeader> | </BCardHeader> | ||||
| <BCardBody v-if="order"> | <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> | ||||
| <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> | ||||
| <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> | ||||
| <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> | ||||
| <BCol md="6" | |||||
| ><strong>رنگ سفارش:</strong> {{ order.color || "---" }}</BCol | |||||
| > | |||||
| </BRow> | </BRow> | ||||
| </div> | </div> | ||||
| @@ -187,7 +202,7 @@ export default { | |||||
| > | > | ||||
| <!-- Price formatting --> | <!-- Price formatting --> | ||||
| <template #cell(price)="data"> | <template #cell(price)="data"> | ||||
| {{ formatWithCommas(data.item.price) }} تومان | |||||
| {{ data.item.price }} تومان | |||||
| </template> | </template> | ||||
| <template #cell(title)="data"> | <template #cell(title)="data"> | ||||
| @@ -292,6 +292,7 @@ | |||||
| :reduce="(option) => option.value" | :reduce="(option) => option.value" | ||||
| :options="formattedCategories" | :options="formattedCategories" | ||||
| placeholder="دسته ای را انتخاب کنید" | placeholder="دسته ای را انتخاب کنید" | ||||
| @search="handleSearch" | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| <small v-if="errors.selectedCat" class="text-danger"> | <small v-if="errors.selectedCat" class="text-danger"> | ||||
| @@ -327,7 +328,6 @@ | |||||
| md="6" | md="6" | ||||
| lg="4" | lg="4" | ||||
| > | > | ||||
| <div class="card shadow-sm"> | <div class="card shadow-sm"> | ||||
| <div class="card-body"> | <div class="card-body"> | ||||
| <!-- Card Header with Delete Icon --> | <!-- Card Header with Delete Icon --> | ||||
| @@ -394,7 +394,11 @@ | |||||
| <button | <button | ||||
| class="btn btn-primary" | class="btn btn-primary" | ||||
| @click=" | @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(() => | const formattedBrands = computed(() => | ||||
| Array.isArray(brands.value) | Array.isArray(brands.value) | ||||
| ? brands.value.map((brand) => ({ | ? 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, () => { | watch(selectedCat, () => { | ||||
| ApiServiece.get(`admin/attributes?category_id=${selectedCat.value}`) | ApiServiece.get(`admin/attributes?category_id=${selectedCat.value}`) | ||||
| .then((resp) => { | .then((resp) => { | ||||
| @@ -920,7 +954,7 @@ export default { | |||||
| ApiServiece.get(`admin/attributes?category_id=${selectedCat.value}`) | ApiServiece.get(`admin/attributes?category_id=${selectedCat.value}`) | ||||
| .then((resp) => { | .then((resp) => { | ||||
| identities.value = resp.data.data; | identities.value = resp.data.data; | ||||
| console.log(identities.value , "identities"); | |||||
| console.log(identities.value, "identities"); | |||||
| }) | }) | ||||
| .then(() => { | .then(() => { | ||||
| localIdentitiesIds.value = localIdentities.value.map( | localIdentitiesIds.value = localIdentities.value.map( | ||||
| @@ -1303,7 +1337,7 @@ export default { | |||||
| }); | }); | ||||
| }; | }; | ||||
| const editAttribute = (id, inventory , attrebuteValueId) => { | |||||
| const editAttribute = (id, inventory, attrebuteValueId) => { | |||||
| Swal.fire({ | Swal.fire({ | ||||
| text: "آیا برای ویرایش ویژگی اطمینان دارید؟", | text: "آیا برای ویرایش ویژگی اطمینان دارید؟", | ||||
| icon: "warning", | icon: "warning", | ||||
| @@ -1616,6 +1650,8 @@ export default { | |||||
| formattedCategories, | formattedCategories, | ||||
| formattedBrands, | formattedBrands, | ||||
| brandSelectorLoader, | brandSelectorLoader, | ||||
| handleSearch, | |||||
| handleBrandSearch, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -96,8 +96,15 @@ export default { | |||||
| let start = currentPage.value - 2; | let start = currentPage.value - 2; | ||||
| let end = 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++) { | for (let i = start; i <= end; i++) { | ||||
| pages.push(i); | pages.push(i); | ||||
| @@ -105,7 +112,6 @@ export default { | |||||
| } | } | ||||
| return pages; | return pages; | ||||
| }); | }); | ||||
| const deleteProduct = (id, title) => { | const deleteProduct = (id, title) => { | ||||
| Swal.fire({ | Swal.fire({ | ||||
| text: `می خواهید محصول ${title} را حذف کنید؟`, | text: `می خواهید محصول ${title} را حذف کنید؟`, | ||||
| @@ -473,17 +479,24 @@ export default { | |||||
| <div class="d-flex justify-content-center"> | <div class="d-flex justify-content-center"> | ||||
| <nav aria-label="Page navigation"> | <nav aria-label="Page navigation"> | ||||
| <ul class="pagination"> | <ul class="pagination"> | ||||
| <!-- Previous page --> | |||||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | <li class="page-item" :class="{ disabled: currentPage === 1 }"> | ||||
| <span class="page-link" @click="prevPage">قبلی</span> | <span class="page-link" @click="prevPage">قبلی</span> | ||||
| </li> | </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> | <a class="page-link" href="javascript:void(0)">1</a> | ||||
| </li> | </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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <!-- Visible pages --> | |||||
| <li | <li | ||||
| v-for="n in visiblePages" | v-for="n in visiblePages" | ||||
| :key="n" | :key="n" | ||||
| @@ -499,23 +512,24 @@ export default { | |||||
| </a> | </a> | ||||
| </li> | </li> | ||||
| <!-- Trailing dots and last page --> | |||||
| <li | <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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <li | <li | ||||
| v-if="currentPage < totalPages - 1" | |||||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||||
| class="page-item" | class="page-item" | ||||
| @click="page = totalPages" | @click="page = totalPages" | ||||
| > | > | ||||
| <a class="page-link" href="javascript:void(0)">{{ | |||||
| totalPages | |||||
| }}</a> | |||||
| <a class="page-link" href="javascript:void(0)"> | |||||
| {{ totalPages }} | |||||
| </a> | |||||
| </li> | </li> | ||||
| <!-- Next page --> | |||||
| <li | <li | ||||
| class="page-item" | class="page-item" | ||||
| :class="{ disabled: currentPage === totalPages }" | :class="{ disabled: currentPage === totalPages }" | ||||
| @@ -9,6 +9,7 @@ import Quill from "quill"; | |||||
| import "quill/dist/quill.snow.css"; | import "quill/dist/quill.snow.css"; | ||||
| import ApiService from "@/services/ApiService"; | import ApiService from "@/services/ApiService"; | ||||
| import DatePicker from "vue3-persian-datetime-picker"; | import DatePicker from "vue3-persian-datetime-picker"; | ||||
| const isFirstLoad = ref(true); | |||||
| const settings = ref([]); | const settings = ref([]); | ||||
| const editors = ref({}); | const editors = ref({}); | ||||
| @@ -18,17 +19,20 @@ const getSettings = async () => { | |||||
| try { | try { | ||||
| const response = await ApiService.get("admin/settings"); | const response = await ApiService.get("admin/settings"); | ||||
| settings.value = response.data.data; | settings.value = response.data.data; | ||||
| settings.value.forEach((setting) => { | settings.value.forEach((setting) => { | ||||
| if (setting.type === "image" && setting.value) { | if (setting.type === "image" && setting.value) { | ||||
| setting.preview = setting.value; | setting.preview = setting.value; | ||||
| localStorage.setItem("logo" , setting.value) | |||||
| localStorage.setItem("logo", setting.value); | |||||
| } | } | ||||
| loading.value[setting.id] = false; | loading.value[setting.id] = false; | ||||
| }); | }); | ||||
| await nextTick(); | await nextTick(); | ||||
| initQuillEditors(); | |||||
| if (isFirstLoad.value) { | |||||
| initQuillEditors(); | |||||
| isFirstLoad.value = false; | |||||
| } | |||||
| } catch (error) { | } catch (error) { | ||||
| console.error("Error fetching settings:", error); | console.error("Error fetching settings:", error); | ||||
| } | } | ||||
| @@ -36,7 +40,7 @@ const getSettings = async () => { | |||||
| const initQuillEditors = () => { | const initQuillEditors = () => { | ||||
| settings.value.forEach((setting) => { | settings.value.forEach((setting) => { | ||||
| if (setting.type === "longtext") { | |||||
| if (setting.type === "longtext" && !editors.value[setting.id]) { | |||||
| const quillContainer = document.getElementById(`editor-${setting.id}`); | const quillContainer = document.getElementById(`editor-${setting.id}`); | ||||
| if (quillContainer) { | if (quillContainer) { | ||||
| const quill = new Quill(quillContainer, { | const quill = new Quill(quillContainer, { | ||||
| @@ -47,7 +51,7 @@ const initQuillEditors = () => { | |||||
| [{ list: "ordered" }, { list: "bullet" }], | [{ list: "ordered" }, { list: "bullet" }], | ||||
| [{ align: [] }], | [{ align: [] }], | ||||
| ["bold", "italic", "underline"], | ["bold", "italic", "underline"], | ||||
| ["link", "image"], | |||||
| ["link"], // Removed "image" from here | |||||
| [{ script: "sub" }, { script: "super" }], | [{ script: "sub" }, { script: "super" }], | ||||
| [{ direction: "rtl" }], | [{ direction: "rtl" }], | ||||
| ], | ], | ||||
| @@ -43,7 +43,7 @@ export default { | |||||
| `admin/users?name=${searchQuery.value || ""}&paginate=${ | `admin/users?name=${searchQuery.value || ""}&paginate=${ | ||||
| paginate.value || 10 | paginate.value || 10 | ||||
| }&page=${page.value || 1}&role=${ | }&page=${page.value || 1}&role=${ | ||||
| selectedRole.value || "admin" | |||||
| selectedRole.value || "" | |||||
| }&trashed=${selectedStatus.value || ""}` | }&trashed=${selectedStatus.value || ""}` | ||||
| ) | ) | ||||
| .then((resp) => { | .then((resp) => { | ||||
| @@ -82,8 +82,15 @@ export default { | |||||
| let start = currentPage.value - 2; | let start = currentPage.value - 2; | ||||
| let end = 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++) { | for (let i = start; i <= end; i++) { | ||||
| pages.push(i); | pages.push(i); | ||||
| @@ -402,17 +409,24 @@ export default { | |||||
| <div class="d-flex justify-content-center"> | <div class="d-flex justify-content-center"> | ||||
| <nav aria-label="Page navigation"> | <nav aria-label="Page navigation"> | ||||
| <ul class="pagination"> | <ul class="pagination"> | ||||
| <!-- Previous page --> | |||||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | <li class="page-item" :class="{ disabled: currentPage === 1 }"> | ||||
| <span class="page-link" @click="prevPage">قبلی</span> | <span class="page-link" @click="prevPage">قبلی</span> | ||||
| </li> | </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> | <a class="page-link" href="javascript:void(0)">1</a> | ||||
| </li> | </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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <!-- Visible pages --> | |||||
| <li | <li | ||||
| v-for="n in visiblePages" | v-for="n in visiblePages" | ||||
| :key="n" | :key="n" | ||||
| @@ -428,23 +442,24 @@ export default { | |||||
| </a> | </a> | ||||
| </li> | </li> | ||||
| <!-- Trailing dots and last page --> | |||||
| <li | <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> | <span class="page-link">...</span> | ||||
| </li> | </li> | ||||
| <li | <li | ||||
| v-if="currentPage < totalPages - 1" | |||||
| v-if="visiblePages[visiblePages.length - 1] < totalPages" | |||||
| class="page-item" | class="page-item" | ||||
| @click="page = totalPages" | @click="page = totalPages" | ||||
| > | > | ||||
| <a class="page-link" href="javascript:void(0)">{{ | |||||
| totalPages | |||||
| }}</a> | |||||
| <a class="page-link" href="javascript:void(0)"> | |||||
| {{ totalPages }} | |||||
| </a> | |||||
| </li> | </li> | ||||
| <!-- Next page --> | |||||
| <li | <li | ||||
| class="page-item" | class="page-item" | ||||
| :class="{ disabled: currentPage === totalPages }" | :class="{ disabled: currentPage === totalPages }" | ||||