| @@ -6,6 +6,8 @@ | |||||
| --bs-body-bg: #{$dark-layout-color}; | --bs-body-bg: #{$dark-layout-color}; | ||||
| --bs-body-bg-rgb: #{to-rgb($dark-layout-color)}; | --bs-body-bg-rgb: #{to-rgb($dark-layout-color)}; | ||||
| --pc-heading-color: rgba(255, 255, 255, 0.8); | --pc-heading-color: rgba(255, 255, 255, 0.8); | ||||
| // Navbar | // Navbar | ||||
| --pc-sidebar-background: #1D2630; | --pc-sidebar-background: #1D2630; | ||||
| @@ -13,6 +15,7 @@ | |||||
| --pc-sidebar-color-rgb: #{to-rgb(#FFFFFF)}; | --pc-sidebar-color-rgb: #{to-rgb(#FFFFFF)}; | ||||
| --pc-sidebar-submenu-border-color: var(--bs-gray-600); | --pc-sidebar-submenu-border-color: var(--bs-gray-600); | ||||
| --pc-sidebar-caption-color: #748892; | --pc-sidebar-caption-color: #748892; | ||||
| --vs-text-color: #18181b; | |||||
| // header | // header | ||||
| --pc-header-background: rgba(#{var(--bs-body-bg-rgb)}, 0.7); | --pc-header-background: rgba(#{var(--bs-body-bg-rgb)}, 0.7); | ||||
| @@ -405,7 +405,7 @@ export default { | |||||
| </li> | </li> | ||||
| <!-- دسته ها --> | <!-- دسته ها --> | ||||
| <li class="pc-item pc-hasmenu"> | |||||
| <!-- <li class="pc-item pc-hasmenu"> | |||||
| <BLink | <BLink | ||||
| class="pc-link" | class="pc-link" | ||||
| data-bs-toggle="collapse" | data-bs-toggle="collapse" | ||||
| @@ -442,7 +442,7 @@ export default { | |||||
| </li> | </li> | ||||
| </ul> | </ul> | ||||
| </div> | </div> | ||||
| </li> | |||||
| </li> --> | |||||
| <li | <li | ||||
| class="pc-item" | class="pc-item" | ||||
| @@ -219,22 +219,24 @@ export default { | |||||
| console.log(resp); | console.log(resp); | ||||
| toast.success("!برند با موفقیت اضافه شد", { | toast.success("!برند با موفقیت اضافه شد", { | ||||
| position: "top-right", | position: "top-right", | ||||
| autoClose: 1000 | |||||
| autoClose: 1000, | |||||
| }); | }); | ||||
| }) | }) | ||||
| .then(()=>{ | |||||
| .then(() => { | |||||
| setTimeout(() => { | setTimeout(() => { | ||||
| document.getElementById("close").click(); | |||||
| emit("brand-updated"); | |||||
| }, 500); | |||||
| document.getElementById("close").click(); | |||||
| emit("brand-updated"); | |||||
| title.value = ""; | |||||
| description.value = ""; | |||||
| image.value = null; | |||||
| imagePreview.value = null; | |||||
| }, 500); | |||||
| }) | }) | ||||
| .catch((error) => { | .catch((error) => { | ||||
| console.error(error); | console.error(error); | ||||
| toast.error("!مشکلی در ایجاد برند پیش آمد", { | toast.error("!مشکلی در ایجاد برند پیش آمد", { | ||||
| position: "top-right", | position: "top-right", | ||||
| autoClose: 1000 | |||||
| autoClose: 1000, | |||||
| }); | }); | ||||
| }) | }) | ||||
| .finally(() => { | .finally(() => { | ||||
| @@ -202,6 +202,11 @@ export default { | |||||
| position: "top-right", | position: "top-right", | ||||
| autoClose: 1000, | autoClose: 1000, | ||||
| }); | }); | ||||
| mobile.value = ""; | |||||
| name.value = ""; | |||||
| role.value = ""; | |||||
| password.value = ""; | |||||
| repeatPassword.value = ""; | |||||
| }) | }) | ||||
| .then(() => { | .then(() => { | ||||
| setTimeout(() => { | setTimeout(() => { | ||||
| @@ -165,6 +165,8 @@ export default { | |||||
| setTimeout(() => { | setTimeout(() => { | ||||
| document.getElementById("close").click(); | document.getElementById("close").click(); | ||||
| emit("attribute-updated"); | emit("attribute-updated"); | ||||
| colorName.value = ""; | |||||
| colorCode.value = ""; | |||||
| }, 500); | }, 500); | ||||
| }) | }) | ||||
| .catch((error) => { | .catch((error) => { | ||||
| @@ -160,6 +160,8 @@ export default { | |||||
| setTimeout(() => { | setTimeout(() => { | ||||
| document.getElementById("closeAddBlogCat").click(); | document.getElementById("closeAddBlogCat").click(); | ||||
| emit("cat-updated"); | emit("cat-updated"); | ||||
| title.value = ""; | |||||
| selectedIcon.value = ""; | |||||
| }, 500); | }, 500); | ||||
| }) | }) | ||||
| .catch((error) => { | .catch((error) => { | ||||
| @@ -101,8 +101,11 @@ | |||||
| <BCol lg="6"> | <BCol lg="6"> | ||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label class="form-label">انتخاب پدر</label> | <label class="form-label">انتخاب پدر</label> | ||||
| <select v-model="selectedPaernt" class="form-control" placeholder="انتخاب کنید"> | |||||
| <select | |||||
| v-model="selectedPaernt" | |||||
| class="form-control" | |||||
| placeholder="انتخاب کنید" | |||||
| > | |||||
| <option | <option | ||||
| v-for="parent in localParents" | v-for="parent in localParents" | ||||
| :key="parent.id" | :key="parent.id" | ||||
| @@ -301,6 +304,11 @@ export default { | |||||
| setTimeout(() => { | setTimeout(() => { | ||||
| document.getElementById("close").click(); | document.getElementById("close").click(); | ||||
| emit("cat-updated"); | emit("cat-updated"); | ||||
| title.value = ""; | |||||
| imagePreview.value = ""; | |||||
| image.value = null; | |||||
| description.value = ""; | |||||
| selectedIcon.value = ""; | |||||
| }, 500); | }, 500); | ||||
| }) | }) | ||||
| .catch((error) => { | .catch((error) => { | ||||
| @@ -214,6 +214,7 @@ export default { | |||||
| autoClose: 1000, | autoClose: 1000, | ||||
| onClose: () => emit("user-updated"), | onClose: () => emit("user-updated"), | ||||
| }); | }); | ||||
| }) | }) | ||||
| .then(() => { | .then(() => { | ||||
| setTimeout(() => { | setTimeout(() => { | ||||
| @@ -139,51 +139,52 @@ export default { | |||||
| }; | }; | ||||
| const addIdentity = () => { | const addIdentity = () => { | ||||
| if (!validateForm()) { | |||||
| toast.error("لطفا فیلد های لازم را وارد نمایید", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | |||||
| return; | |||||
| } | |||||
| loading.value = true; | |||||
| const formData = new FormData(); | |||||
| if (selectedCat.value) { | |||||
| formData.append("category_id", selectedCat.value); | |||||
| } | |||||
| formData.append("title", title.value); | |||||
| ApiServiece.post(`admin/attributes`, formData) | |||||
| .then((resp) => { | |||||
| console.log(resp); | |||||
| toast.success("!مشخصه با موفقیت اضافه شد", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | |||||
| // **Reset form fields** | |||||
| title.value = ""; | |||||
| selectedCat.value = null; | |||||
| }) | |||||
| .then(() => { | |||||
| setTimeout(() => { | |||||
| document.getElementById("close").click(); | |||||
| emit("identity-updated"); | |||||
| }, 500); | |||||
| }) | |||||
| .catch((error) => { | |||||
| console.error(error); | |||||
| toast.error("!اضافه کردن مشخصه با مشکل مواجه شد", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | |||||
| }) | |||||
| .finally(() => { | |||||
| loading.value = false; | |||||
| }); | |||||
| }; | |||||
| if (!validateForm()) { | |||||
| toast.error("لطفا فیلد های لازم را وارد نمایید", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | |||||
| return; | |||||
| } | |||||
| loading.value = true; | |||||
| const formData = new FormData(); | |||||
| if (selectedCat.value) { | |||||
| formData.append("category_id", selectedCat.value); | |||||
| } | |||||
| formData.append("title", title.value); | |||||
| ApiServiece.post(`admin/attributes`, formData) | |||||
| .then((resp) => { | |||||
| console.log(resp); | |||||
| toast.success("!مشخصه با موفقیت اضافه شد", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | |||||
| // **Reset form fields** | |||||
| title.value = ""; | |||||
| selectedCat.value = null; | |||||
| }) | |||||
| .then(() => { | |||||
| setTimeout(() => { | |||||
| document.getElementById("close").click(); | |||||
| emit("identity-updated"); | |||||
| selectedCat.value = ""; | |||||
| title.value = ""; | |||||
| }, 500); | |||||
| }) | |||||
| .catch((error) => { | |||||
| console.error(error); | |||||
| toast.error("!اضافه کردن مشخصه با مشکل مواجه شد", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | |||||
| }) | |||||
| .finally(() => { | |||||
| loading.value = false; | |||||
| }); | |||||
| }; | |||||
| return { | return { | ||||
| errors, | errors, | ||||
| @@ -204,21 +204,6 @@ export default { | |||||
| console.log(resp); | console.log(resp); | ||||
| addrerssId.value = resp.data.data.id; | addrerssId.value = resp.data.data.id; | ||||
| }) | }) | ||||
| .then(() => { | |||||
| if (!localShowCard.value && localOrderId.value) { | |||||
| ApiServiece.post( | |||||
| `wholesale/orders/${localOrderId.value}/addresses`, | |||||
| { | |||||
| user_address_id: addrerssId.value, | |||||
| } | |||||
| ).then(() => { | |||||
| toast.success("!این آدرس به سفارش اضافه گردید", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | |||||
| }); | |||||
| } | |||||
| }) | |||||
| .catch(() => { | .catch(() => { | ||||
| loading.value = false; | loading.value = false; | ||||
| toast.error("!مشکلی در ایجاد آدرس بوجود آمد", { | toast.error("!مشکلی در ایجاد آدرس بوجود آمد", { | ||||
| @@ -28,13 +28,22 @@ export default { | |||||
| const attributeTitle = ref(); | const attributeTitle = ref(); | ||||
| const attributeId = ref(); | const attributeId = ref(); | ||||
| const attributeCode = ref(); | const attributeCode = ref(); | ||||
| let searchTimeout = null; | |||||
| const convertToJalali = (date) => { | const convertToJalali = (date) => { | ||||
| return moment(date, "YYYY-MM-DD HH:mm:ss") | return moment(date, "YYYY-MM-DD HH:mm:ss") | ||||
| .locale("fa") | .locale("fa") | ||||
| .format("YYYY/MM/DD"); | .format("YYYY/MM/DD"); | ||||
| }; | }; | ||||
| const handleSearchChange = () => { | |||||
| clearTimeout(searchTimeout); | |||||
| searchTimeout = setTimeout(() => { | |||||
| getAttributes(); | |||||
| page.value = 1; | |||||
| }, 500); | |||||
| }; | |||||
| watch(searchQuery, () => { | watch(searchQuery, () => { | ||||
| getAttributes(); | |||||
| handleSearchChange(); | |||||
| }); | }); | ||||
| const getAttributes = () => { | const getAttributes = () => { | ||||
| filterLoading.value = true; | filterLoading.value = true; | ||||
| @@ -47,7 +56,7 @@ export default { | |||||
| ) | ) | ||||
| .then((resp) => { | .then((resp) => { | ||||
| filterLoading.value = false; | filterLoading.value = false; | ||||
| console.log(resp.data) | |||||
| console.log(resp.data); | |||||
| attributes.value = resp.data.data.data; | attributes.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; | ||||
| @@ -150,13 +159,11 @@ export default { | |||||
| attributeCode.value = code; | attributeCode.value = code; | ||||
| }; | }; | ||||
| watch(searchQuery, () => { | |||||
| getAttributes(); | |||||
| }); | |||||
| const getAttributeValues = () => { | const getAttributeValues = () => { | ||||
| ApiServiece.get(`admin/attributes`).then((resp) => { | ApiServiece.get(`admin/attributes`).then((resp) => { | ||||
| console.log(resp) | |||||
| console.log(resp); | |||||
| attributeValues.value = resp.data.data; | attributeValues.value = resp.data.data; | ||||
| }); | }); | ||||
| }; | }; | ||||
| @@ -196,7 +203,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"> | ||||
| @@ -207,14 +214,15 @@ 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" | ||||
| /> | /> | ||||
| <button | |||||
| </div> | |||||
| <button | |||||
| data-bs-toggle="modal" | data-bs-toggle="modal" | ||||
| data-bs-target="#addAttribute" | data-bs-target="#addAttribute" | ||||
| class="btn btn-light text-primary btn-sm px-3" | class="btn btn-light text-primary btn-sm px-3" | ||||
| > | > | ||||
| افزودن رنگ | افزودن رنگ | ||||
| </button> | </button> | ||||
| </div> | |||||
| </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"> | ||||
| @@ -223,7 +231,7 @@ export default { | |||||
| <tr> | <tr> | ||||
| <th>نام</th> | <th>نام</th> | ||||
| <th>رنگ</th> | <th>رنگ</th> | ||||
| <td>کد رنگ</td> | |||||
| <th>کد رنگ</th> | |||||
| <th>تاریخ ایجاد</th> | <th>تاریخ ایجاد</th> | ||||
| <th>عملیات</th> | <th>عملیات</th> | ||||
| </tr> | </tr> | ||||
| @@ -20,7 +20,7 @@ export default { | |||||
| const store = useStore(); | const store = useStore(); | ||||
| const mobile = ref(""); | const mobile = ref(""); | ||||
| const otpSent = ref(false); | const otpSent = ref(false); | ||||
| const timer = ref(120); | |||||
| const timer = ref(60); | |||||
| const otpCode = ref(""); | const otpCode = ref(""); | ||||
| const resendAvailable = ref(false); | const resendAvailable = ref(false); | ||||
| let timerInterval = null; | let timerInterval = null; | ||||
| @@ -28,9 +28,8 @@ export default { | |||||
| const sendOtp = () => { | const sendOtp = () => { | ||||
| if (!validateSendOtpForm()) return; | if (!validateSendOtpForm()) return; | ||||
| sendOtpLoading.value = true; | sendOtpLoading.value = true; | ||||
| ApiServiece.post(`auth/send-otp`, { mobile: mobile.value }) | |||||
| .then((resp) => { | |||||
| console.log(resp.data); | |||||
| ApiServiece.post("auth/send-otp", { mobile: mobile.value }) | |||||
| .then(() => { | |||||
| otpSent.value = true; | otpSent.value = true; | ||||
| resendAvailable.value = false; | resendAvailable.value = false; | ||||
| sendOtpLoading.value = false; | sendOtpLoading.value = false; | ||||
| @@ -43,25 +42,14 @@ export default { | |||||
| startTimer(); | startTimer(); | ||||
| }) | }) | ||||
| .catch((err) => { | .catch((err) => { | ||||
| sendOtpLoading.value = false; | |||||
| console.log(err); | console.log(err); | ||||
| const status = err.status; | |||||
| if (status == 429) { | |||||
| Swal.fire({ | |||||
| icon: "error", | |||||
| title: "تعداد تلاشها زیاد شد", | |||||
| text: "لطفاً بعداً دوباره امتحان کنید", | |||||
| confirmButtonText: "باشه", | |||||
| }); | |||||
| } | |||||
| if (status == 400) { | |||||
| Swal.fire({ | |||||
| icon: "warning", | |||||
| title: "کد تایید قبلاً ارسال شده", | |||||
| text: "برای این شماره، کد ورود قبلاً ارسال شده است. لطفاً کمی صبر کنید تا کد قبلی منقضی شود", | |||||
| confirmButtonText: "باشه", | |||||
| }); | |||||
| } | |||||
| Swal.fire({ | |||||
| icon: "error", | |||||
| title: "خطا در ارسال کد", | |||||
| text: err.response.data.message, | |||||
| confirmButtonText: "متوجه شدم", | |||||
| }); | |||||
| sendOtpLoading.value = false; | |||||
| }); | }); | ||||
| }; | }; | ||||
| @@ -77,10 +65,11 @@ export default { | |||||
| }; | }; | ||||
| const resendOtp = () => { | const resendOtp = () => { | ||||
| otpSent.value = false; | |||||
| otpCode.value = ""; | otpCode.value = ""; | ||||
| timer.value = 120; | |||||
| timer.value = 60; | |||||
| resendAvailable.value = false; | resendAvailable.value = false; | ||||
| sendOtp(); // Resend OTP | |||||
| startTimer(); // Restart the countdown | |||||
| }; | }; | ||||
| const verifyOtp = async () => { | const verifyOtp = async () => { | ||||
| @@ -91,14 +80,13 @@ export default { | |||||
| mobile: mobile.value, | mobile: mobile.value, | ||||
| otpCode: otpCode.value, | otpCode: otpCode.value, | ||||
| }); | }); | ||||
| console.log("worked"); | |||||
| verifyOtpLoading.value = false; | verifyOtpLoading.value = false; | ||||
| router.push({ name: "dashPage" }); | |||||
| router.push({ name: "products" }); | |||||
| } catch (error) { | } catch (error) { | ||||
| verifyOtpLoading.value = false; | verifyOtpLoading.value = false; | ||||
| Swal.fire({ | Swal.fire({ | ||||
| icon: "error", | icon: "error", | ||||
| title: "اوه! انگار چیزی اشتباه شد", | |||||
| title: "انگار چیزی اشتباه شد", | |||||
| text: `${error.message}`, | text: `${error.message}`, | ||||
| confirmButtonText: "باشه", | confirmButtonText: "باشه", | ||||
| }); | }); | ||||
| @@ -243,7 +231,7 @@ export default { | |||||
| <div class="d-grid mt-3"> | <div class="d-grid mt-3"> | ||||
| <button | <button | ||||
| v-if="!otpSent && !resendAvailable" | |||||
| v-if="!otpSent" | |||||
| @click="sendOtp" | @click="sendOtp" | ||||
| type="button" | type="button" | ||||
| class="btn btn-primary" | class="btn btn-primary" | ||||
| @@ -258,15 +246,14 @@ export default { | |||||
| <span v-else> ارسال کد ورود </span> | <span v-else> ارسال کد ورود </span> | ||||
| </button> | </button> | ||||
| <div class="d-grid mt-3" v-if="resendAvailable && otpSent"> | |||||
| <button | |||||
| @click="resendOtp" | |||||
| type="button" | |||||
| class="btn btn-primary" | |||||
| > | |||||
| ارسال مجدد کد | |||||
| </button> | |||||
| </div> | |||||
| <button | |||||
| v-if="resendAvailable && otpSent" | |||||
| @click="resendOtp" | |||||
| type="button" | |||||
| class="btn btn-primary" | |||||
| > | |||||
| ارسال مجدد کد | |||||
| </button> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -192,6 +192,7 @@ | |||||
| v-model="selectedLandingProduct" | v-model="selectedLandingProduct" | ||||
| :options="formattedProducts" | :options="formattedProducts" | ||||
| placeholder="محصولی را انتخاب کنید" | placeholder="محصولی را انتخاب کنید" | ||||
| @search="handleSearch" | |||||
| /> | /> | ||||
| <small v-if="errors.selectedLandingProduct" class="text-danger"> | <small v-if="errors.selectedLandingProduct" class="text-danger"> | ||||
| {{ errors.selectedLandingProduct }} | {{ errors.selectedLandingProduct }} | ||||
| @@ -436,16 +437,30 @@ export default { | |||||
| }); | }); | ||||
| }; | }; | ||||
| const getProduct = () => { | |||||
| ApiServiece.get(`admin/products`) | |||||
| .then((resp) => { | |||||
| products.value = resp.data.data; | |||||
| }) | |||||
| .catch((err) => { | |||||
| console.log(err); | |||||
| }); | |||||
| const handleSearch = async (searchTerm) => { | |||||
| if (searchTerm.length < 3) return; | |||||
| try { | |||||
| const response = await ApiServiece.get( | |||||
| `admin/products?title=${searchTerm}` | |||||
| ); | |||||
| products.value = response.data.data; | |||||
| console.log(products.value, "products"); | |||||
| } catch (error) { | |||||
| console.error("Error fetching products:", error); | |||||
| products.value = []; // ✅ Reset to an empty array on error | |||||
| } | |||||
| }; | }; | ||||
| const formattedProducts = computed(() => | |||||
| Array.isArray(products.value) // ✅ Check if products.value is an array | |||||
| ? products.value.map((product) => ({ | |||||
| value: product.id, | |||||
| label: product.title, | |||||
| })) | |||||
| : [] | |||||
| ); | |||||
| const handleImageUpload = (event) => { | const handleImageUpload = (event) => { | ||||
| const file = event.target.files[0]; | const file = event.target.files[0]; | ||||
| @@ -462,13 +477,6 @@ export default { | |||||
| } | } | ||||
| }; | }; | ||||
| const formattedProducts = computed(() => { | |||||
| return products.value.map((product) => ({ | |||||
| value: product.id, | |||||
| label: product.title, | |||||
| })); | |||||
| }); | |||||
| const validateForm = () => { | const validateForm = () => { | ||||
| errors.value = {}; | errors.value = {}; | ||||
| if (!title.value) errors.value.title = "وارد کردن عنوان بنر الزامی است"; | if (!title.value) errors.value.title = "وارد کردن عنوان بنر الزامی است"; | ||||
| @@ -510,7 +518,6 @@ export default { | |||||
| onMounted(() => { | onMounted(() => { | ||||
| getCats(); | getCats(); | ||||
| getBrands(); | getBrands(); | ||||
| getProduct(); | |||||
| }); | }); | ||||
| const submitForm = () => { | const submitForm = () => { | ||||
| @@ -530,8 +537,6 @@ export default { | |||||
| formData.append("page_id", selectedCatPage.value); | formData.append("page_id", selectedCatPage.value); | ||||
| } | } | ||||
| if (pageType.value === "brand") { | if (pageType.value === "brand") { | ||||
| formData.append("page_id", selectedBrandPage.value); | formData.append("page_id", selectedBrandPage.value); | ||||
| } | } | ||||
| @@ -631,6 +636,7 @@ export default { | |||||
| imagePreview, | imagePreview, | ||||
| loading, | loading, | ||||
| brands, | brands, | ||||
| handleSearch, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -16,6 +16,8 @@ export default { | |||||
| showDescription, | showDescription, | ||||
| }, | }, | ||||
| setup() { | setup() { | ||||
| const selectedPageType = ref(""); | |||||
| let searchTimeout = null; | |||||
| const searchPage = ref(); | const searchPage = ref(); | ||||
| const currentPage = ref(1); | const currentPage = ref(1); | ||||
| const totalPages = ref(1); | const totalPages = ref(1); | ||||
| @@ -34,13 +36,26 @@ export default { | |||||
| .locale("fa") | .locale("fa") | ||||
| .format("YYYY/MM/DD"); | .format("YYYY/MM/DD"); | ||||
| }; | }; | ||||
| const handleSearchChange = () => { | |||||
| clearTimeout(searchTimeout); | |||||
| searchTimeout = setTimeout(() => { | |||||
| getBanners(); | |||||
| page.value = 1; | |||||
| }, 500); | |||||
| }; | |||||
| watch(searchQuery, () => { | watch(searchQuery, () => { | ||||
| getBanners(); | |||||
| handleSearchChange(); | |||||
| }); | }); | ||||
| const getBanners = () => { | const getBanners = () => { | ||||
| filterLoading.value = true; | filterLoading.value = true; | ||||
| ApiServiece.get( | ApiServiece.get( | ||||
| `admin/banners?paginate=${paginate.value || 10}&page=${page.value || 1}` | |||||
| `admin/banners?paginate=${paginate.value || 10}&page=${ | |||||
| page.value || 1 | |||||
| }&title=${searchQuery.value || ""}&page_type=${ | |||||
| selectedPageType.value || "" | |||||
| }` | |||||
| ) | ) | ||||
| .then((resp) => { | .then((resp) => { | ||||
| filterLoading.value = false; | filterLoading.value = false; | ||||
| @@ -136,11 +151,11 @@ export default { | |||||
| catDescription.value = desc; | catDescription.value = desc; | ||||
| }; | }; | ||||
| watch(searchQuery, () => { | |||||
| watch(page, () => { | |||||
| getBanners(); | getBanners(); | ||||
| }); | }); | ||||
| watch(page, () => { | |||||
| watch(selectedPageType, () => { | |||||
| getBanners(); | getBanners(); | ||||
| }); | }); | ||||
| @@ -201,6 +216,7 @@ export default { | |||||
| handlePageInput, | handlePageInput, | ||||
| searchPage, | searchPage, | ||||
| visiblePages, | visiblePages, | ||||
| selectedPageType, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -211,28 +227,53 @@ 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 shadow-sm rounded" | |||||
| dir="rtl" | dir="rtl" | ||||
| > | > | ||||
| <div class="d-flex align-items-center"> | |||||
| <router-link to="/addBanner"> | |||||
| <button class="btn btn-light text-primary btn-sm px-3"> | |||||
| افزودن بنر | |||||
| </button> | |||||
| </router-link> | |||||
| <div class="d-flex align-items-center gap-2"> | |||||
| <input | |||||
| v-model="searchQuery" | |||||
| type="text" | |||||
| placeholder="جستجو..." | |||||
| class="form-control form-control-sm" | |||||
| style=" | |||||
| width: 250px; | |||||
| border-radius: 15px; | |||||
| border: 1px solid #ddd; | |||||
| " | |||||
| /> | |||||
| <select | |||||
| class="form-select form-select-sm" | |||||
| v-model="selectedPageType" | |||||
| style="width: 120px; border-radius: 15px" | |||||
| > | |||||
| <option value="" disabled selected>نوع صفحه</option> | |||||
| <option value="">همه</option> | |||||
| <option value="special_page">صفحه ویژه</option> | |||||
| <option value="category">دسته بندی</option> | |||||
| <option value="brand">برند</option> | |||||
| </select> | |||||
| </div> | </div> | ||||
| <router-link to="/addBanner"> | |||||
| <button class="btn btn-light text-primary btn-sm px-3"> | |||||
| افزودن بنر | |||||
| </button> | |||||
| </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"> | ||||
| <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"> | |||||
| <tr> | <tr> | ||||
| <th>عکس</th> | <th>عکس</th> | ||||
| <th>عنوان</th> | <th>عنوان</th> | ||||
| <th>حالت صفحه</th> | <th>حالت صفحه</th> | ||||
| <th>شناسه صفحه</th> | <th>شناسه صفحه</th> | ||||
| <td>موقعیت</td> | |||||
| <td>نوع</td> | |||||
| <th>موقعیت</th> | |||||
| <th>نوع</th> | |||||
| <th>تاریخ ایجاد</th> | <th>تاریخ ایجاد</th> | ||||
| <th>عملیات</th> | <th>عملیات</th> | ||||
| </tr> | </tr> | ||||
| @@ -483,4 +524,12 @@ export default { | |||||
| cursor: pointer; | cursor: pointer; | ||||
| user-select: none; | user-select: none; | ||||
| } | } | ||||
| .filter-input { | |||||
| padding: 10px 16px; | |||||
| font-size: 14px; | |||||
| border: 1px solid #ccc; | |||||
| border-radius: 8px; | |||||
| margin-right: 16px; | |||||
| transition: all 0.3s ease; | |||||
| } | |||||
| </style> | </style> | ||||
| @@ -2,7 +2,7 @@ | |||||
| import Layout from "@/layout/custom.vue"; | import Layout from "@/layout/custom.vue"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| import moment from "jalali-moment"; | import moment from "jalali-moment"; | ||||
| import { onMounted, ref, watch , computed } from "vue"; | |||||
| import { onMounted, ref, watch, computed } from "vue"; | |||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| import Swal from "sweetalert2"; | import Swal from "sweetalert2"; | ||||
| @@ -17,6 +17,7 @@ export default { | |||||
| editBlogCat, | editBlogCat, | ||||
| }, | }, | ||||
| setup() { | setup() { | ||||
| let searchTimeout = null; | |||||
| const searchPage = ref(); | const searchPage = ref(); | ||||
| const currentPage = ref(1); | const currentPage = ref(1); | ||||
| const totalPages = ref(1); | const totalPages = ref(1); | ||||
| @@ -34,8 +35,17 @@ export default { | |||||
| .locale("fa") | .locale("fa") | ||||
| .format("YYYY/MM/DD"); | .format("YYYY/MM/DD"); | ||||
| }; | }; | ||||
| const handleSearchChange = () => { | |||||
| clearTimeout(searchTimeout); | |||||
| searchTimeout = setTimeout(() => { | |||||
| getCats(); | |||||
| page.value = 1; | |||||
| }, 500); | |||||
| }; | |||||
| watch(searchQuery, () => { | watch(searchQuery, () => { | ||||
| getCats(); | |||||
| handleSearchChange(); | |||||
| }); | }); | ||||
| const getCats = () => { | const getCats = () => { | ||||
| filterLoading.value = true; | filterLoading.value = true; | ||||
| @@ -49,14 +59,12 @@ export default { | |||||
| cats.value = resp.data.data.data; | cats.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; | ||||
| }) | }) | ||||
| .catch(() => { | .catch(() => { | ||||
| filterLoading.value = false; | filterLoading.value = false; | ||||
| }); | }); | ||||
| }; | }; | ||||
| const visiblePages = computed(() => { | const visiblePages = computed(() => { | ||||
| const pages = []; | const pages = []; | ||||
| if (totalPages.value <= 5) { | if (totalPages.value <= 5) { | ||||
| @@ -137,7 +145,6 @@ export default { | |||||
| }; | }; | ||||
| const editModalData = (id, title, icon) => { | const editModalData = (id, title, icon) => { | ||||
| catId.value = id; | catId.value = id; | ||||
| catTitle.value = title; | catTitle.value = title; | ||||
| catIcon.value = icon; | catIcon.value = icon; | ||||
| @@ -147,11 +154,6 @@ export default { | |||||
| getCats(); | getCats(); | ||||
| }); | }); | ||||
| watch(searchQuery, () => { | |||||
| getCats(); | |||||
| }); | |||||
| onMounted(() => { | onMounted(() => { | ||||
| getCats(); | getCats(); | ||||
| }); | }); | ||||
| @@ -195,14 +197,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" | ||||
| /> | /> | ||||
| <button | |||||
| data-bs-toggle="modal" | |||||
| data-bs-target="#addBlogCat" | |||||
| class="btn btn-light text-primary btn-sm px-3" | |||||
| > | |||||
| افزودن دسته | |||||
| </button> | |||||
| </div> | </div> | ||||
| <button | |||||
| data-bs-toggle="modal" | |||||
| data-bs-target="#addBlogCat" | |||||
| 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"> | ||||
| @@ -186,13 +186,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" | ||||
| /> | /> | ||||
| <router-link | |||||
| </div> | |||||
| <router-link | |||||
| to="/addBlog" | to="/addBlog" | ||||
| class="btn btn-light text-primary btn-sm px-3" | class="btn btn-light text-primary btn-sm px-3" | ||||
| > | > | ||||
| افزودن بلاگ | افزودن بلاگ | ||||
| </router-link> | </router-link> | ||||
| </div> | |||||
| </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"> | ||||
| @@ -201,7 +202,7 @@ export default { | |||||
| <tr> | <tr> | ||||
| <th>عکس</th> | <th>عکس</th> | ||||
| <th>عنوان</th> | <th>عنوان</th> | ||||
| <td>کلمه کلیدی</td> | |||||
| <th>کلمه کلیدی</th> | |||||
| <th>تاریخ ایجاد</th> | <th>تاریخ ایجاد</th> | ||||
| <th>عملیات</th> | <th>عملیات</th> | ||||
| </tr> | </tr> | ||||
| @@ -211,14 +211,15 @@ 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" | ||||
| /> | /> | ||||
| <button | |||||
| </div> | |||||
| <button | |||||
| data-bs-toggle="modal" | data-bs-toggle="modal" | ||||
| data-bs-target="#addBrand" | data-bs-target="#addBrand" | ||||
| class="btn btn-light text-primary btn-sm px-3" | class="btn btn-light text-primary btn-sm px-3" | ||||
| > | > | ||||
| افزودن برند | افزودن برند | ||||
| </button> | </button> | ||||
| </div> | |||||
| </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"> | ||||
| @@ -227,7 +228,7 @@ export default { | |||||
| <tr> | <tr> | ||||
| <th>عکس</th> | <th>عکس</th> | ||||
| <th>عنوان</th> | <th>عنوان</th> | ||||
| <td>توضیحات</td> | |||||
| <th>توضیحات</th> | |||||
| <th>تاریخ ایجاد</th> | <th>تاریخ ایجاد</th> | ||||
| <th>عملیات</th> | <th>عملیات</th> | ||||
| </tr> | </tr> | ||||
| @@ -16,10 +16,10 @@ export default { | |||||
| const searchPage = ref(); | const searchPage = ref(); | ||||
| const currentPage = ref(1); | const currentPage = ref(1); | ||||
| const totalPages = ref(1); | const totalPages = ref(1); | ||||
| const paginate = ref(5); | |||||
| const paginate = ref(20); | |||||
| const page = ref(1); | const page = ref(1); | ||||
| const filterLoading = ref(false); | const filterLoading = ref(false); | ||||
| const selectedStatus = ref(); | |||||
| const selectedStatus = ref(""); | |||||
| const calls = ref(); | const calls = ref(); | ||||
| const callText = ref(); | const callText = ref(); | ||||
| const convertToJalali = (date) => { | const convertToJalali = (date) => { | ||||
| @@ -35,7 +35,7 @@ export default { | |||||
| filterLoading.value = true; | filterLoading.value = true; | ||||
| ApiServiece.get( | ApiServiece.get( | ||||
| `admin/forms?status=${selectedStatus.value || ""}&paginate=${ | `admin/forms?status=${selectedStatus.value || ""}&paginate=${ | ||||
| paginate.value || 10 | |||||
| paginate.value || 20 | |||||
| }&page=${page.value || 1}` | }&page=${page.value || 1}` | ||||
| ) | ) | ||||
| .then((resp) => { | .then((resp) => { | ||||
| @@ -223,13 +223,13 @@ export default { | |||||
| dir="rtl" | dir="rtl" | ||||
| > | > | ||||
| <div class="d-flex align-items-center"> | <div class="d-flex align-items-center"> | ||||
| <label for="statusSelect" class="form-label me-2"> وضعیت </label> | |||||
| <select | <select | ||||
| class="form-select form-select-sm" | |||||
| v-model="selectedStatus" | v-model="selectedStatus" | ||||
| id="statusSelect" | |||||
| class="form-control form-control-sm d-inline-block me-2" | |||||
| style="width: 250px; border-radius: 15px" | |||||
| style="width: 120px; border-radius: 15px" | |||||
| > | > | ||||
| <option value="" disabled selected>وضعیت</option> | |||||
| <option value="">همه</option> | |||||
| <option value="answered">پاسخ داده شده</option> | <option value="answered">پاسخ داده شده</option> | ||||
| <option value="waiting">در انتظار</option> | <option value="waiting">در انتظار</option> | ||||
| </select> | </select> | ||||
| @@ -259,18 +259,11 @@ export default { | |||||
| ></i> | ></i> | ||||
| </td> | </td> | ||||
| <td>{{ call?.subject }}</td> | <td>{{ call?.subject }}</td> | ||||
| <td> | |||||
| <textarea | |||||
| :value="call.text" | |||||
| disabled | |||||
| readonly | |||||
| style=" | |||||
| width: 100%; | |||||
| resize: none; | |||||
| border: none; | |||||
| background: transparent; | |||||
| " | |||||
| ></textarea> | |||||
| <td | |||||
| class="text-center" | |||||
| style=" background: transparent" | |||||
| > | |||||
| {{ call.text }} | |||||
| </td> | </td> | ||||
| <td> | <td> | ||||
| <span class="badge" :class="getStatusClass(call.status)"> | <span class="badge" :class="getStatusClass(call.status)"> | ||||
| @@ -1,6 +1,6 @@ | |||||
| <script> | <script> | ||||
| import Layout from "@/layout/custom.vue"; | import Layout from "@/layout/custom.vue"; | ||||
| import { debounce } from "lodash"; | |||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| import moment from "jalali-moment"; | import moment from "jalali-moment"; | ||||
| import { onMounted, ref, watch, computed } from "vue"; | import { onMounted, ref, watch, computed } from "vue"; | ||||
| @@ -19,6 +19,7 @@ export default { | |||||
| editCat, | editCat, | ||||
| }, | }, | ||||
| setup() { | setup() { | ||||
| let searchTimeout = null; | |||||
| const catIcon = ref(); | const catIcon = ref(); | ||||
| const searchPage = ref(); | const searchPage = ref(); | ||||
| const currentPage = ref(1); | const currentPage = ref(1); | ||||
| @@ -90,16 +91,6 @@ export default { | |||||
| } | } | ||||
| } | } | ||||
| const debouncedSearch = debounce(() => { | |||||
| console.log("Searching for:", searchQuery.value); | |||||
| getCats(); | |||||
| }, 2000); | |||||
| // Use the watcher to react to changes in `searchQuery` | |||||
| watch(searchQuery, () => { | |||||
| debouncedSearch(); // Call the debounced function, no need to pass `newQuery` | |||||
| }); | |||||
| const nextPage = () => { | const nextPage = () => { | ||||
| if (currentPage.value < totalPages.value) { | if (currentPage.value < totalPages.value) { | ||||
| page.value++; | page.value++; | ||||
| @@ -160,10 +151,18 @@ export default { | |||||
| catDescription.value = desc; | catDescription.value = desc; | ||||
| }; | }; | ||||
| const handleSearchChange = () => { | |||||
| clearTimeout(searchTimeout); | |||||
| searchTimeout = setTimeout(() => { | |||||
| getCats(); | |||||
| page.value = 1; | |||||
| }, 500); | |||||
| }; | |||||
| watch(searchQuery, () => { | watch(searchQuery, () => { | ||||
| getCats(); | |||||
| handleSearchChange(); | |||||
| }); | }); | ||||
| const restoreCat = (id, title) => { | const restoreCat = (id, title) => { | ||||
| Swal.fire({ | Swal.fire({ | ||||
| text: `می خواهید دسته ${title} را بازیابی کنید؟`, | text: `می خواهید دسته ${title} را بازیابی کنید؟`, | ||||
| @@ -244,14 +243,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" | ||||
| /> | /> | ||||
| <button | |||||
| data-bs-toggle="modal" | |||||
| data-bs-target="#addCat" | |||||
| class="btn btn-light text-primary btn-sm px-3" | |||||
| > | |||||
| افزودن دسته | |||||
| </button> | |||||
| </div> | </div> | ||||
| <button | |||||
| data-bs-toggle="modal" | |||||
| data-bs-target="#addCat" | |||||
| 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"> | ||||
| @@ -260,7 +259,7 @@ export default { | |||||
| <tr> | <tr> | ||||
| <th>عکس</th> | <th>عکس</th> | ||||
| <th>عنوان</th> | <th>عنوان</th> | ||||
| <td>توضیحات</td> | |||||
| <th>توضیحات</th> | |||||
| <th>تاریخ ایجاد</th> | <th>تاریخ ایجاد</th> | ||||
| <th>پدر</th> | <th>پدر</th> | ||||
| <th>عملیات</th> | <th>عملیات</th> | ||||
| @@ -2,6 +2,7 @@ | |||||
| import Layout from "@/layout/custom.vue"; | import Layout from "@/layout/custom.vue"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| import moment from "jalali-moment"; | import moment from "jalali-moment"; | ||||
| import VueSelect from "vue3-select-component"; | |||||
| import { onMounted, ref, watch, computed } from "vue"; | import { onMounted, ref, watch, computed } from "vue"; | ||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| @@ -12,16 +13,22 @@ export default { | |||||
| name: "BORDER", | name: "BORDER", | ||||
| components: { | components: { | ||||
| Layout, | Layout, | ||||
| VueSelect, | |||||
| showDescription, | showDescription, | ||||
| }, | }, | ||||
| setup() { | setup() { | ||||
| const products = ref([]); | |||||
| const selectedProduct = ref(); | |||||
| const blogs = ref([]); | |||||
| const selectedBlog = ref(); | |||||
| const selectedCommentType = ref(""); | |||||
| const comment = ref(); | const comment = ref(); | ||||
| const searchPage = ref(); | const searchPage = ref(); | ||||
| const currentPage = ref(1); | const currentPage = ref(1); | ||||
| 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 selectedStatus = ref(""); | |||||
| const filterLoading = ref(false); | const filterLoading = ref(false); | ||||
| const searchQuery = ref(""); | const searchQuery = ref(""); | ||||
| const comments = ref(); | const comments = ref(); | ||||
| @@ -38,9 +45,11 @@ export default { | |||||
| const getComments = () => { | const getComments = () => { | ||||
| filterLoading.value = true; | filterLoading.value = true; | ||||
| ApiServiece.get( | ApiServiece.get( | ||||
| `admin/comments?title=${searchQuery.value || ""}&paginate=${ | |||||
| paginate.value || 10 | |||||
| }&page=${page.value || 1}` | |||||
| `admin/comments?type=${selectedCommentType.value || ""}&status=${ | |||||
| selectedStatus.value || "" | |||||
| }&commentable_id=${ | |||||
| selectedBlog.value ?? selectedProduct.value ?? "" | |||||
| }&paginate=${paginate.value || 10}&page=${page.value || 1}` | |||||
| ) | ) | ||||
| .then((resp) => { | .then((resp) => { | ||||
| filterLoading.value = false; | filterLoading.value = false; | ||||
| @@ -54,6 +63,50 @@ export default { | |||||
| }); | }); | ||||
| }; | }; | ||||
| const handleBlogSearch = async (searchTerm) => { | |||||
| if (searchTerm.length < 3) return; | |||||
| try { | |||||
| const response = await ApiServiece.get( | |||||
| `admin/blogs?title=${searchTerm}` | |||||
| ); | |||||
| blogs.value = response.data.data; | |||||
| } catch (error) { | |||||
| blogs.value = []; | |||||
| } | |||||
| }; | |||||
| const formattedBlog = computed(() => | |||||
| Array.isArray(blogs.value) | |||||
| ? blogs.value.map((blog) => ({ | |||||
| value: blog.id, | |||||
| label: blog.title, | |||||
| })) | |||||
| : [] | |||||
| ); | |||||
| const handleProductsSearch = async (searchTerm) => { | |||||
| if (searchTerm.length < 3) return; | |||||
| try { | |||||
| const response = await ApiServiece.get( | |||||
| `admin/products?title=${searchTerm}` | |||||
| ); | |||||
| products.value = response.data.data; | |||||
| } catch (error) { | |||||
| products.value = []; | |||||
| } | |||||
| }; | |||||
| const formattedProducts = computed(() => | |||||
| Array.isArray(products.value) | |||||
| ? products.value.map((product) => ({ | |||||
| value: product.id, | |||||
| label: product.title, | |||||
| })) | |||||
| : [] | |||||
| ); | |||||
| const modalData = (text) => { | const modalData = (text) => { | ||||
| comment.value = text; | comment.value = text; | ||||
| }; | }; | ||||
| @@ -173,6 +226,20 @@ export default { | |||||
| getComments(); | getComments(); | ||||
| }); | }); | ||||
| watch(selectedStatus, () => { | |||||
| getComments(); | |||||
| }); | |||||
| watch(selectedCommentType, () => { | |||||
| selectedProduct.value = ""; | |||||
| selectedBlog.value = ""; | |||||
| getComments(); | |||||
| }); | |||||
| watch([selectedBlog, selectedProduct], () => { | |||||
| getComments(); | |||||
| }); | |||||
| const nextPage = () => { | const nextPage = () => { | ||||
| if (currentPage.value < totalPages.value) { | if (currentPage.value < totalPages.value) { | ||||
| page.value++; | page.value++; | ||||
| @@ -207,6 +274,14 @@ export default { | |||||
| visiblePages, | visiblePages, | ||||
| modalData, | modalData, | ||||
| comment, | comment, | ||||
| selectedCommentType, | |||||
| selectedStatus, | |||||
| handleBlogSearch, | |||||
| formattedBlog, | |||||
| selectedBlog, | |||||
| formattedProducts, | |||||
| handleProductsSearch, | |||||
| selectedProduct, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -220,28 +295,67 @@ export default { | |||||
| 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"> | |||||
| <input | |||||
| v-model="searchQuery" | |||||
| type="text" | |||||
| placeholder="جستجو..." | |||||
| class="form-control form-control-sm d-inline-block me-2" | |||||
| style="width: 250px; border-radius: 15px" | |||||
| <div class="d-flex align-items-center"> | |||||
| <select | |||||
| class="form-select form-select-sm" | |||||
| v-model="selectedCommentType" | |||||
| style="width: 120px; border-radius: 15px" | |||||
| > | |||||
| <option value="" disabled selected>نوع نظر</option> | |||||
| <option value="">همه</option> | |||||
| <option value="product">محصول</option> | |||||
| <option value="blog">بلاگ</option> | |||||
| </select> | |||||
| <select | |||||
| class="form-select form-select-sm" | |||||
| v-model="selectedStatus" | |||||
| style="width: 120px; border-radius: 15px; margin-right: 7px" | |||||
| > | |||||
| <option value="" disabled selected>وضعیت</option> | |||||
| <option value="">همه</option> | |||||
| <option value="confirmed">تایید شده</option> | |||||
| <option value="rejected">رد شده</option> | |||||
| <option value="pending">معلق</option> | |||||
| </select> | |||||
| <VueSelect | |||||
| v-if="selectedCommentType === 'blog'" | |||||
| style=" | |||||
| --vs-border-radius: 16px; | |||||
| margin-right: 7px; | |||||
| --vs-min-height: 18px; | |||||
| " | |||||
| v-model="selectedBlog" | |||||
| :options="formattedBlog" | |||||
| placeholder="بلاگی را انتخاب کنید" | |||||
| @search="handleBlogSearch" | |||||
| /> | /> | ||||
| </div> --> | |||||
| <VueSelect | |||||
| v-if="selectedCommentType === 'product'" | |||||
| style=" | |||||
| --vs-border-radius: 16px; | |||||
| margin-right: 7px; | |||||
| --vs-min-height: 18px; | |||||
| " | |||||
| v-model="selectedProduct" | |||||
| :options="formattedProducts" | |||||
| placeholder="محصولی را انتخاب کنید" | |||||
| @search="handleProductsSearch" | |||||
| /> | |||||
| </div> | |||||
| </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"> | ||||
| <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> | ||||
| <td>امتیاز</td> | |||||
| <th>امتیاز</th> | |||||
| <th>نظر</th> | <th>نظر</th> | ||||
| <th>وضعیت</th> | <th>وضعیت</th> | ||||
| <th>تاریخ ایجاد</th> | <th>تاریخ ایجاد</th> | ||||
| @@ -250,9 +364,8 @@ export default { | |||||
| </thead> | </thead> | ||||
| <tbody> | <tbody> | ||||
| <tr v-for="comment in comments" :key="comment.id"> | <tr v-for="comment in comments" :key="comment.id"> | ||||
| <td>{{ comment?.user?.name }}</td> | <td>{{ comment?.user?.name }}</td> | ||||
| <td v-if="comment?.commentable_type === 'product'"> | <td v-if="comment?.commentable_type === 'product'"> | ||||
| محصول | محصول | ||||
| </td> | </td> | ||||
| @@ -267,7 +380,7 @@ export default { | |||||
| " | " | ||||
| ></span> | ></span> | ||||
| </td> | </td> | ||||
| <td | <td | ||||
| data-bs-toggle="modal" | data-bs-toggle="modal" | ||||
| data-bs-target="#showDescription" | data-bs-target="#showDescription" | ||||
| @@ -532,7 +645,6 @@ export default { | |||||
| display: inline-block; | display: inline-block; | ||||
| max-width: 100%; | max-width: 100%; | ||||
| font-size: 14px; | font-size: 14px; | ||||
| color: #333; | |||||
| white-space: nowrap; | white-space: nowrap; | ||||
| overflow: hidden; | overflow: hidden; | ||||
| text-overflow: ellipsis; | text-overflow: ellipsis; | ||||
| @@ -1,5 +1,6 @@ | |||||
| <script> | <script> | ||||
| import Layout from "@/layout/custom.vue"; | import Layout from "@/layout/custom.vue"; | ||||
| import VueSelect from "vue3-select-component"; | |||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| import moment from "jalali-moment"; | import moment from "jalali-moment"; | ||||
| import { onMounted, ref, watch, computed } from "vue"; | import { onMounted, ref, watch, computed } from "vue"; | ||||
| @@ -14,8 +15,16 @@ export default { | |||||
| components: { | components: { | ||||
| Layout, | Layout, | ||||
| showDescription, | showDescription, | ||||
| VueSelect, | |||||
| }, | }, | ||||
| setup() { | setup() { | ||||
| let searchTimeout = null; | |||||
| const selectedDiscountFormat = ref(""); | |||||
| const selectedDiscountType = ref(""); | |||||
| const categories = ref([]); | |||||
| const products = ref([]); | |||||
| const selectedcategory = ref(); | |||||
| const selectedProduct = ref(); | |||||
| const searchPage = ref(); | const searchPage = ref(); | ||||
| const currentPage = ref(1); | const currentPage = ref(1); | ||||
| const totalPages = ref(1); | const totalPages = ref(1); | ||||
| @@ -31,14 +40,27 @@ export default { | |||||
| .locale("fa") | .locale("fa") | ||||
| .format("YYYY/MM/DD"); | .format("YYYY/MM/DD"); | ||||
| }; | }; | ||||
| const handleSearchChange = () => { | |||||
| clearTimeout(searchTimeout); | |||||
| searchTimeout = setTimeout(() => { | |||||
| getDiscounts(); | |||||
| page.value = 1; | |||||
| }, 500); | |||||
| }; | |||||
| watch(searchQuery, () => { | watch(searchQuery, () => { | ||||
| getDiscounts(); | |||||
| handleSearchChange(); | |||||
| page.value = 1; | page.value = 1; | ||||
| }); | }); | ||||
| const getDiscounts = () => { | const getDiscounts = () => { | ||||
| filterLoading.value = true; | filterLoading.value = true; | ||||
| ApiServiece.get( | ApiServiece.get( | ||||
| `admin/discounts?title=${searchQuery.value || ""}&paginate=${ | |||||
| `admin/discounts?title=${searchQuery.value || ""}&product_id=${ | |||||
| selectedProduct.value || "" | |||||
| }&category_id=${selectedcategory.value || ""}&type=${selectedDiscountFormat.value || ""}&paginate=${ | |||||
| paginate.value || 10 | paginate.value || 10 | ||||
| }&page=${page.value || 1}` | }&page=${page.value || 1}` | ||||
| ) | ) | ||||
| @@ -118,11 +140,85 @@ export default { | |||||
| } | } | ||||
| } | } | ||||
| watch(searchQuery, () => { | |||||
| const handleCategorySearch = async (searchTerm) => { | |||||
| if (searchTerm.length < 3) return; | |||||
| try { | |||||
| const response = await ApiServiece.get( | |||||
| `admin/categories?title=${searchTerm}` | |||||
| ); | |||||
| categories.value = response.data.data; | |||||
| console.log(categories.value, "products"); | |||||
| } catch (error) { | |||||
| console.error("Error fetching products:", error); | |||||
| categories.value = []; | |||||
| } | |||||
| }; | |||||
| const formattedCategories = computed(() => | |||||
| Array.isArray(categories.value) | |||||
| ? categories.value.map((category) => ({ | |||||
| value: category.id, | |||||
| label: category.title, | |||||
| })) | |||||
| : [] | |||||
| ); | |||||
| const handleProductsSearch = async (searchTerm) => { | |||||
| if (searchTerm.length < 3) return; | |||||
| try { | |||||
| const response = await ApiServiece.get( | |||||
| `admin/products?title=${searchTerm}` | |||||
| ); | |||||
| products.value = response.data.data; | |||||
| } catch (error) { | |||||
| console.error("Error fetching products:", error); | |||||
| products.value = []; | |||||
| } | |||||
| }; | |||||
| const formattedProducts = computed(() => | |||||
| Array.isArray(products.value) | |||||
| ? products.value.map((product) => ({ | |||||
| value: product.id, | |||||
| label: product.title, | |||||
| })) | |||||
| : [] | |||||
| ); | |||||
| watch(page, () => { | |||||
| getDiscounts(); | getDiscounts(); | ||||
| }); | }); | ||||
| watch(page, () => { | |||||
| watch(selectedDiscountType, () => { | |||||
| selectedProduct.value = "", | |||||
| selectedcategory.value = "" | |||||
| if (selectedDiscountType.value === "") { | |||||
| filterLoading.value = true; | |||||
| ApiServiece.get( | |||||
| `admin/discounts?title=${searchQuery.value || ""}&paginate=${ | |||||
| paginate.value || 10 | |||||
| }&page=${page.value || 1}` | |||||
| ) | |||||
| .then((resp) => { | |||||
| filterLoading.value = false; | |||||
| discounts.value = resp.data.data.data; | |||||
| console.log(resp.data.data); | |||||
| currentPage.value = resp.data.data.current_page; | |||||
| totalPages.value = resp.data.data.last_page; | |||||
| }) | |||||
| .catch(() => { | |||||
| filterLoading.value = false; | |||||
| }); | |||||
| } | |||||
| }); | |||||
| watch([selectedProduct, selectedcategory], () => { | |||||
| getDiscounts(); | |||||
| }); | |||||
| watch(selectedDiscountFormat, () => { | |||||
| getDiscounts(); | getDiscounts(); | ||||
| }); | }); | ||||
| @@ -157,6 +253,14 @@ export default { | |||||
| handlePageInput, | handlePageInput, | ||||
| searchPage, | searchPage, | ||||
| visiblePages, | visiblePages, | ||||
| selectedDiscountType, | |||||
| handleCategorySearch, | |||||
| formattedCategories, | |||||
| formattedProducts, | |||||
| handleProductsSearch, | |||||
| selectedcategory, | |||||
| selectedProduct, | |||||
| selectedDiscountFormat, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -178,13 +282,53 @@ 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" | ||||
| /> | /> | ||||
| <router-link | |||||
| to="/addDiscount" | |||||
| class="btn btn-light text-primary btn-sm px-3 " | |||||
| <select | |||||
| class="form-select form-select-sm" | |||||
| v-model="selectedDiscountFormat" | |||||
| style="width: 120px; border-radius: 15px" | |||||
| > | |||||
| <option value="" disabled selected>حالت</option> | |||||
| <option value="">همه</option> | |||||
| <option value="percentage">درصدی</option> | |||||
| <option value="const">مبلغی</option> | |||||
| </select> | |||||
| <select | |||||
| class="form-select form-select-sm" | |||||
| v-model="selectedDiscountType" | |||||
| style="width: 120px; border-radius: 15px ; margin-right: 7px;" | |||||
| > | > | ||||
| افزودن تخفیف | |||||
| </router-link> | |||||
| <option value="" disabled selected>اعمال بر</option> | |||||
| <option value="">همه</option> | |||||
| <option value="products">محصولات</option> | |||||
| <option value="categories">دسته ها</option> | |||||
| </select> | |||||
| <VueSelect | |||||
| v-if="selectedDiscountType === 'categories'" | |||||
| style="--vs-border-radius: 16px; margin-right: 7px" | |||||
| v-model="selectedcategory" | |||||
| :options="formattedCategories" | |||||
| placeholder="دسته ای را انتخاب کنید" | |||||
| @search="handleCategorySearch" | |||||
| /> | |||||
| <VueSelect | |||||
| v-if="selectedDiscountType === 'products'" | |||||
| style="--vs-border-radius: 16px; margin-right: 7px" | |||||
| v-model="selectedProduct" | |||||
| :options="formattedProducts" | |||||
| placeholder="محصولی را انتخاب کنید" | |||||
| @search="handleProductsSearch" | |||||
| /> | |||||
| </div> | </div> | ||||
| <router-link | |||||
| to="/addDiscount" | |||||
| 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"> | ||||
| @@ -192,9 +336,9 @@ export default { | |||||
| <thead class="table-light"> | <thead class="table-light"> | ||||
| <tr> | <tr> | ||||
| <th>عنوان</th> | <th>عنوان</th> | ||||
| <td>مدل</td> | |||||
| <td>مقدار</td> | |||||
| <td>حداقل سفارش</td> | |||||
| <th>مدل</th> | |||||
| <th>مقدار</th> | |||||
| <th>حداقل سفارش</th> | |||||
| <th>تاریخ ایجاد</th> | <th>تاریخ ایجاد</th> | ||||
| <th>تاریخ انقضا</th> | <th>تاریخ انقضا</th> | ||||
| <th>عملیات</th> | <th>عملیات</th> | ||||
| @@ -31,7 +31,7 @@ export default { | |||||
| .locale("fa") | .locale("fa") | ||||
| .format("YYYY/MM/DD"); | .format("YYYY/MM/DD"); | ||||
| }; | }; | ||||
| watch( selectedStatus, () => { | |||||
| watch(selectedStatus, () => { | |||||
| getFaqs(); | getFaqs(); | ||||
| page.value = 1; | page.value = 1; | ||||
| }); | }); | ||||
| @@ -162,7 +162,7 @@ export default { | |||||
| visiblePages, | visiblePages, | ||||
| modalData, | modalData, | ||||
| comment, | comment, | ||||
| selectedStatus | |||||
| selectedStatus, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -177,7 +177,11 @@ export default { | |||||
| dir="rtl" | dir="rtl" | ||||
| > | > | ||||
| <div class="d-flex align-items-center"> | <div class="d-flex align-items-center"> | ||||
| <select class="form-select filter-input" v-model="selectedStatus"> | |||||
| <select | |||||
| class="form-select form-select-sm" | |||||
| v-model="selectedStatus" | |||||
| style="width: 120px; border-radius: 15px" | |||||
| > | |||||
| <option value="" disabled selected>وضعیت</option> | <option value="" disabled selected>وضعیت</option> | ||||
| <option value="">همه</option> | <option value="">همه</option> | ||||
| <option value="confirmed">تایید شده</option> | <option value="confirmed">تایید شده</option> | ||||
| @@ -429,7 +433,6 @@ export default { | |||||
| display: inline-block; | display: inline-block; | ||||
| max-width: 100%; | max-width: 100%; | ||||
| font-size: 14px; | font-size: 14px; | ||||
| color: #333; | |||||
| white-space: nowrap; | white-space: nowrap; | ||||
| overflow: hidden; | overflow: hidden; | ||||
| text-overflow: ellipsis; | text-overflow: ellipsis; | ||||
| @@ -450,13 +453,11 @@ export default { | |||||
| } | } | ||||
| .table-hover tbody tr:hover { | .table-hover tbody tr:hover { | ||||
| background-color: #f1f1f1; | |||||
| cursor: pointer; | cursor: pointer; | ||||
| } | } | ||||
| .comment-td { | .comment-td { | ||||
| cursor: pointer; | cursor: pointer; | ||||
| color: #007bff; | |||||
| text-decoration: underline; | text-decoration: underline; | ||||
| } | } | ||||
| @@ -6,6 +6,7 @@ import { onMounted, ref, watch, computed } from "vue"; | |||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| import Swal from "sweetalert2"; | import Swal from "sweetalert2"; | ||||
| import VueSelect from "vue3-select-component"; | |||||
| import addIdentity from "@/components/modals/identity/addIdentity.vue"; | import addIdentity from "@/components/modals/identity/addIdentity.vue"; | ||||
| import editIdentity from "@/components/modals/identity/editIdentity.vue"; | import editIdentity from "@/components/modals/identity/editIdentity.vue"; | ||||
| export default { | export default { | ||||
| @@ -14,8 +15,12 @@ export default { | |||||
| Layout, | Layout, | ||||
| addIdentity, | addIdentity, | ||||
| editIdentity, | editIdentity, | ||||
| VueSelect, | |||||
| }, | }, | ||||
| setup() { | setup() { | ||||
| let searchTimeout = null; | |||||
| const selectedcategory = ref(); | |||||
| const categories = ref([]); | |||||
| const cats = ref([]); | const cats = ref([]); | ||||
| const searchPage = ref(); | const searchPage = ref(); | ||||
| const currentPage = ref(1); | const currentPage = ref(1); | ||||
| @@ -30,19 +35,58 @@ export default { | |||||
| const attributeId = ref(); | const attributeId = ref(); | ||||
| const attrebuteCat = ref(); | const attrebuteCat = ref(); | ||||
| const handleSearch = async (searchTerm) => { | |||||
| if (searchTerm.length < 3) return; | |||||
| try { | |||||
| const response = await ApiServiece.get( | |||||
| `admin/categories?title=${searchTerm}` | |||||
| ); | |||||
| categories.value = response.data.data; | |||||
| console.log(categories.value, "products"); | |||||
| } catch (error) { | |||||
| console.error("Error fetching products:", error); | |||||
| categories.value = []; | |||||
| } | |||||
| }; | |||||
| const formattedCategories = computed(() => | |||||
| Array.isArray(categories.value) | |||||
| ? categories.value.map((category) => ({ | |||||
| value: category.id, | |||||
| label: category.title, | |||||
| })) | |||||
| : [] | |||||
| ); | |||||
| const convertToJalali = (date) => { | const convertToJalali = (date) => { | ||||
| return moment(date, "YYYY-MM-DD HH:mm:ss") | return moment(date, "YYYY-MM-DD HH:mm:ss") | ||||
| .locale("fa") | .locale("fa") | ||||
| .format("YYYY/MM/DD"); | .format("YYYY/MM/DD"); | ||||
| }; | }; | ||||
| const handleSearchChange = () => { | |||||
| clearTimeout(searchTimeout); | |||||
| searchTimeout = setTimeout(() => { | |||||
| getAttributes(); | |||||
| page.value = 1; | |||||
| }, 500); | |||||
| }; | |||||
| watch(searchQuery, () => { | watch(searchQuery, () => { | ||||
| handleSearchChange(); | |||||
| }); | |||||
| watch(selectedcategory, () => { | |||||
| getAttributes(); | getAttributes(); | ||||
| }); | }); | ||||
| const getAttributes = () => { | const getAttributes = () => { | ||||
| filterLoading.value = true; | filterLoading.value = true; | ||||
| ApiServiece.get( | ApiServiece.get( | ||||
| `admin/attributes?title=${searchQuery.value || ""} | `admin/attributes?title=${searchQuery.value || ""} | ||||
| &paginate=${paginate.value || 10}&page=${page.value || 1} | |||||
| &paginate=${paginate.value || 10}&page=${page.value || 1}&category_id=${ | |||||
| selectedcategory.value || "" | |||||
| } | |||||
| ` | ` | ||||
| ) | ) | ||||
| .then((resp) => { | .then((resp) => { | ||||
| @@ -157,10 +201,6 @@ export default { | |||||
| attrebuteCat.value = cat; | attrebuteCat.value = cat; | ||||
| }; | }; | ||||
| watch(searchQuery, () => { | |||||
| getAttributes(); | |||||
| }); | |||||
| onMounted(() => { | onMounted(() => { | ||||
| getAttributes(); | getAttributes(); | ||||
| getCategories(); | getCategories(); | ||||
| @@ -188,6 +228,9 @@ export default { | |||||
| visiblePages, | visiblePages, | ||||
| getCategories, | getCategories, | ||||
| cats, | cats, | ||||
| formattedCategories, | |||||
| handleSearch, | |||||
| selectedcategory, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -198,10 +241,10 @@ 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 gap-2"> | |||||
| <input | <input | ||||
| v-model="searchQuery" | v-model="searchQuery" | ||||
| type="text" | type="text" | ||||
| @@ -209,14 +252,21 @@ 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" | ||||
| /> | /> | ||||
| <button | |||||
| data-bs-toggle="modal" | |||||
| data-bs-target="#addIdentity" | |||||
| class="btn btn-light text-primary btn-sm px-3" | |||||
| > | |||||
| افزودن مشخصه | |||||
| </button> | |||||
| <VueSelect | |||||
| style="--vs-border-radius: 16px" | |||||
| v-model="selectedcategory" | |||||
| :options="formattedCategories" | |||||
| placeholder="دسته ای را انتخاب کنید" | |||||
| @search="handleSearch" | |||||
| /> | |||||
| </div> | </div> | ||||
| <button | |||||
| data-bs-toggle="modal" | |||||
| data-bs-target="#addIdentity" | |||||
| 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"> | ||||
| @@ -237,7 +287,9 @@ export default { | |||||
| backgroundColor: attribute.code, | backgroundColor: attribute.code, | ||||
| textAlign: 'center', | textAlign: 'center', | ||||
| }" | }" | ||||
| ></td> | |||||
| > | |||||
| {{ attribute?.category?.title }} | |||||
| </td> | |||||
| <td>{{ convertToJalali(attribute?.created_at) }}</td> | <td>{{ convertToJalali(attribute?.created_at) }}</td> | ||||
| <td> | <td> | ||||
| <button | <button | ||||
| @@ -279,7 +331,7 @@ export default { | |||||
| :title="attributeTitle" | :title="attributeTitle" | ||||
| :catId="attrebuteCat" | :catId="attrebuteCat" | ||||
| :id="attributeId" | :id="attributeId" | ||||
| :cats="cats" | |||||
| :cats="cats" | |||||
| /> | /> | ||||
| </BRow> | </BRow> | ||||
| <BRow> | <BRow> | ||||
| @@ -34,24 +34,14 @@ export default { | |||||
| }&paginate=${paginate.value || 10}&page=${page.value || 1}` | }&paginate=${paginate.value || 10}&page=${page.value || 1}` | ||||
| ) | ) | ||||
| .then((resp) => { | .then((resp) => { | ||||
| console.log(resp); | |||||
| filterLoading.value = false; | |||||
| allProducts.value = resp.data.data.data; | allProducts.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; | ||||
| }) | }) | ||||
| .catch((err) => { | |||||
| console.log(err); | |||||
| }); | |||||
| }; | |||||
| const getAllBrands = () => { | |||||
| ApiServiece.get("admin/brands") | |||||
| .then((resp) => { | |||||
| console.log(resp); | |||||
| brands.value = resp.data.data; | |||||
| }) | |||||
| .catch((err) => { | |||||
| console.log(err); | |||||
| .catch(() => { | |||||
| filterLoading.value = false; | |||||
| }); | }); | ||||
| }; | }; | ||||
| @@ -61,13 +51,6 @@ export default { | |||||
| .format("YYYY/MM/DD"); | .format("YYYY/MM/DD"); | ||||
| }; | }; | ||||
| const formattedBrands = computed(() => { | |||||
| return brands.value.map((brand) => ({ | |||||
| value: brand?.id, | |||||
| label: brand?.title, | |||||
| })); | |||||
| }); | |||||
| const getFile = () => { | const getFile = () => { | ||||
| isLoading.value = true; | isLoading.value = true; | ||||
| ApiServiece.post( | ApiServiece.post( | ||||
| @@ -164,9 +147,31 @@ export default { | |||||
| return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); | return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); | ||||
| } | } | ||||
| const handleBrandSearch = async (searchTerm) => { | |||||
| if (searchTerm.length < 3) return; | |||||
| try { | |||||
| const response = await ApiServiece.get( | |||||
| `admin/brands?title=${searchTerm}` | |||||
| ); | |||||
| brands.value = response.data.data; | |||||
| } catch (error) { | |||||
| console.error("Error fetching products:", error); | |||||
| brands.value = []; | |||||
| } | |||||
| }; | |||||
| const formattedBrands = computed(() => | |||||
| Array.isArray(brands.value) | |||||
| ? brands.value.map((brand) => ({ | |||||
| value: brand.id, | |||||
| label: brand.title, | |||||
| })) | |||||
| : [] | |||||
| ); | |||||
| onMounted(() => { | onMounted(() => { | ||||
| getAllProducts(); | getAllProducts(); | ||||
| getAllBrands(); | |||||
| }); | }); | ||||
| return { | return { | ||||
| allProducts, | allProducts, | ||||
| @@ -186,6 +191,8 @@ export default { | |||||
| date, | date, | ||||
| isLoading, | isLoading, | ||||
| formatWithCommas, | formatWithCommas, | ||||
| handleBrandSearch, | |||||
| filterLoading, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -197,52 +204,49 @@ export default { | |||||
| <BCol class="col-sm-12"> | <BCol class="col-sm-12"> | ||||
| <BCard no-body class="table-card"> | <BCard no-body class="table-card"> | ||||
| <BCardBody> | <BCardBody> | ||||
| <div class="text-end p-sm-4 pb-sm-2"> | |||||
| <!-- Button to Trigger Export --> | |||||
| <BRow> | |||||
| <BCol sm="3" class="mt-3"> | |||||
| <VueSelect | |||||
| style="--vs-border-radius: 8px" | |||||
| v-model="selectedBrand" | |||||
| :options="formattedBrands" | |||||
| placeholder="انتخاب برند" | |||||
| /> | |||||
| </BCol> | |||||
| <BCol style="margin-right: 180px" class="mt-3" sm="3"> | |||||
| <div class="form-group"> | |||||
| <DatePicker | |||||
| format="YYYY/MM/DD HH:mm:ss" | |||||
| type="date" | |||||
| :range="true" | |||||
| v-model="date" | |||||
| @input="handleInput" | |||||
| ></DatePicker> | |||||
| </div> | |||||
| </BCol> | |||||
| <BCol sm="4" class="mt-3"> | |||||
| <button | |||||
| @click="getFile" | |||||
| type="button" | |||||
| class="btn btn-primary" | |||||
| :disabled="isLoading" | |||||
| > | |||||
| <span | |||||
| v-if="isLoading" | |||||
| class="spinner-border spinner-border-sm" | |||||
| role="status" | |||||
| aria-hidden="true" | |||||
| ></span> | |||||
| <span v-else>گرفتن خروجی</span> | |||||
| </button> | |||||
| </BCol> | |||||
| </BRow> | |||||
| <!-- Product Selection Section --> | |||||
| <div | |||||
| class="card-header d-flex justify-content-between align-items-center p-3" | |||||
| dir="rtl" | |||||
| > | |||||
| <div class="d-flex align-items-center gap-3"> | |||||
| <!-- VueSelect for Brand Selection --> | |||||
| <VueSelect | |||||
| style="--vs-border-radius: 8px; min-width: 180px" | |||||
| v-model="selectedBrand" | |||||
| :options="formattedBrands" | |||||
| placeholder="برندی را انتخاب کنید" | |||||
| @search="handleBrandSearch" | |||||
| /> | |||||
| <!-- Date Picker with Proper Width --> | |||||
| <DatePicker | |||||
| format="YYYY/MM/DD HH:mm:ss" | |||||
| type="date" | |||||
| :range="true" | |||||
| v-model="date" | |||||
| @input="handleInput" | |||||
| class="custom-datepicker" | |||||
| /> | |||||
| </div> | |||||
| <!-- Export Button with Better Spacing --> | |||||
| <button | |||||
| @click="getFile" | |||||
| type="button" | |||||
| class="btn btn-light text-primary btn-sm px-4" | |||||
| :disabled="isLoading" | |||||
| > | |||||
| <span | |||||
| v-if="isLoading" | |||||
| class="spinner-border spinner-border-sm" | |||||
| role="status" | |||||
| aria-hidden="true" | |||||
| ></span> | |||||
| <span v-else>گرفتن خروجی</span> | |||||
| </button> | |||||
| </div> | </div> | ||||
| <div class="table-responsive"> | |||||
| <div v-if="!filterLoading"> | |||||
| <table class="table table-hover tbl-product" id="pc-dt-simple"> | <table class="table table-hover tbl-product" id="pc-dt-simple"> | ||||
| <thead> | <thead> | ||||
| <tr> | <tr> | ||||
| @@ -327,6 +331,10 @@ export default { | |||||
| </tbody> | </tbody> | ||||
| </table> | </table> | ||||
| </div> | </div> | ||||
| <div | |||||
| v-else | |||||
| class="filter-loader card table-card user-profile-list" | |||||
| ></div> | |||||
| </BCardBody> | </BCardBody> | ||||
| </BCard> | </BCard> | ||||
| </BCol> | </BCol> | ||||
| @@ -461,4 +469,7 @@ export default { | |||||
| cursor: pointer; | cursor: pointer; | ||||
| user-select: none; | user-select: none; | ||||
| } | } | ||||
| .custom-datepicker { | |||||
| min-width: 340px; | |||||
| } | |||||
| </style> | </style> | ||||
| @@ -13,10 +13,11 @@ export default { | |||||
| Layout, | Layout, | ||||
| }, | }, | ||||
| setup() { | setup() { | ||||
| const selectedStatus = ref(""); | |||||
| const searchPage = ref(); | const searchPage = ref(); | ||||
| const currentPage = ref(1); | const currentPage = ref(1); | ||||
| const totalPages = ref(1); | const totalPages = ref(1); | ||||
| const paginate = ref(5); | |||||
| const paginate = ref(20); | |||||
| const page = ref(1); | const page = ref(1); | ||||
| const filterLoading = ref(false); | const filterLoading = ref(false); | ||||
| @@ -35,9 +36,9 @@ export default { | |||||
| const getOrders = () => { | const getOrders = () => { | ||||
| filterLoading.value = true; | filterLoading.value = true; | ||||
| ApiServiece.get( | ApiServiece.get( | ||||
| `admin/orders?title=${searchQuery.value || ""}&paginate=${ | |||||
| paginate.value || 10 | |||||
| }&page=${page.value || 1}` | |||||
| `admin/orders?title=${searchQuery.value || ""}&status=${ | |||||
| selectedStatus.value || "" | |||||
| }&paginate=${paginate.value || 10}&page=${page.value || 1}` | |||||
| ) | ) | ||||
| .then((resp) => { | .then((resp) => { | ||||
| filterLoading.value = false; | filterLoading.value = false; | ||||
| @@ -185,6 +186,10 @@ export default { | |||||
| getOrders(); | getOrders(); | ||||
| }); | }); | ||||
| watch(selectedStatus, () => { | |||||
| getOrders(); | |||||
| }); | |||||
| const nextPage = () => { | const nextPage = () => { | ||||
| if (currentPage.value < totalPages.value) { | if (currentPage.value < totalPages.value) { | ||||
| page.value++; | page.value++; | ||||
| @@ -219,6 +224,7 @@ export default { | |||||
| visiblePages, | visiblePages, | ||||
| getStatusClass, | getStatusClass, | ||||
| getStatusLabel, | getStatusLabel, | ||||
| selectedStatus, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -240,6 +246,23 @@ 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" | ||||
| /> | /> | ||||
| <select | |||||
| class="form-select form-select-sm" | |||||
| v-model="selectedStatus" | |||||
| style="width: 120px; border-radius: 15px" | |||||
| > | |||||
| <option value="" disabled selected>وضعیت</option> | |||||
| <option value="">همه</option> | |||||
| <option value="waiting">در انتظار</option> | |||||
| <option value="paid">پرداخت شده</option> | |||||
| <option value="un_paid">پرداخت نشده</option> | |||||
| <option value="approved">تایید شده</option> | |||||
| <option value="processing">در حال پردازش</option> | |||||
| <option value="shipping">در حال ارسال</option> | |||||
| <option value="delivered">تحویل شده</option> | |||||
| <option value="canceled">لغو شده</option> | |||||
| </select> | |||||
| </div> | </div> | ||||
| </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"> | ||||
| @@ -248,7 +271,7 @@ export default { | |||||
| <thead class="table-light"> | <thead class="table-light"> | ||||
| <tr> | <tr> | ||||
| <th>شناسه</th> | <th>شناسه</th> | ||||
| <td>وضعیت</td> | |||||
| <th>وضعیت</th> | |||||
| <th>تاریخ ایجاد</th> | <th>تاریخ ایجاد</th> | ||||
| @@ -1,6 +1,7 @@ | |||||
| <script> | <script> | ||||
| import Layout from "@/layout/custom.vue"; | import Layout from "@/layout/custom.vue"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| import VueSelect from "vue3-select-component"; | |||||
| import moment from "jalali-moment"; | import moment from "jalali-moment"; | ||||
| import { onMounted, ref, watch, computed } from "vue"; | import { onMounted, ref, watch, computed } from "vue"; | ||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| @@ -11,8 +12,15 @@ export default { | |||||
| name: "BORDER", | name: "BORDER", | ||||
| components: { | components: { | ||||
| Layout, | Layout, | ||||
| VueSelect, | |||||
| }, | }, | ||||
| setup() { | setup() { | ||||
| let searchTimeout = null; | |||||
| const selectedProductType = ref(""); | |||||
| const categories = ref([]); | |||||
| const brands = ref(); | |||||
| const selectedcategory = ref(); | |||||
| const selectedBrand = ref(); | |||||
| const searchPage = ref(); | const searchPage = ref(); | ||||
| const currentPage = ref(1); | const currentPage = ref(1); | ||||
| const totalPages = ref(1); | const totalPages = ref(1); | ||||
| @@ -31,14 +39,36 @@ export default { | |||||
| .locale("fa") | .locale("fa") | ||||
| .format("YYYY/MM/DD"); | .format("YYYY/MM/DD"); | ||||
| }; | }; | ||||
| const handleSearchChange = () => { | |||||
| clearTimeout(searchTimeout); | |||||
| searchTimeout = setTimeout(() => { | |||||
| getProducts(); | |||||
| page.value = 1; | |||||
| }, 500); | |||||
| }; | |||||
| watch(searchQuery, () => { | watch(searchQuery, () => { | ||||
| getProducts(); | |||||
| handleSearchChange(); | |||||
| page.value = 1; | page.value = 1; | ||||
| }); | }); | ||||
| const getProducts = () => { | const getProducts = () => { | ||||
| filterLoading.value = true; | filterLoading.value = true; | ||||
| let isSpecial = 0; | |||||
| let isChosen = 0; | |||||
| if (selectedProductType.value === "special") { | |||||
| isSpecial = 1; | |||||
| } | |||||
| if (selectedProductType.value === "chosen") { | |||||
| isChosen = 1; | |||||
| } | |||||
| ApiServiece.get( | ApiServiece.get( | ||||
| `admin/products?title=${searchQuery.value || ""}&paginate=${ | |||||
| `admin/products?title=${searchQuery.value || ""}&category_id=${ | |||||
| selectedcategory.value || "" | |||||
| }&brand_id=${ | |||||
| selectedBrand.value || "" | |||||
| }&is_chosen=${isChosen}&is_special=${isSpecial}&paginate=${ | |||||
| paginate.value || 10 | paginate.value || 10 | ||||
| }&page=${page.value || 1}` | }&page=${page.value || 1}` | ||||
| ) | ) | ||||
| @@ -150,11 +180,15 @@ export default { | |||||
| } | } | ||||
| } | } | ||||
| watch(searchQuery, () => { | |||||
| watch(page, () => { | |||||
| getProducts(); | getProducts(); | ||||
| }); | }); | ||||
| watch(page, () => { | |||||
| watch([selectedcategory, selectedBrand], () => { | |||||
| getProducts(); | |||||
| }); | |||||
| watch(selectedProductType, () => { | |||||
| getProducts(); | getProducts(); | ||||
| }); | }); | ||||
| @@ -172,6 +206,53 @@ export default { | |||||
| } | } | ||||
| }; | }; | ||||
| const handleSearch = async (searchTerm) => { | |||||
| if (searchTerm.length < 3) return; | |||||
| try { | |||||
| const response = await ApiServiece.get( | |||||
| `admin/categories?title=${searchTerm}` | |||||
| ); | |||||
| categories.value = response.data.data; | |||||
| console.log(categories.value, "products"); | |||||
| } catch (error) { | |||||
| console.error("Error fetching products:", error); | |||||
| categories.value = []; | |||||
| } | |||||
| }; | |||||
| const formattedCategories = computed(() => | |||||
| Array.isArray(categories.value) | |||||
| ? categories.value.map((category) => ({ | |||||
| value: category.id, | |||||
| label: category.title, | |||||
| })) | |||||
| : [] | |||||
| ); | |||||
| const handleBrandSearch = async (searchTerm) => { | |||||
| if (searchTerm.length < 3) return; | |||||
| try { | |||||
| const response = await ApiServiece.get( | |||||
| `admin/brands?title=${searchTerm}` | |||||
| ); | |||||
| brands.value = response.data.data; | |||||
| } catch (error) { | |||||
| console.error("Error fetching products:", error); | |||||
| brands.value = []; | |||||
| } | |||||
| }; | |||||
| const formattedBrands = computed(() => | |||||
| Array.isArray(brands.value) | |||||
| ? brands.value.map((brand) => ({ | |||||
| value: brand.id, | |||||
| label: brand.title, | |||||
| })) | |||||
| : [] | |||||
| ); | |||||
| onMounted(() => { | onMounted(() => { | ||||
| getProducts(); | getProducts(); | ||||
| }); | }); | ||||
| @@ -191,6 +272,13 @@ export default { | |||||
| visiblePages, | visiblePages, | ||||
| restoreProduct, | restoreProduct, | ||||
| formatWithCommas, | formatWithCommas, | ||||
| handleSearch, | |||||
| formattedCategories, | |||||
| selectedcategory, | |||||
| selectedProductType, | |||||
| formattedBrands, | |||||
| handleBrandSearch, | |||||
| selectedBrand, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -212,13 +300,47 @@ 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" | ||||
| /> | /> | ||||
| <router-link | |||||
| to="/addProduct" | |||||
| class="btn btn-light text-primary btn-sm px-3" | |||||
| <select | |||||
| class="form-select form-select-sm" | |||||
| v-model="selectedProductType" | |||||
| style="width: 120px; border-radius: 15px" | |||||
| > | > | ||||
| افزودن محصول | |||||
| </router-link> | |||||
| <option value="" disabled selected>نوع محصول</option> | |||||
| <option value="">همه</option> | |||||
| <option value="chosen">برگزیده</option> | |||||
| <option value="special">ویژه</option> | |||||
| </select> | |||||
| <VueSelect | |||||
| style=" | |||||
| --vs-border-radius: 16px; | |||||
| --vs-min-height: 18px; | |||||
| margin-right: 7px; | |||||
| " | |||||
| v-model="selectedcategory" | |||||
| :options="formattedCategories" | |||||
| placeholder="دسته ای را انتخاب کنید" | |||||
| @search="handleSearch" | |||||
| /> | |||||
| <VueSelect | |||||
| style=" | |||||
| --vs-border-radius: 16px; | |||||
| --vs-min-height: 18px; | |||||
| margin-right: 7px; | |||||
| " | |||||
| v-model="selectedBrand" | |||||
| :options="formattedBrands" | |||||
| placeholder="برندی را انتخاب کنید" | |||||
| @search="handleBrandSearch" | |||||
| /> | |||||
| </div> | </div> | ||||
| <router-link | |||||
| to="/addProduct" | |||||
| 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"> | ||||
| @@ -227,14 +349,14 @@ export default { | |||||
| <tr> | <tr> | ||||
| <th>عکس</th> | <th>عکس</th> | ||||
| <th>عنوان</th> | <th>عنوان</th> | ||||
| <td>مدل</td> | |||||
| <td>تعداد فروش</td> | |||||
| <td>برگزیده</td> | |||||
| <td>تخفیف برگزیده</td> | |||||
| <td>ویژه</td> | |||||
| <td>قیمت عمده</td> | |||||
| <td>قیمت تک</td> | |||||
| <td>وضعیت</td> | |||||
| <th>مدل</th> | |||||
| <th>تعداد فروش</th> | |||||
| <th>برگزیده</th> | |||||
| <th>تخفیف برگزیده</th> | |||||
| <th>ویژه</th> | |||||
| <th>قیمت عمده</th> | |||||
| <th>قیمت تک</th> | |||||
| <th>وضعیت</th> | |||||
| <th>عملیات</th> | <th>عملیات</th> | ||||
| </tr> | </tr> | ||||
| </thead> | </thead> | ||||
| @@ -516,10 +638,7 @@ export default { | |||||
| font-weight: bold; | font-weight: bold; | ||||
| } | } | ||||
| .table-hover tbody tr:hover { | |||||
| background-color: #e9ecef; | |||||
| cursor: pointer; | |||||
| } | |||||
| .Product-Image { | .Product-Image { | ||||
| width: 50px; | width: 50px; | ||||
| @@ -562,9 +681,7 @@ export default { | |||||
| color: #fff; | color: #fff; | ||||
| } | } | ||||
| table tbody tr:hover { | |||||
| background-color: #f1f1f1; | |||||
| } | |||||
| td, | td, | ||||
| table[dir="rtl"] td, | table[dir="rtl"] td, | ||||
| @@ -46,7 +46,7 @@ export default { | |||||
| <Layout> | <Layout> | ||||
| <BRow> | <BRow> | ||||
| <BCol class="col-sm-12"> | <BCol class="col-sm-12"> | ||||
| <BCard | |||||
| <!-- <BCard | |||||
| no-body | no-body | ||||
| class="alert alert-warning p-0" | class="alert alert-warning p-0" | ||||
| @@ -70,7 +70,7 @@ export default { | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </BCardBody> | </BCardBody> | ||||
| </BCard> | |||||
| </BCard> --> | |||||
| <BRow> | <BRow> | ||||
| <BCol class="col-lg-5 col-xxl-3"> | <BCol class="col-lg-5 col-xxl-3"> | ||||
| <BCard no-body class="overflow-hidden"> | <BCard no-body class="overflow-hidden"> | ||||
| @@ -106,7 +106,7 @@ export default { | |||||
| حساب | حساب | ||||
| </span> | </span> | ||||
| </a> | </a> | ||||
| <a | |||||
| <!-- <a | |||||
| class="nav-link list-group-item list-group-item-action" | class="nav-link list-group-item list-group-item-action" | ||||
| id="user-set-information-tab" | id="user-set-information-tab" | ||||
| data-bs-toggle="pill" | data-bs-toggle="pill" | ||||
| @@ -119,8 +119,8 @@ export default { | |||||
| ><i class="ph-duotone ph-clipboard-text m-r-10"></i> اضافه | ><i class="ph-duotone ph-clipboard-text m-r-10"></i> اضافه | ||||
| کردن آدرس</span | کردن آدرس</span | ||||
| > | > | ||||
| </a> | |||||
| <a | |||||
| </a> --> | |||||
| <!-- <a | |||||
| class="nav-link list-group-item list-group-item-action" | class="nav-link list-group-item list-group-item-action" | ||||
| id="user-list-address-tab" | id="user-list-address-tab" | ||||
| data-bs-toggle="pill" | data-bs-toggle="pill" | ||||
| @@ -132,7 +132,7 @@ export default { | |||||
| <span class="f-w-500" | <span class="f-w-500" | ||||
| ><i class="ph-duotone ph-map-pin m-r-10"></i> مشاهده آدرس ها | ><i class="ph-duotone ph-map-pin m-r-10"></i> مشاهده آدرس ها | ||||
| </span> | </span> | ||||
| </a> | |||||
| </a> --> | |||||
| </div> | </div> | ||||
| </BCard> | </BCard> | ||||
| </BCol> | </BCol> | ||||
| @@ -17,6 +17,8 @@ export default { | |||||
| addUser, | addUser, | ||||
| }, | }, | ||||
| setup() { | setup() { | ||||
| const selectedStatus = ref(""); | |||||
| const filterLoading = ref(false); | |||||
| const searchPage = ref(); | const searchPage = ref(); | ||||
| const currentPage = ref(1); | const currentPage = ref(1); | ||||
| const totalPages = ref(1); | const totalPages = ref(1); | ||||
| @@ -36,16 +38,24 @@ export default { | |||||
| }; | }; | ||||
| const getUsers = () => { | const getUsers = () => { | ||||
| filterLoading.value = true; | |||||
| ApiServiece.get( | ApiServiece.get( | ||||
| `admin/users?name=${searchQuery.value || ""}&paginate=${ | `admin/users?name=${searchQuery.value || ""}&paginate=${ | ||||
| paginate.value || 10 | paginate.value || 10 | ||||
| }&page=${page.value || 1}&role=${selectedRole.value || "admin"}` | |||||
| ).then((resp) => { | |||||
| console.log(resp.data.data); | |||||
| users.value = resp.data.data.data; | |||||
| currentPage.value = resp.data.data.current_page; | |||||
| totalPages.value = resp.data.data.last_page; | |||||
| }); | |||||
| }&page=${page.value || 1}&role=${ | |||||
| selectedRole.value || "admin" | |||||
| }&trashed=${selectedStatus.value || ""}` | |||||
| ) | |||||
| .then((resp) => { | |||||
| console.log(resp.data.data); | |||||
| users.value = resp.data.data.data; | |||||
| currentPage.value = resp.data.data.current_page; | |||||
| totalPages.value = resp.data.data.last_page; | |||||
| filterLoading.value = false; | |||||
| }) | |||||
| .catch(() => { | |||||
| filterLoading.value = false; | |||||
| }); | |||||
| }; | }; | ||||
| const nextPage = () => { | const nextPage = () => { | ||||
| @@ -86,7 +96,9 @@ export default { | |||||
| getUsers(); | getUsers(); | ||||
| }); | }); | ||||
| watch(selectedStatus, () => { | |||||
| getUsers(); | |||||
| }); | |||||
| watch(searchQuery, (newQuery) => { | watch(searchQuery, (newQuery) => { | ||||
| debouncedSearch(newQuery); | debouncedSearch(newQuery); | ||||
| @@ -95,7 +107,7 @@ export default { | |||||
| const debouncedSearch = debounce((query) => { | const debouncedSearch = debounce((query) => { | ||||
| console.log("Searching for:", query); | console.log("Searching for:", query); | ||||
| getUsers(); | getUsers(); | ||||
| }, 2000); | |||||
| }, 1000); | |||||
| watch(page, () => { | watch(page, () => { | ||||
| getUsers(); | getUsers(); | ||||
| @@ -213,6 +225,8 @@ export default { | |||||
| page, | page, | ||||
| searchPage, | searchPage, | ||||
| selectedRole, | selectedRole, | ||||
| selectedStatus, | |||||
| filterLoading, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -228,15 +242,19 @@ export default { | |||||
| <div class="search-filters d-flex align-items-center gap-3"> | <div class="search-filters d-flex align-items-center gap-3"> | ||||
| <!-- Search Input --> | <!-- Search Input --> | ||||
| <input | <input | ||||
| type="text" | |||||
| class="form-control search-input" | |||||
| placeholder="جستجو بر اساس نام کاربر" | |||||
| id="search-users" | |||||
| v-model="searchQuery" | v-model="searchQuery" | ||||
| type="text" | |||||
| placeholder="جستجو..." | |||||
| class="form-control form-control-sm d-inline-block me-2" | |||||
| style="width: 250px; border-radius: 15px" | |||||
| /> | /> | ||||
| <!-- User Role Selector --> | <!-- User Role Selector --> | ||||
| <select class="form-select filter-input" v-model="selectedRole"> | |||||
| <select | |||||
| class="form-select form-select-sm" | |||||
| v-model="selectedRole" | |||||
| style="width: 120px; border-radius: 15px" | |||||
| > | |||||
| <option value="" disabled selected>نقش کاربر</option> | <option value="" disabled selected>نقش کاربر</option> | ||||
| <option value="">همه</option> | <option value="">همه</option> | ||||
| <option value="admin">فقط مدیران</option> | <option value="admin">فقط مدیران</option> | ||||
| @@ -244,6 +262,17 @@ export default { | |||||
| <option value="operator">فقط اپراتورها</option> | <option value="operator">فقط اپراتورها</option> | ||||
| </select> | </select> | ||||
| <select | |||||
| class="form-select form-select-sm" | |||||
| v-model="selectedStatus" | |||||
| style="width: 120px; border-radius: 15px" | |||||
| > | |||||
| <option value="" disabled selected>وضعیت</option> | |||||
| <option value="">همه</option> | |||||
| <option value="0">فعال</option> | |||||
| <option value="1">بلاک</option> | |||||
| </select> | |||||
| <!-- User Status Selector --> | <!-- User Status Selector --> | ||||
| <!-- <select | <!-- <select | ||||
| v-model="selectedStatus" | v-model="selectedStatus" | ||||
| @@ -265,7 +294,7 @@ export default { | |||||
| </button> | </button> | ||||
| </div> | </div> | ||||
| <div class="table-responsive"> | |||||
| <div v-if="!filterLoading" class="table-responsive"> | |||||
| <table class="table table-hover" id="pc-dt-simple"> | <table class="table table-hover" id="pc-dt-simple"> | ||||
| <thead> | <thead> | ||||
| <tr> | <tr> | ||||
| @@ -352,6 +381,10 @@ export default { | |||||
| </tbody> | </tbody> | ||||
| </table> | </table> | ||||
| </div> | </div> | ||||
| <div | |||||
| v-else | |||||
| class="filter-loader card table-card user-profile-list" | |||||
| ></div> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -575,4 +608,13 @@ export default { | |||||
| cursor: pointer; | cursor: pointer; | ||||
| user-select: none; | user-select: none; | ||||
| } | } | ||||
| .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> | ||||