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