| @@ -1,11 +1,11 @@ | |||
| { | |||
| "name": "LightAble", | |||
| "name": "NovinPlast", | |||
| "version": "0.1.0", | |||
| "lockfileVersion": 3, | |||
| "requires": true, | |||
| "packages": { | |||
| "": { | |||
| "name": "LightAble", | |||
| "name": "NovinPlast", | |||
| "version": "0.1.0", | |||
| "dependencies": { | |||
| "@amcharts/amcharts4": "^4.10.38", | |||
| @@ -1,5 +1,5 @@ | |||
| { | |||
| "name": "LightAble", | |||
| "name": "NovinPlast", | |||
| "version": "0.1.0", | |||
| "private": true, | |||
| "scripts": { | |||
| @@ -21,7 +21,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; | |||
| @@ -45,24 +45,12 @@ export default { | |||
| }) | |||
| .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: "متوجه شدم", | |||
| }); | |||
| }); | |||
| }; | |||
| @@ -78,10 +66,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 () => { | |||
| @@ -134,7 +123,6 @@ export default { | |||
| try { | |||
| const response = await ApiServiece.get("settings/logo_fav"); | |||
| settings.value = response.data.data; | |||
| } catch (error) { | |||
| console.error("Error fetching settings:", error); | |||
| } | |||
| @@ -230,14 +218,14 @@ export default { | |||
| role="status" | |||
| aria-hidden="true" | |||
| ></span> | |||
| <span v-else> ورود</span> | |||
| <span v-else>ورود</span> | |||
| </button> | |||
| </div> | |||
| </div> | |||
| <div class="d-grid mt-3"> | |||
| <button | |||
| v-if="!otpSent && !resendAvailable" | |||
| v-if="!otpSent" | |||
| @click="sendOtp" | |||
| type="button" | |||
| class="btn btn-primary" | |||
| @@ -252,15 +240,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> | |||
| @@ -1,12 +1,15 @@ | |||
| <script> | |||
| import { useStore } from "vuex"; | |||
| import { ref } from "vue"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import { ref, onMounted } from "vue"; | |||
| import { useRouter } from "vue-router"; | |||
| import Swal from "sweetalert2"; | |||
| export default { | |||
| name: "RESET-PASSWORD", | |||
| components: {}, | |||
| setup() { | |||
| const logo = ref(); | |||
| const settings = ref() | |||
| const router = useRouter(); | |||
| const loading = ref(false); | |||
| const errors = ref({}); | |||
| @@ -58,6 +61,20 @@ export default { | |||
| errors.value[field] = ""; | |||
| }; | |||
| const getSettings = async () => { | |||
| logo.value = localStorage.getItem("logo"); | |||
| try { | |||
| const response = await ApiServiece.get("settings/logo_fav"); | |||
| settings.value = response.data.data; | |||
| } catch (error) { | |||
| console.error("Error fetching settings:", error); | |||
| } | |||
| }; | |||
| onMounted(() => { | |||
| getSettings(); | |||
| }); | |||
| return { | |||
| newPassword, | |||
| confirmPassword, | |||
| @@ -65,6 +82,7 @@ export default { | |||
| clearError, | |||
| errors, | |||
| loading, | |||
| logo, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -74,10 +92,12 @@ export default { | |||
| <div class="auth-main v2"> | |||
| <div class="bg-overlay bg-dark"></div> | |||
| <div class="auth-wrapper"> | |||
| <div class="auth-sidecontent"></div> | |||
| <div class="auth-form"> | |||
| <div class="card my-5 mx-3"> | |||
| <div style="direction: rtl" class="card-body"> | |||
| <div class="text-center mb-4"> | |||
| <img :src="logo" alt="Logo" class="styled-logo" /> | |||
| </div> | |||
| <h4 class="f-w-500 mb-1">بازنشانی رمز عبور</h4> | |||
| <p class="mb-3"> | |||
| باز گشت به صفحه | |||
| @@ -134,3 +154,45 @@ export default { | |||
| </div> | |||
| <Rightbar /> | |||
| </template> | |||
| <style scoped> | |||
| .styled-logo { | |||
| display: block; | |||
| max-width: 120px; /* Adjust the logo size here */ | |||
| width: 100%; | |||
| height: auto; | |||
| border-radius: 10px; | |||
| box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2); | |||
| margin: 0 auto 30px; | |||
| margin-bottom: 90px; | |||
| } | |||
| .card { | |||
| border: none; | |||
| border-radius: 10px; | |||
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); | |||
| } | |||
| .card-body { | |||
| padding: 40px; | |||
| } | |||
| h4 { | |||
| font-size: 1.5rem; | |||
| font-weight: 500; | |||
| } | |||
| .text-center { | |||
| text-align: center; | |||
| } | |||
| .mb-3 { | |||
| margin-bottom: 1.5rem; | |||
| } | |||
| .form-control { | |||
| border-radius: 5px; | |||
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); | |||
| padding: 0.8rem 1rem; | |||
| } | |||
| </style> | |||
| @@ -309,12 +309,14 @@ export default { | |||
| Authorization: `Bearer ${localStorage.getItem("token")}`, | |||
| }, | |||
| }) | |||
| .then((resp) => { | |||
| console.log(resp); | |||
| .then(() => { | |||
| toast.success("!بلاگ با موفقیت اضافه شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| setTimeout(() => { | |||
| window.location.reload(); | |||
| }, 1500); | |||
| }) | |||
| .catch((error) => { | |||
| @@ -17,6 +17,8 @@ export default { | |||
| showDescription, | |||
| }, | |||
| setup() { | |||
| const productSeletorLoader = ref(false); | |||
| const blogSelectorLoader = ref(false); | |||
| const products = ref([]); | |||
| const selectedProduct = ref(); | |||
| const blogs = ref([]); | |||
| @@ -65,13 +67,15 @@ export default { | |||
| const handleBlogSearch = async (searchTerm) => { | |||
| if (searchTerm.length < 3) return; | |||
| blogSelectorLoader.value = true; | |||
| try { | |||
| const response = await ApiServiece.get( | |||
| `admin/blogs?title=${searchTerm}` | |||
| ); | |||
| blogs.value = response.data.data; | |||
| blogSelectorLoader.value = false; | |||
| } catch (error) { | |||
| blogSelectorLoader.value = false; | |||
| blogs.value = []; | |||
| } | |||
| }; | |||
| @@ -87,14 +91,16 @@ export default { | |||
| const handleProductsSearch = async (searchTerm) => { | |||
| if (searchTerm.length < 3) return; | |||
| productSeletorLoader.value = true; | |||
| try { | |||
| const response = await ApiServiece.get( | |||
| `admin/products?title=${searchTerm}` | |||
| ); | |||
| products.value = response.data.data; | |||
| productSeletorLoader.value = false; | |||
| } catch (error) { | |||
| products.value = []; | |||
| productSeletorLoader.value = false; | |||
| } | |||
| }; | |||
| @@ -282,6 +288,8 @@ export default { | |||
| formattedProducts, | |||
| handleProductsSearch, | |||
| selectedProduct, | |||
| productSeletorLoader, | |||
| blogSelectorLoader | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -328,6 +336,7 @@ export default { | |||
| " | |||
| v-model="selectedBlog" | |||
| :options="formattedBlog" | |||
| :isLoading="blogSelectorLoader" | |||
| placeholder="بلاگی را انتخاب کنید" | |||
| @search="handleBlogSearch" | |||
| /> | |||
| @@ -341,6 +350,7 @@ export default { | |||
| " | |||
| v-model="selectedProduct" | |||
| :options="formattedProducts" | |||
| :isLoading="productSeletorLoader" | |||
| placeholder="محصولی را انتخاب کنید" | |||
| @search="handleProductsSearch" | |||
| /> | |||
| @@ -107,28 +107,26 @@ | |||
| > | |||
| <option value="cat">دسته</option> | |||
| <option value="product">محصول</option> | |||
| <option value="both">هردو</option> | |||
| <option value="all">همه</option> | |||
| </select> | |||
| </div> | |||
| <small v-if="errors.discountType" class="text-danger"> | |||
| {{ errors.discountType }} | |||
| <small v-if="errors.whichPart" class="text-danger"> | |||
| {{ errors.whichPart }} | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="whichPart === 'cat' || whichPart === 'both'" md="6"> | |||
| <BCol v-if="whichPart === 'cat'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">دسته</label> | |||
| <select | |||
| :class="{ 'is-invalid': errors.selectedCat }" | |||
| <VueSelect | |||
| style="--vs-min-height: 48px; --vs-border-radius: 8px" | |||
| :isLoading="categorySelectorLoader" | |||
| v-model="selectedCat" | |||
| class="form-control" | |||
| @select="clearError('selectedCat')" | |||
| placeholder="انتخاب دسته" | |||
| > | |||
| <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | |||
| {{ cat.title }} | |||
| </option> | |||
| </select> | |||
| :options="formattedCategories" | |||
| placeholder="دسته ای را انتخاب کنید" | |||
| @search="handleSearch" | |||
| /> | |||
| </div> | |||
| <small v-if="errors.blogCat" class="text-danger"> | |||
| {{ errors.blogCat }} | |||
| @@ -136,7 +134,7 @@ | |||
| </BCol> | |||
| <BCol | |||
| v-if="whichPart === 'product' || whichPart === 'both'" | |||
| v-if="whichPart === 'product'" | |||
| sm="6" | |||
| class="mt-3" | |||
| style="margin-top: 30px" | |||
| @@ -147,8 +145,10 @@ | |||
| <VueSelect | |||
| style="--vs-min-height: 48px; --vs-border-radius: 8px" | |||
| :isLoading="categorySelectorLoader" | |||
| v-model="selectedProduct" | |||
| :options="formattedProducts" | |||
| @search="handleProductSearch" | |||
| placeholder="محصولی را انتخاب کنید" | |||
| /> | |||
| <small v-if="errors.selectedProduct" class="text-danger"> | |||
| @@ -212,11 +212,11 @@ | |||
| <script> | |||
| import VueSelect from "vue3-select-component"; | |||
| import moment from "moment-jalaali"; | |||
| import moment from "jalali-moment"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import { ref, onMounted, computed } from "vue"; | |||
| import { ref, computed } from "vue"; | |||
| import Layout from "@/layout/custom.vue"; | |||
| import DatePicker from "vue3-persian-datetime-picker"; | |||
| @@ -229,6 +229,7 @@ export default { | |||
| VueSelect, | |||
| }, | |||
| setup() { | |||
| const productSelectorLoader = ref(false); | |||
| const title = ref(); | |||
| const discountType = ref(); | |||
| const amount = ref(); | |||
| @@ -240,36 +241,58 @@ export default { | |||
| const maxUsage = ref(); | |||
| const whichPart = ref(); | |||
| const loading = ref(false); | |||
| const cats = ref([]); | |||
| const categories = ref([]); | |||
| const errors = ref({}); | |||
| const products = ref(); | |||
| const getCats = () => { | |||
| ApiServiece.get(`admin/categories`) | |||
| .then((resp) => { | |||
| cats.value = resp.data.data; | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| }); | |||
| const categorySelectorLoader = ref(false); | |||
| const handleSearch = async (searchTerm) => { | |||
| if (searchTerm.length < 3) return; | |||
| categorySelectorLoader.value = true; | |||
| try { | |||
| const response = await ApiServiece.get( | |||
| `admin/categories?title=${searchTerm}` | |||
| ); | |||
| categories.value = response.data.data; | |||
| categorySelectorLoader.value = false; | |||
| } catch (error) { | |||
| categorySelectorLoader.value = false; | |||
| categories.value = []; | |||
| } | |||
| }; | |||
| const getProduct = () => { | |||
| ApiServiece.get(`admin/products`) | |||
| .then((resp) => { | |||
| products.value = resp.data.data; | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| }); | |||
| const formattedCategories = computed(() => | |||
| Array.isArray(categories.value) | |||
| ? categories.value.map((category) => ({ | |||
| value: category.id, | |||
| label: category.title, | |||
| })) | |||
| : [] | |||
| ); | |||
| const handleProductSearch = async (searchTerm) => { | |||
| if (searchTerm.length < 3) return; | |||
| productSelectorLoader.value = true; | |||
| try { | |||
| const response = await ApiServiece.get( | |||
| `admin/products?title=${searchTerm}` | |||
| ); | |||
| products.value = response.data.data; | |||
| productSelectorLoader.value = false; | |||
| } catch (error) { | |||
| productSelectorLoader.value = false; | |||
| products.value = []; | |||
| } | |||
| }; | |||
| const formattedProducts = computed(() => { | |||
| return products.value.map((product) => ({ | |||
| value: product.id, | |||
| label: product.title, | |||
| })); | |||
| }); | |||
| const formattedProducts = computed(() => | |||
| Array.isArray(products.value) | |||
| ? products.value.map((product) => ({ | |||
| value: product.id, | |||
| label: product.title, | |||
| })) | |||
| : [] | |||
| ); | |||
| const validateForm = () => { | |||
| errors.value = {}; | |||
| @@ -278,24 +301,14 @@ export default { | |||
| errors.value.discountType = "وارد کردن حالت تخفیف الزامی است"; | |||
| if (!amount.value) | |||
| errors.value.amount = "وارد کردن مقدار تخفیف الزامی می باشد"; | |||
| if (!minOrder.value) | |||
| errors.value.minOrder = "وارد کردن حداقل میزان تخفیف الزامی می باشد"; | |||
| if ( | |||
| !selectedCat.value && | |||
| (whichPart.value === "cat" || whichPart.value === "both") | |||
| ) | |||
| if (!selectedCat.value && whichPart.value === "cat") | |||
| errors.value.selectedCat = "انتخاب دسته برای تخفیف الزامی می باشد"; | |||
| if ( | |||
| !selectedProduct.value && | |||
| (whichPart.value === "product" || whichPart.value === "both") | |||
| ) | |||
| if (!selectedProduct.value && whichPart.value === "product") | |||
| errors.value.selectedProduct = "انتخاب محصول برای تخفیف الزامی می باشد"; | |||
| if (!startDate.value) | |||
| errors.value.startDate = "انتخاب تاریخ اعمال تخفیف الزامی می باشد "; | |||
| if (!expire.value) | |||
| errors.value.expire = "انتخاب تاریخ انقضای تخفیف الزامی می باشد "; | |||
| if (!maxUsage.value) | |||
| errors.value.maxUsage = "مشخص کنید تخفیف چند بار مصرف است "; | |||
| if (!whichPart.value) | |||
| errors.value.whichPart = "مشخص کنید تخفیف بر چه بخشی اعمال شود"; | |||
| return Object.keys(errors.value).length === 0; | |||
| @@ -305,21 +318,7 @@ export default { | |||
| errors.value[field] = ""; | |||
| }; | |||
| onMounted(() => { | |||
| getCats(); | |||
| getProduct(); | |||
| }); | |||
| function convertJalaliToGregorian(jalaliDate) { | |||
| return moment(jalaliDate, "jYYYY/jMM/jDD HH:mm:ss") | |||
| .locale("fa") // Ensure Persian locale | |||
| .format("YYYY-MM-DD HH:mm:ss"); // Convert to Gregorian format | |||
| } | |||
| const submitForm = () => { | |||
| startDate.value = convertJalaliToGregorian(startDate.value); | |||
| expire.value = convertJalaliToGregorian(expire.value); | |||
| if (!validateForm()) { | |||
| toast.error("لطفا فیلد های لازم را وارد نمایید", { | |||
| position: "top-right", | |||
| @@ -327,22 +326,38 @@ export default { | |||
| }); | |||
| return; | |||
| } | |||
| loading.value = true; | |||
| const formData = new FormData(); | |||
| formData.append("title", title.value); | |||
| formData.append("type", discountType.value); | |||
| formData.append("amount", amount.value); | |||
| formData.append("min_order", minOrder.value); | |||
| if (whichPart.value === "cat" || whichPart.value === "both") { | |||
| if (whichPart.value === "cat") { | |||
| formData.append("category_id", selectedCat.value); | |||
| } | |||
| if (whichPart.value === "product" || whichPart.value === "both") { | |||
| if (whichPart.value === "product") { | |||
| formData.append("product_id", selectedProduct.value); | |||
| } | |||
| formData.append("starts_at", startDate.value); | |||
| formData.append("expires_at", expire.value); | |||
| if (startDate.value) { | |||
| const georgianDate = moment( | |||
| startDate.value, | |||
| "jYYYY/jMM/jDD HH:mm:ss" | |||
| ).format("YYYY/MM/DD HH:mm:ss"); | |||
| formData.append("starts_at", georgianDate); | |||
| } | |||
| if (expire.value) { | |||
| const georgianDate = moment( | |||
| expire.value, | |||
| "jYYYY/jMM/jDD HH:mm:ss" | |||
| ).format("YYYY/MM/DD HH:mm:ss"); | |||
| formData.append("expires_at", georgianDate); | |||
| } | |||
| formData.append("max_usage", maxUsage.value); | |||
| ApiServiece.post(`/admin/discounts`, formData) | |||
| @@ -353,6 +368,18 @@ export default { | |||
| autoClose: 1000, | |||
| }); | |||
| console.log(resp); | |||
| // Clear form fields after successful submission | |||
| title.value = ""; | |||
| discountType.value = ""; | |||
| amount.value = ""; | |||
| minOrder.value = ""; | |||
| whichPart.value = ""; | |||
| selectedCat.value = null; | |||
| selectedProduct.value = null; | |||
| startDate.value = ""; | |||
| expire.value = ""; | |||
| maxUsage.value = ""; | |||
| }) | |||
| .catch((error) => { | |||
| loading.value = false; | |||
| @@ -365,7 +392,7 @@ export default { | |||
| }; | |||
| return { | |||
| cats, | |||
| categories, | |||
| errors, | |||
| title, | |||
| products, | |||
| @@ -381,6 +408,12 @@ export default { | |||
| whichPart, | |||
| clearError, | |||
| loading, | |||
| handleSearch, | |||
| categorySelectorLoader, | |||
| formattedCategories, | |||
| handleProductSearch, | |||
| productSelectorLoader, | |||
| formattedProducts, | |||
| }; | |||
| }, | |||
| @@ -60,9 +60,9 @@ export default { | |||
| ApiServiece.get( | |||
| `admin/discounts?title=${searchQuery.value || ""}&product_id=${ | |||
| selectedProduct.value || "" | |||
| }&category_id=${selectedcategory.value || ""}&type=${selectedDiscountFormat.value || ""}&paginate=${ | |||
| paginate.value || 10 | |||
| }&page=${page.value || 1}` | |||
| }&category_id=${selectedcategory.value || ""}&type=${ | |||
| selectedDiscountFormat.value || "" | |||
| }&paginate=${paginate.value || 10}&page=${page.value || 1}` | |||
| ) | |||
| .then((resp) => { | |||
| filterLoading.value = false; | |||
| @@ -192,8 +192,7 @@ export default { | |||
| }); | |||
| watch(selectedDiscountType, () => { | |||
| selectedProduct.value = "", | |||
| selectedcategory.value = "" | |||
| (selectedProduct.value = ""), (selectedcategory.value = ""); | |||
| if (selectedDiscountType.value === "") { | |||
| filterLoading.value = true; | |||
| ApiServiece.get( | |||
| @@ -297,7 +296,7 @@ export default { | |||
| <select | |||
| class="form-select form-select-sm" | |||
| v-model="selectedDiscountType" | |||
| style="width: 120px; border-radius: 15px ; margin-right: 7px;" | |||
| style="width: 120px; border-radius: 15px; margin-right: 7px" | |||
| > | |||
| <option value="" disabled selected>اعمال بر</option> | |||
| <option value="">همه</option> | |||
| @@ -339,7 +338,8 @@ export default { | |||
| <th>مدل</th> | |||
| <th>مقدار</th> | |||
| <th>حداقل سفارش</th> | |||
| <th>تاریخ ایجاد</th> | |||
| <th>حداکثر میزان استفاده</th> | |||
| <th>تاریخ شروع</th> | |||
| <th>تاریخ انقضا</th> | |||
| <th>عملیات</th> | |||
| </tr> | |||
| @@ -351,6 +351,7 @@ export default { | |||
| <td v-if="discount.type === 'percentage'">درصدی</td> | |||
| <td>{{ discount.amount }}</td> | |||
| <td>{{ discount.min_order }}</td> | |||
| <td>{{ discount.max_usage || "" }}</td> | |||
| <td>{{ convertToJalali(discount?.starts_at) }}</td> | |||
| <td>{{ convertToJalali(discount?.expires_at) }}</td> | |||
| <td> | |||
| @@ -108,7 +108,7 @@ | |||
| > | |||
| <option value="cat">دسته</option> | |||
| <option value="product">محصول</option> | |||
| <option value="both">هردو</option> | |||
| <option value="all">همه</option> | |||
| </select> | |||
| </div> | |||
| <small v-if="errors.discountType" class="text-danger"> | |||
| @@ -116,47 +116,33 @@ | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="whichPart === 'cat' || whichPart === 'both'" md="6"> | |||
| <BCol v-if="whichPart === 'cat'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">دسته</label> | |||
| <select | |||
| :class="{ 'is-invalid': errors.selectedCat }" | |||
| <VueSelect | |||
| style="--vs-min-height: 48px; --vs-border-radius: 8px" | |||
| :isLoading="categorySelectorLoader" | |||
| v-model="selectedCat" | |||
| class="form-control" | |||
| @select="clearError('selectedCat')" | |||
| placeholder="انتخاب دسته" | |||
| > | |||
| <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | |||
| {{ cat.title }} | |||
| </option> | |||
| </select> | |||
| :options="formattedCategories" | |||
| placeholder="دسته ای را انتخاب کنید" | |||
| /> | |||
| </div> | |||
| <small v-if="errors.selectedCat" class="text-danger"> | |||
| {{ errors.selectedCat }} | |||
| </small> | |||
| </BCol> | |||
| <BCol | |||
| v-if="whichPart === 'product' || whichPart === 'both'" | |||
| md="6" | |||
| > | |||
| <BCol v-if="whichPart === 'product'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">محصول</label> | |||
| <select | |||
| :class="{ 'is-invalid': errors.selectedProduct }" | |||
| <VueSelect | |||
| style="--vs-min-height: 48px; --vs-border-radius: 8px" | |||
| :isLoading="productSelectorLoader" | |||
| v-model="selectedProduct" | |||
| class="form-control" | |||
| @select="clearError('selectedProduct')" | |||
| placeholder="انتخاب محصول" | |||
| > | |||
| <option | |||
| v-for="product in products" | |||
| :key="product.id" | |||
| :value="product.id" | |||
| > | |||
| {{ product.title }} | |||
| </option> | |||
| </select> | |||
| :options="formattedProducts" | |||
| placeholder="محصولی را انتخاب کنید " | |||
| @search="handleProductSearch" | |||
| /> | |||
| </div> | |||
| <small v-if="errors.selectedProduct" class="text-danger"> | |||
| {{ errors.selectedProduct }} | |||
| @@ -220,12 +206,13 @@ | |||
| </template> | |||
| <script> | |||
| import moment from "moment"; | |||
| import VueSelect from "vue3-select-component"; | |||
| import moment from "jalali-moment"; | |||
| import { useRoute } from "vue-router"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import { ref, onMounted } from "vue"; | |||
| import { ref, onMounted, computed } from "vue"; | |||
| import Layout from "@/layout/custom.vue"; | |||
| import DatePicker from "vue3-persian-datetime-picker"; | |||
| @@ -234,39 +221,78 @@ export default { | |||
| components: { | |||
| Layout, | |||
| DatePicker, | |||
| VueSelect, | |||
| }, | |||
| setup() { | |||
| const route = useRoute(); | |||
| const productSelectorLoader = ref(false); | |||
| const title = ref(); | |||
| const discountType = ref(); | |||
| const amount = ref(); | |||
| const minOrder = ref(); | |||
| const selectedCat = ref(); | |||
| const selectedProduct = ref(); | |||
| const selectedProduct = ref({ | |||
| value: 'product-id-123', // Example selected product ID | |||
| label: 'Amazing Product', // Example selected product label | |||
| }); | |||
| const startDate = ref(); | |||
| const expire = ref(); | |||
| const maxUsage = ref(); | |||
| const whichPart = ref(); | |||
| const loading = ref(false); | |||
| const cats = ref([]); | |||
| const categories = ref([]); | |||
| const errors = ref({}); | |||
| const products = ref(); | |||
| const discount = ref(); | |||
| const getCats = () => { | |||
| ApiServiece.get(`admin/categories`) | |||
| .then((resp) => { | |||
| cats.value = resp.data.data; | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| }); | |||
| const formattedCategories = computed(() => | |||
| Array.isArray(categories.value) | |||
| ? categories.value.map((category) => ({ | |||
| value: category.id, | |||
| label: category.title, | |||
| })) | |||
| : [] | |||
| ); | |||
| const handleProductSearch = async (searchTerm) => { | |||
| if (searchTerm.length < 3) return; | |||
| productSelectorLoader.value = true; | |||
| try { | |||
| const response = await ApiServiece.get( | |||
| `admin/products?title=${searchTerm}` | |||
| ); | |||
| products.value = response.data.data; | |||
| productSelectorLoader.value = false; | |||
| } catch (error) { | |||
| productSelectorLoader.value = false; | |||
| products.value = []; | |||
| } | |||
| }; | |||
| const getProduct = () => { | |||
| ApiServiece.get(`admin/products`) | |||
| const formattedProducts = computed(() => { | |||
| // If products are available, map them to options | |||
| if (Array.isArray(products.value) && products.value.length > 0) { | |||
| return products.value.map((product) => ({ | |||
| value: product.id, | |||
| label: product.title, | |||
| })); | |||
| } | |||
| // If no products are available, show a placeholder option | |||
| return [ | |||
| { | |||
| value: "", | |||
| label: "برای جستجو شروع به تایپ کنید...", | |||
| disabled: true, | |||
| }, | |||
| ]; | |||
| }); | |||
| const getCats = () => { | |||
| ApiServiece.get(`admin/categories`) | |||
| .then((resp) => { | |||
| products.value = resp.data.data; | |||
| categories.value = resp.data.data; | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| @@ -285,14 +311,30 @@ export default { | |||
| if (discount.value.category_id) { | |||
| whichPart.value = "cat"; | |||
| } | |||
| console.log(discount.value.product_id); | |||
| selectedProduct.value = discount.value.product_id; | |||
| if (!discount.value.category_id && !discount.value.product_id) { | |||
| whichPart.value = "all"; | |||
| } | |||
| if (discount.value.product_id) { | |||
| whichPart.value = "product"; | |||
| } | |||
| startDate.value = discount.value.starts_at; | |||
| expire.value = discount.value.expires_at; | |||
| if (discount?.value.starts_at) { | |||
| startDate.value = moment( | |||
| discount?.value.starts_at, | |||
| "YYYY-MM-DD HH:mm:ss" | |||
| ).format("jYYYY/jMM/jDD HH:mm:ss"); | |||
| } | |||
| if (discount?.value.expires_at) { | |||
| expire.value = moment( | |||
| discount?.value.expires_at, | |||
| "YYYY-MM-DD HH:mm:ss" | |||
| ).format("jYYYY/jMM/jDD HH:mm:ss"); | |||
| } | |||
| maxUsage.value = discount.value.max_usage; | |||
| }) | |||
| .catch((err) => { | |||
| @@ -328,24 +370,14 @@ export default { | |||
| errors.value.discountType = "وارد کردن حالت تخفیف الزامی است"; | |||
| if (!amount.value) | |||
| errors.value.amount = "وارد کردن مقدار تخفیف الزامی می باشد"; | |||
| if (!minOrder.value) | |||
| errors.value.minOrder = "وارد کردن حداقل میزان تخفیف الزامی می باشد"; | |||
| if ( | |||
| !selectedCat.value && | |||
| (whichPart.value === "cat" || whichPart.value === "both") | |||
| ) | |||
| if (!selectedCat.value && whichPart.value === "cat") | |||
| errors.value.selectedCat = "انتخاب دسته برای تخفیف الزامی می باشد"; | |||
| if ( | |||
| !selectedProduct.value && | |||
| (whichPart.value === "product" || whichPart.value === "both") | |||
| ) | |||
| if (!selectedProduct.value && whichPart.value === "product") | |||
| errors.value.selectedProduct = "انتخاب محصول برای تخفیف الزامی می باشد"; | |||
| if (!startDate.value) | |||
| errors.value.startDate = "انتخاب تاریخ اعمال تخفیف الزامی می باشد "; | |||
| if (!expire.value) | |||
| errors.value.expire = "انتخاب تاریخ انقضای تخفیف الزامی می باشد "; | |||
| if (!maxUsage.value) | |||
| errors.value.maxUsage = "مشخص کنید تخفیف چند بار مصرف است "; | |||
| if (!whichPart.value) | |||
| errors.value.whichPart = "مشخص کنید تخفیف بر چه بخشی اعمال شود"; | |||
| return Object.keys(errors.value).length === 0; | |||
| @@ -357,7 +389,7 @@ export default { | |||
| onMounted(() => { | |||
| getCats(); | |||
| getProduct(); | |||
| getDiscount(); | |||
| }); | |||
| @@ -375,16 +407,30 @@ export default { | |||
| formData.append("type", discountType.value); | |||
| formData.append("amount", amount.value); | |||
| formData.append("min_order", minOrder.value); | |||
| if (whichPart.value === "cat" || whichPart.value === "both") { | |||
| if (whichPart.value === "cat") { | |||
| formData.append("category_id", selectedCat.value); | |||
| } | |||
| if (whichPart.value === "product" || whichPart.value === "both") { | |||
| if (whichPart.value === "product") { | |||
| formData.append("product_id", selectedProduct.value); | |||
| } | |||
| formData.append("starts_at", startDate.value); | |||
| formData.append("expires_at", expire.value); | |||
| if (startDate.value) { | |||
| const georgianDate = moment( | |||
| startDate.value, | |||
| "jYYYY/jMM/jDD HH:mm:ss" | |||
| ).format("YYYY/MM/DD HH:mm:ss"); | |||
| formData.append("starts_at", georgianDate); | |||
| } | |||
| if (expire.value) { | |||
| const georgianDate = moment( | |||
| expire.value, | |||
| "jYYYY/jMM/jDD HH:mm:ss" | |||
| ).format("YYYY/MM/DD HH:mm:ss"); | |||
| formData.append("expires_at", georgianDate); | |||
| } | |||
| formData.append("max_usage", maxUsage.value); | |||
| ApiServiece.post(`/admin/discounts`, formData) | |||
| @@ -407,7 +453,7 @@ export default { | |||
| }; | |||
| return { | |||
| cats, | |||
| categories, | |||
| errors, | |||
| title, | |||
| products, | |||
| @@ -425,6 +471,10 @@ export default { | |||
| whichPart, | |||
| clearError, | |||
| loading, | |||
| formattedCategories, | |||
| formattedProducts, | |||
| handleProductSearch, | |||
| productSelectorLoader, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -34,18 +34,19 @@ export default { | |||
| const attributeTitle = ref(); | |||
| const attributeId = ref(); | |||
| const attrebuteCat = ref(); | |||
| const selectorLoader = ref(false); | |||
| const handleSearch = async (searchTerm) => { | |||
| if (searchTerm.length < 3) return; | |||
| selectorLoader.value = true; | |||
| try { | |||
| const response = await ApiServiece.get( | |||
| `admin/categories?title=${searchTerm}` | |||
| ); | |||
| categories.value = response.data.data; | |||
| console.log(categories.value, "products"); | |||
| selectorLoader.value = false; | |||
| } catch (error) { | |||
| console.error("Error fetching products:", error); | |||
| selectorLoader.value = false; | |||
| categories.value = []; | |||
| } | |||
| }; | |||
| @@ -231,6 +232,7 @@ export default { | |||
| formattedCategories, | |||
| handleSearch, | |||
| selectedcategory, | |||
| selectorLoader, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -255,10 +257,17 @@ export default { | |||
| <VueSelect | |||
| style="--vs-border-radius: 16px" | |||
| v-model="selectedcategory" | |||
| :isLoading="selectorLoader" | |||
| :options="formattedCategories" | |||
| placeholder="دسته ای را انتخاب کنید" | |||
| @search="handleSearch" | |||
| /> | |||
| > | |||
| <template #menu-header> | |||
| <div class="menu-header"> | |||
| <h3>دسته ها</h3> | |||
| </div> | |||
| </template> | |||
| </VueSelect> | |||
| </div> | |||
| <button | |||
| data-bs-toggle="modal" | |||
| @@ -500,4 +509,15 @@ export default { | |||
| cursor: pointer; | |||
| user-select: none; | |||
| } | |||
| .menu-header { | |||
| position: sticky; | |||
| top: 0; | |||
| padding: 0.5rem 1rem; | |||
| background-color: #f4f4f5; | |||
| } | |||
| .menu-header h3 { | |||
| margin: 0; | |||
| color: var(--vs-option-text-color); | |||
| } | |||
| </style> | |||
| @@ -13,6 +13,7 @@ export default { | |||
| DatePicker, | |||
| }, | |||
| setup() { | |||
| const selectorLoader = ref(false); | |||
| const isLoading = ref(false); | |||
| const date = ref([]); | |||
| const brands = ref([]); | |||
| @@ -25,6 +26,18 @@ export default { | |||
| const selectedBrand = ref(); | |||
| const allProducts = ref([]); | |||
| const getAllProducts = () => { | |||
| if (date.value[0]) { | |||
| date.value[0] = moment(date.value[0], "YYYY-MM-DD HH:mm:ss").format( | |||
| "YYYY/MM/DD HH:mm:ss" | |||
| ); | |||
| } | |||
| if (date.value[1]) { | |||
| date.value[1] = moment(date.value[1], "YYYY-MM-DD HH:mm:ss").format( | |||
| "YYYY/MM/DD HH:mm:ss" | |||
| ); | |||
| } | |||
| filterLoading.value = true; | |||
| ApiServiece.get( | |||
| `admin/orders/order-items/approved?brand_id=${ | |||
| @@ -46,10 +59,10 @@ export default { | |||
| }; | |||
| const convertToJalali = (date) => { | |||
| return moment(date, "YYYY-MM-DD HH:mm:ss") | |||
| .locale("fa") | |||
| .format("YYYY/MM/DD"); | |||
| }; | |||
| return moment(date, "YYYY-MM-DD HH:mm:ss") | |||
| .locale("fa") | |||
| .format("jYYYY/jMM/jDD HH:mm:ss"); | |||
| }; | |||
| const getFile = () => { | |||
| isLoading.value = true; | |||
| @@ -149,14 +162,15 @@ export default { | |||
| const handleBrandSearch = async (searchTerm) => { | |||
| if (searchTerm.length < 3) return; | |||
| selectorLoader.value = true; | |||
| try { | |||
| const response = await ApiServiece.get( | |||
| `admin/brands?title=${searchTerm}` | |||
| ); | |||
| brands.value = response.data.data; | |||
| selectorLoader.value = false; | |||
| } catch (error) { | |||
| console.error("Error fetching products:", error); | |||
| selectorLoader.value = false; | |||
| brands.value = []; | |||
| } | |||
| }; | |||
| @@ -193,6 +207,7 @@ export default { | |||
| formatWithCommas, | |||
| handleBrandSearch, | |||
| filterLoading, | |||
| selectorLoader, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -214,17 +229,17 @@ export default { | |||
| style="--vs-border-radius: 8px; min-width: 180px" | |||
| v-model="selectedBrand" | |||
| :options="formattedBrands" | |||
| :isLoading="selectorLoader" | |||
| placeholder="برندی را انتخاب کنید" | |||
| @search="handleBrandSearch" | |||
| /> | |||
| <!-- Date Picker with Proper Width --> | |||
| <DatePicker | |||
| format="YYYY/MM/DD HH:mm:ss" | |||
| type="date" | |||
| :format="'jYYYY/jMM/jDD HH:mm:ss'" | |||
| type="datetime" | |||
| :range="true" | |||
| v-model="date" | |||
| @input="handleInput" | |||
| class="custom-datepicker" | |||
| /> | |||
| </div> | |||
| @@ -82,6 +82,7 @@ export default { | |||
| shipping: "badge-shipping", | |||
| delivered: "badge-delivered", | |||
| canceled: "badge-canceled", | |||
| in_cart: "badge-in-cart", | |||
| }; | |||
| return statusClasses[status] || "badge-secondary"; | |||
| }; | |||
| @@ -96,6 +97,7 @@ export default { | |||
| shipping: "در حال ارسال", | |||
| delivered: "تحویلشده", | |||
| canceled: "لغوشده", | |||
| in_cart: "در سبد خرید", | |||
| }; | |||
| return statusLabels[status] || "نامشخص"; | |||
| }; | |||
| @@ -270,23 +272,29 @@ export default { | |||
| <table class="table table-hover table-bordered m-0" dir="rtl"> | |||
| <thead class="table-light"> | |||
| <tr> | |||
| <th>شناسه</th> | |||
| <th>وضعیت</th> | |||
| <th>قیمت</th> | |||
| <th>کاربر</th> | |||
| <th>کد پیگیری</th> | |||
| <th>تاریخ ایجاد</th> | |||
| <th>عملیات</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| <tr v-for="order in orders" :key="order.id"> | |||
| <td>{{ order?.id }}</td> | |||
| <td> | |||
| <span class="badge" :class="getStatusClass(order.status)"> | |||
| {{ getStatusLabel(order.status) }} | |||
| </span> | |||
| </td> | |||
| <td>{{ order?.total_price || "" }}</td> | |||
| <td>{{ order?.user?.mobile || "مهمان" }}</td> | |||
| <td>{{ order?.tracking_code || "" }}</td> | |||
| <td>{{ convertToJalali(order?.created_at) }}</td> | |||
| <td> | |||
| @@ -597,6 +605,9 @@ export default { | |||
| .badge-canceled { | |||
| background-color: #6c757d; | |||
| } | |||
| .badge-in-cart { | |||
| background-color: #0620c6; /* You can choose any color you prefer */ | |||
| } | |||
| .dropdown-item { | |||
| cursor: pointer; | |||
| } | |||
| @@ -235,7 +235,7 @@ | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="spescial" md="6"> | |||
| <BCol v-if="spescial == 1" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label"> تخفیف ویژه </label> | |||
| <input | |||
| @@ -252,7 +252,7 @@ | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="spescial" md="6"> | |||
| <BCol v-if="spescial == 1" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label"> | |||
| تاریخ انقضای تخفیف محصول ویژه | |||
| @@ -262,7 +262,6 @@ | |||
| :format="'jYYYY/jMM/jDD HH:mm:ss'" | |||
| type="datetime" | |||
| v-model="expire" | |||
| @input="handleInput" | |||
| ></DatePicker> | |||
| </div> | |||
| <small v-if="errors.expire" class="text-danger"> | |||
| @@ -270,7 +269,7 @@ | |||
| </small> | |||
| </BCol> | |||
| <BCol md="6"> | |||
| <!-- <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">دسته</label> | |||
| <select | |||
| @@ -288,26 +287,34 @@ | |||
| <small v-if="errors.selectedCat" class="text-danger"> | |||
| {{ errors.selectedCat }} | |||
| </small> | |||
| </BCol> --> | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">دسته</label> | |||
| <VueSelect | |||
| :isLoading="categorySelectorLoader" | |||
| v-model="selectedCat" | |||
| :options="formattedCategories" | |||
| placeholder="دسته ای را انتخاب کنید" | |||
| @search="handleSearch" | |||
| /> | |||
| </div> | |||
| <small v-if="errors.selectedCat" class="text-danger"> | |||
| {{ errors.selectedCat }} | |||
| </small> | |||
| </BCol> | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">برند</label> | |||
| <select | |||
| :class="{ 'is-invalid': errors.selectedBrand }" | |||
| <VueSelect | |||
| v-model="selectedBrand" | |||
| class="form-control" | |||
| @change="clearError('selectedBrand')" | |||
| placeholder="انتخاب برند محصول" | |||
| > | |||
| <option | |||
| v-for="brand in brands" | |||
| :key="brand.id" | |||
| :value="brand.id" | |||
| > | |||
| {{ brand.title }} | |||
| </option> | |||
| </select> | |||
| :isLoading="brandSelectorLoader" | |||
| :options="formattedBrands" | |||
| placeholder="برندی را انتخاب کنید" | |||
| @search="handleBrandSearch" | |||
| /> | |||
| </div> | |||
| <small v-if="errors.selectedBrand" class="text-danger"> | |||
| {{ errors.selectedBrand }} | |||
| @@ -549,7 +556,10 @@ | |||
| </div> | |||
| </div> | |||
| </BCardFooter> | |||
| <addIdentity :cats="cats" @identity-updated="handleIdentityUpdate" /> | |||
| <addIdentity | |||
| :cats="categories" | |||
| @identity-updated="handleIdentityUpdate" | |||
| /> | |||
| <addAttribute | |||
| :attributeValues="attributeValues" | |||
| @attribute-updated="handleAttributeUpdated()" | |||
| @@ -561,15 +571,17 @@ | |||
| </template> | |||
| <script> | |||
| import VueSelect from "vue3-select-component"; | |||
| import addAttribute from "@/components/modals/attribute/addAttribute.vue"; | |||
| import moment from "moment"; | |||
| import moment from "jalali-moment"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import { ref, onMounted, watch } from "vue"; | |||
| import { ref, onMounted, watch, computed } from "vue"; | |||
| import Layout from "@/layout/custom.vue"; | |||
| import DatePicker from "vue3-persian-datetime-picker"; | |||
| import addIdentity from "@/components/modals/identity/addIdentity.vue"; | |||
| export default { | |||
| name: "SAMPLE-PAGE", | |||
| components: { | |||
| @@ -577,8 +589,12 @@ export default { | |||
| DatePicker, | |||
| addIdentity, | |||
| addAttribute, | |||
| VueSelect, | |||
| }, | |||
| setup() { | |||
| const categorySelectorLoader = ref(false); | |||
| const categories = ref([]); | |||
| const brandSelectorLoader = ref(false); | |||
| const attributeValues = ref(); | |||
| const relatedAttrebutes = ref([]); | |||
| const countInCarton = ref(); | |||
| @@ -610,18 +626,8 @@ export default { | |||
| const blogCat = ref(); | |||
| const author = ref(""); | |||
| const editor = ref(null); | |||
| const cats = ref([]); | |||
| const editorContent = ref(""); | |||
| const getCats = () => { | |||
| ApiServiece.get(`admin/categories`) | |||
| .then((resp) => { | |||
| cats.value = resp.data.data; | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| }); | |||
| }; | |||
| const editorContent = ref(""); | |||
| const getAttributeValues = () => { | |||
| ApiServiece.get(`admin/attributes`).then((resp) => { | |||
| @@ -656,28 +662,54 @@ export default { | |||
| }); | |||
| }; | |||
| const handleInput = () => { | |||
| if (expire.value) { | |||
| // Convert from Jalali to Georgian (Gregorian) | |||
| expire.value = moment(expire.value, "jYYYY/jMM/jDD HH:mm:ss").format( | |||
| "YYYY-MM-DD HH:mm:ss" | |||
| const handleBrandSearch = async (searchTerm) => { | |||
| if (searchTerm.length < 3) return; | |||
| brandSelectorLoader.value = true; | |||
| try { | |||
| const response = await ApiServiece.get( | |||
| `admin/brands?title=${searchTerm}` | |||
| ); | |||
| } else { | |||
| expire.value = null; | |||
| clearError("expire"); | |||
| brands.value = response.data.data; | |||
| brandSelectorLoader.value = false; | |||
| } catch (error) { | |||
| brandSelectorLoader.value = false; | |||
| brands.value = []; | |||
| } | |||
| }; | |||
| const getBrands = () => { | |||
| ApiServiece.get(`admin/brands`) | |||
| .then((resp) => { | |||
| brands.value = resp.data.data; | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| }); | |||
| const formattedBrands = computed(() => | |||
| Array.isArray(brands.value) | |||
| ? brands.value.map((brand) => ({ | |||
| value: brand.id, | |||
| label: brand.title, | |||
| })) | |||
| : [] | |||
| ); | |||
| const handleSearch = async (searchTerm) => { | |||
| if (searchTerm.length < 3) return; | |||
| categorySelectorLoader.value = true; | |||
| try { | |||
| const response = await ApiServiece.get( | |||
| `admin/categories?title=${searchTerm}` | |||
| ); | |||
| categories.value = response.data.data; | |||
| categorySelectorLoader.value = false; | |||
| } catch (error) { | |||
| categorySelectorLoader.value = false; | |||
| categories.value = []; | |||
| } | |||
| }; | |||
| const formattedCategories = computed(() => | |||
| Array.isArray(categories.value) | |||
| ? categories.value.map((category) => ({ | |||
| value: category.id, | |||
| label: category.title, | |||
| })) | |||
| : [] | |||
| ); | |||
| const getAttrebuteValues = () => { | |||
| ApiServiece.get(`admin/attribute-values?attribute_id=1`) | |||
| .then((resp) => { | |||
| @@ -815,18 +847,12 @@ export default { | |||
| errors.value[field] = ""; | |||
| }; | |||
| onMounted(() => { | |||
| getCats(); | |||
| getBrands(); | |||
| getAttrebuteValues(); | |||
| getAttributeValues(); | |||
| }); | |||
| let id = ""; | |||
| const submitForm = () => { | |||
| console.log(expire.value , "test") | |||
| if (!validateForm()) { | |||
| toast.error("لطفا فیلد های لازم را وارد نمایید", { | |||
| position: "top-right", | |||
| @@ -842,6 +868,8 @@ export default { | |||
| formData.append("slug", slug.value); | |||
| formData.append("summary", summary.value); | |||
| formData.append("description", description.value); | |||
| formData.append("is_special", spescial.value); | |||
| formData.append("is_chosen", isChosen.value); | |||
| if (productType.value == 2) { | |||
| formData.append("wholesale_price", wholesalePrice.value); | |||
| } | |||
| @@ -858,31 +886,24 @@ export default { | |||
| formData.append("chosen_price", chosenPrice.value); | |||
| } | |||
| if (isChosen.value == 1 && spescial.value == 0) { | |||
| formData.append("is_chosen", isChosen.value); | |||
| } | |||
| if (isChosen.value == 0 && spescial.value == 1) { | |||
| formData.append("is_chosen", isChosen.value); | |||
| } | |||
| if (spescial.value == 1 && isChosen.value == 0) { | |||
| formData.append("special_price", parseInt(spescialPrice.value, 10)); | |||
| formData.append("special_expires_at", expire.value); | |||
| } | |||
| if (spescial.value == 1 && isChosen.value == 0) { | |||
| formData.append("is_special", spescial.value); | |||
| } | |||
| if (spescial.value == 0 && isChosen.value == 1) { | |||
| formData.append("is_special", spescial.value); | |||
| // Convert Jalali to Gregorian before sending | |||
| const georgianDate = moment( | |||
| expire.value, | |||
| "jYYYY/jMM/jDD HH:mm:ss" | |||
| ).format("YYYY/MM/DD HH:mm:ss"); | |||
| formData.append("special_expires_at", georgianDate); | |||
| } | |||
| formData.append("brand_id", selectedBrand.value); | |||
| formData.append("category_id", selectedCat.value); | |||
| formData.append("count_in_carton", countInCarton.value); | |||
| if (productType.value === "2" || productType.value === "3") { | |||
| formData.append("count_in_carton", countInCarton.value); | |||
| } | |||
| formData.append("image", image.value); | |||
| ApiServiece.post(`admin/products`, formData, { | |||
| @@ -892,76 +913,98 @@ export default { | |||
| }, | |||
| }) | |||
| .then((resp) => { | |||
| id = resp.data.data.id; | |||
| console.log(id); | |||
| const id = resp.data.data.id; | |||
| // 🔸 Call attributes API if needed | |||
| selectedAttributes.value = attrebutes.value | |||
| .filter((attribute) => attribute.isChecked) | |||
| .filter((attribute) => attribute.isChecked && attribute.value) | |||
| .map((attribute) => ({ | |||
| attribute_value_id: attribute.id, | |||
| inventory: attribute.value, | |||
| type: productType.value, | |||
| })); | |||
| const finalPayload = { | |||
| productAttributes: selectedAttributes.value, | |||
| }; | |||
| const jsonString = JSON.stringify(finalPayload, null, 2); | |||
| console.log(jsonString); | |||
| ApiServiece.post(`admin/products/${id}/attributes`, jsonString) | |||
| .then(() => { | |||
| selectedIdentities.value = relatedAttrebutes.value | |||
| .filter((identity) => identity.isChecked) | |||
| .map((identity) => ({ | |||
| attribute_id: identity.id, | |||
| attribute_value_title: identity.value, | |||
| })); | |||
| const finalPayload = { | |||
| productSolidAttributes: selectedIdentities.value, | |||
| }; | |||
| const jsonString = JSON.stringify(finalPayload, null, 2); | |||
| ApiServiece.post( | |||
| `admin/products/${id}/solid-attributes`, | |||
| jsonString | |||
| ).then((resp) => { | |||
| console.log(resp); | |||
| }); | |||
| }) | |||
| .then((resp) => { | |||
| console.log(resp); | |||
| images.value.map((image) => { | |||
| console.log(image.file); | |||
| const formData = new FormData(); | |||
| formData.append("image", image.file); | |||
| ApiServiece.post(`admin/products/${id}/images`, formData, { | |||
| headers: { | |||
| "content-type": "multipart", | |||
| Authorization: `Bearer ${localStorage.getItem("token")}`, | |||
| }, | |||
| }); | |||
| if (selectedAttributes.value.length > 0) { | |||
| const json = JSON.stringify( | |||
| { productAttributes: selectedAttributes.value }, | |||
| null, | |||
| 2 | |||
| ); | |||
| ApiServiece.post(`admin/products/${id}/attributes`, json); | |||
| } | |||
| // 🔸 Call solid-attributes API if needed | |||
| selectedIdentities.value = relatedAttrebutes.value | |||
| .filter((identity) => identity.isChecked) | |||
| .map((identity) => ({ | |||
| attribute_id: identity.id, | |||
| attribute_value_title: identity.value, | |||
| })); | |||
| if (selectedIdentities.value.length > 0) { | |||
| const json = JSON.stringify( | |||
| { productSolidAttributes: selectedIdentities.value }, | |||
| null, | |||
| 2 | |||
| ); | |||
| ApiServiece.post(`admin/products/${id}/solid-attributes`, json); | |||
| } | |||
| // 🔸 Upload images if any | |||
| const validImages = images.value.filter((image) => image.file); | |||
| if (validImages.length > 0) { | |||
| console.log(validImages, "valid images"); | |||
| validImages.forEach((image) => { | |||
| const formData = new FormData(); | |||
| formData.append("image", image.file); | |||
| ApiServiece.post(`admin/products/${id}/images`, formData, { | |||
| headers: { | |||
| "content-type": "multipart", | |||
| Authorization: `Bearer ${localStorage.getItem("token")}`, | |||
| }, | |||
| }); | |||
| }); | |||
| }) | |||
| } | |||
| .then(() => { | |||
| loading.value = false; | |||
| toast.success("!محصول با موفقیت اضافه شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .catch((error) => { | |||
| console.error(error); | |||
| loading.value = false; | |||
| toast.error(`${error?.response?.data?.message}`, { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .finally(() => { | |||
| loading.value = false; | |||
| title.value = ""; | |||
| imagePreview.value = null; | |||
| slug.value = ""; | |||
| summary.value = ""; | |||
| description.value = ""; | |||
| spescial.value = 0; | |||
| isChosen.value = 0; | |||
| wholesalePrice.value = ""; | |||
| retailePrice.value = ""; | |||
| productType.value = ""; | |||
| chosenPrice.value = ""; | |||
| spescialPrice.value = ""; | |||
| expire.value = ""; | |||
| selectedBrand.value = null; | |||
| selectedCat.value = null; | |||
| countInCarton.value = ""; | |||
| image.value = null; | |||
| images.value = []; | |||
| attrebutes.value.forEach((attribute) => { | |||
| attribute.isChecked = false; | |||
| attribute.value = ""; | |||
| }); | |||
| relatedAttrebutes.value.forEach((identity) => { | |||
| identity.isChecked = false; | |||
| }); | |||
| }); | |||
| }; | |||
| @@ -970,7 +1013,6 @@ export default { | |||
| slug, | |||
| summary, | |||
| editor, | |||
| cats, | |||
| errors, | |||
| image, | |||
| imagePreview, | |||
| @@ -1001,13 +1043,18 @@ export default { | |||
| spescialPrice, | |||
| chosenPrice, | |||
| expire, | |||
| handleInput, | |||
| countInCarton, | |||
| relatedAttrebutes, | |||
| selectedIdentities, | |||
| attributeValues, | |||
| handleAttributeUpdated, | |||
| handleIdentityUpdate, | |||
| handleBrandSearch, | |||
| brandSelectorLoader, | |||
| formattedBrands, | |||
| handleSearch, | |||
| formattedCategories, | |||
| categorySelectorLoader, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -140,7 +140,7 @@ | |||
| </small> | |||
| </BCol> | |||
| <BCol md="6"> | |||
| <BCol md="6" v-if="productType != 1"> | |||
| <div class="form-group"> | |||
| <label class="form-label">تعداد در کارتن</label> | |||
| <input | |||
| @@ -229,7 +229,7 @@ | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="spescial" md="6"> | |||
| <BCol v-if="spescial == 1" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label"> تخفیف ویژه </label> | |||
| <input | |||
| @@ -246,7 +246,7 @@ | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="spescial" md="6"> | |||
| <BCol v-if="spescial == 1" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label"> | |||
| تاریخ انقضای تخفیف محصول ویژه | |||
| @@ -263,7 +263,7 @@ | |||
| </small> | |||
| </BCol> | |||
| <BCol md="6"> | |||
| <!-- <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">دسته</label> | |||
| <select | |||
| @@ -281,43 +281,40 @@ | |||
| <small v-if="errors.blogCat" class="text-danger"> | |||
| {{ errors.blogCat }} | |||
| </small> | |||
| </BCol> --> | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">دسته</label> | |||
| <VueSelect | |||
| :isLoading="categorySelectorLoader" | |||
| v-model="selectedCat" | |||
| :options="formattedCategories" | |||
| placeholder="دسته ای را انتخاب کنید" | |||
| /> | |||
| </div> | |||
| <small v-if="errors.selectedCat" class="text-danger"> | |||
| {{ errors.selectedCat }} | |||
| </small> | |||
| </BCol> | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">برند</label> | |||
| <select | |||
| :class="{ 'is-invalid': errors.selectedBrand }" | |||
| <VueSelect | |||
| v-model="selectedBrand" | |||
| class="form-control" | |||
| @select="clearError('selectedBrand')" | |||
| placeholder="انتخاب برند محصول" | |||
| > | |||
| <option | |||
| v-for="brand in brands" | |||
| :key="brand.id" | |||
| :value="brand.id" | |||
| > | |||
| {{ brand.title }} | |||
| </option> | |||
| </select> | |||
| :isLoading="brandSelectorLoader" | |||
| :options="formattedBrands" | |||
| placeholder="برندی را انتخاب کنید" | |||
| @search="handleBrandSearch" | |||
| /> | |||
| </div> | |||
| <small v-if="errors.selectedBrand" class="text-danger"> | |||
| {{ errors.selectedBrand }} | |||
| </small> | |||
| </BCol> | |||
| <BCard> | |||
| <div class="card-header"> | |||
| <h5 class="mb-0">ویرایش ویژگی ها</h5> | |||
| </div> | |||
| <template v-if="locals.length === 0"> | |||
| <BCol> | |||
| <div class="alert alert-info text-center"> | |||
| هیچ ویژگی برای این محصول انتخاب نکرده اید ... | |||
| </div> | |||
| </BCol> | |||
| </template> | |||
| <BCard v-if="locals.length !== 0"> | |||
| <BRow class="g-3 mt-2"> | |||
| <!-- Loop through attributes --> | |||
| <BCol | |||
| @@ -410,18 +407,11 @@ | |||
| </BRow> | |||
| </BCard> | |||
| <BCard> | |||
| <BCard v-if="localIdentities.length !== 0"> | |||
| <div class="card-header"> | |||
| <h5 class="mb-0">ویرایش مشخصه ها</h5> | |||
| </div> | |||
| <template v-if="localIdentities.length === 0"> | |||
| <BCol> | |||
| <div class="alert alert-info text-center"> | |||
| برای شما مشخصهای ثبت نشده است. برای ساختن یک مشخصه کلیک | |||
| کنید. | |||
| </div> | |||
| </BCol> | |||
| </template> | |||
| <BRow class="g-3 mt-2"> | |||
| <!-- Loop through attributes --> | |||
| <BCol | |||
| @@ -802,7 +792,7 @@ | |||
| </div> | |||
| </div> | |||
| <addIdentity | |||
| :cats="cats" | |||
| :cats="categories" | |||
| @attribute-updated="handleAttributeUpdated()" | |||
| /> | |||
| <addAttribute @attribute-updated="handleAttributeUpdated()" /> | |||
| @@ -814,13 +804,14 @@ | |||
| </template> | |||
| <script> | |||
| import VueSelect from "vue3-select-component"; | |||
| import Swal from "sweetalert2"; | |||
| import { useRoute } from "vue-router"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import moment from "moment"; | |||
| import moment from "jalali-moment"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import { ref, onMounted, watch } from "vue"; | |||
| import { ref, onMounted, watch, computed } from "vue"; | |||
| import Layout from "@/layout/custom.vue"; | |||
| import addIdentity from "@/components/modals/identity/addIdentity.vue"; | |||
| import addAttribute from "@/components/modals/attribute/addAttribute.vue"; | |||
| @@ -833,6 +824,7 @@ export default { | |||
| DatePicker, | |||
| addIdentity, | |||
| addAttribute, | |||
| VueSelect, | |||
| }, | |||
| setup() { | |||
| const locals = ref([ | |||
| @@ -885,19 +877,45 @@ export default { | |||
| const blogCat = ref(); | |||
| const author = ref(""); | |||
| const editor = ref(null); | |||
| const cats = ref([]); | |||
| const categories = ref([]); | |||
| const editorContent = ref(""); | |||
| const categorySelectorLoader = ref(false); | |||
| const brandSelectorLoader = ref(false); | |||
| const getCats = () => { | |||
| ApiServiece.get(`admin/categories`) | |||
| .then((resp) => { | |||
| cats.value = resp.data.data; | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| }); | |||
| ApiServiece.get("admin/categories").then((resp) => { | |||
| categories.value = resp.data.data; | |||
| }); | |||
| console.log(categories.value, "cats"); | |||
| }; | |||
| const getBrands = () => { | |||
| ApiServiece.get("admin/brands").then((resp) => { | |||
| brands.value = resp.data.data; | |||
| }); | |||
| console.log(brands.value, "brands"); | |||
| }; | |||
| const formattedCategories = computed(() => | |||
| Array.isArray(categories.value) | |||
| ? categories.value.map((category) => ({ | |||
| value: category.id, | |||
| label: category.title, | |||
| })) | |||
| : [] | |||
| ); | |||
| const formattedBrands = computed(() => | |||
| Array.isArray(brands.value) | |||
| ? brands.value.map((brand) => ({ | |||
| value: brand.id, | |||
| label: brand.title, | |||
| })) | |||
| : [] | |||
| ); | |||
| watch(selectedCat, () => { | |||
| ApiServiece.get(`admin/attributes?category_id=${selectedCat.value}`) | |||
| .then((resp) => { | |||
| @@ -937,43 +955,17 @@ export default { | |||
| }); | |||
| }; | |||
| const convertToGeorgian = (date) => { | |||
| return moment(date, "jYYYY/jMM/jDD HH:mm:ss").format( | |||
| "YYYY-MM-DD HH:mm:ss" | |||
| ); | |||
| }; | |||
| watch(expire, (newValue) => { | |||
| if (newValue) { | |||
| // Convert from Jalali to Georgian (Gregorian) format when `expire` changes | |||
| expire.value = convertToGeorgian(newValue); | |||
| console.log(expire.value); // Logs the Georgian format for debugging | |||
| } | |||
| }); | |||
| const getBrands = () => { | |||
| ApiServiece.get(`admin/brands`) | |||
| .then((resp) => { | |||
| brands.value = resp.data.data; | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| }); | |||
| }; | |||
| const getAttrebuteValues = () => { | |||
| ApiServiece.get(`admin/attribute-values?attribute_id=1`) | |||
| .then((resp) => { | |||
| console.log(resp); | |||
| console.log(resp, "attrebute values"); | |||
| attrebutes.value = resp.data.data; | |||
| console.log("Attributes before filtering:", attrebutes.value); | |||
| }) | |||
| .then(() => { | |||
| getProduct(); | |||
| }) | |||
| .then(() => { | |||
| console.log("Locals after processing:", locals.value); | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| }); | |||
| @@ -1180,12 +1172,12 @@ export default { | |||
| if (!selectedBrand.value) | |||
| errors.value.selectedBrand = "انتخاب برند برای محصول ضروری می باشد"; | |||
| if (!countInCarton.value) | |||
| if (productType.value != 1 && !countInCarton.value) | |||
| errors.value.countInCarton = | |||
| "انتخاب تعداد محصول در هر کارتن ضروری می باشد"; | |||
| if (images.value.length <= 0) | |||
| errors.value.images = "انتخاب عکس برای محصول ضروری می باشد"; | |||
| // if (images.value.length <= 0) | |||
| // errors.value.images = "انتخاب عکس برای محصول ضروری می باشد"; | |||
| const missingInventory = attrebutes.value.filter( | |||
| (attribute) => | |||
| @@ -1241,9 +1233,15 @@ export default { | |||
| chosenPrice.value = product.value?.chosen_price; | |||
| spescial.value = product.value?.is_special; | |||
| spescialPrice.value = product.value?.special_price; | |||
| expire.value = product.value?.special_expires_at; | |||
| if (product.value?.special_expires_at) { | |||
| expire.value = moment( | |||
| product.value.special_expires_at, | |||
| "YYYY-MM-DD HH:mm:ss" | |||
| ).format("jYYYY/jMM/jDD HH:mm:ss"); | |||
| } | |||
| selectedBrand.value = product.value?.brand_id; | |||
| selectedCat.value = product.value?.category_id; | |||
| countInCarton.value = product.value?.count_in_carton; | |||
| // Update images | |||
| @@ -1255,8 +1253,9 @@ export default { | |||
| preview: imageUrl, | |||
| })); | |||
| locals.value = product.value.product_attributes.map( | |||
| locals.value = product.value.attribute_value_products.map( | |||
| (productAttribute) => { | |||
| console.log(locals.value, "locals"); | |||
| return { | |||
| id: productAttribute.id, | |||
| title: productAttribute.attribute_value.title, | |||
| @@ -1283,21 +1282,18 @@ export default { | |||
| repeatedAttrebute.value = hasRepeatedAttribute; | |||
| console.log(attrebutes.value); | |||
| console.log(attrebutes.value); | |||
| localIdentities.value = product.value.product_solid_attributes.map( | |||
| (productIdentities) => { | |||
| return { | |||
| id: productIdentities.id, | |||
| title: productIdentities.attribute_value.attribute.title, | |||
| isChecked: true, | |||
| value: productIdentities.attribute_value.title, | |||
| attribute_value_id: productIdentities.attribute_value_id, | |||
| }; | |||
| } | |||
| ); | |||
| localIdentities.value = | |||
| product.value.solid_attribute_value_products.map( | |||
| (productIdentities) => { | |||
| return { | |||
| id: productIdentities.id, | |||
| title: productIdentities.attribute_value.attribute.title, | |||
| isChecked: true, | |||
| value: productIdentities.attribute_value.title, | |||
| attribute_value_id: productIdentities.attribute_value_id, | |||
| }; | |||
| } | |||
| ); | |||
| }) | |||
| .then(() => { | |||
| getIdentities(); | |||
| @@ -1376,14 +1372,12 @@ export default { | |||
| }; | |||
| onMounted(() => { | |||
| getAttrebuteValues(); | |||
| getCats(); | |||
| getBrands(); | |||
| getAttrebuteValues(); | |||
| }); | |||
| let id = ""; | |||
| const submitForm = () => { | |||
| if (!validateForm()) { | |||
| toast.error("لطفا فیلد های لازم را وارد نمایید", { | |||
| position: "top-right", | |||
| @@ -1400,7 +1394,9 @@ export default { | |||
| formData.append("slug", slug.value); | |||
| formData.append("summary", summary.value); | |||
| formData.append("description", description.value); | |||
| formData.append("count_in_carton", countInCarton.value); | |||
| if (productType.value === "2" || productType.value === "3") { | |||
| formData.append("count_in_carton", countInCarton.value); | |||
| } | |||
| if (productType.value == 2) { | |||
| formData.append("wholesale_price", wholesalePrice.value); | |||
| } | |||
| @@ -1426,8 +1422,18 @@ export default { | |||
| } | |||
| if (spescial.value == 1 && isChosen.value == 0) { | |||
| formData.append("special_price", parseInt(spescialPrice.value, 10)); | |||
| formData.append("special_expires_at", expire.value); | |||
| if (spescialPrice.value) { | |||
| formData.append("special_price", parseInt(spescialPrice.value, 10)); | |||
| } | |||
| if (expire.value) { | |||
| const georgianDate = moment( | |||
| expire.value, | |||
| "jYYYY/jMM/jDD HH:mm:ss" | |||
| ).format("YYYY/MM/DD HH:mm:ss"); | |||
| formData.append("special_expires_at", georgianDate); | |||
| } | |||
| } | |||
| if (spescial.value == 1 && isChosen.value == 0) { | |||
| @@ -1562,7 +1568,7 @@ export default { | |||
| slug, | |||
| summary, | |||
| editor, | |||
| cats, | |||
| categories, | |||
| errors, | |||
| image, | |||
| imagePreview, | |||
| @@ -1607,6 +1613,10 @@ export default { | |||
| editIdentity, | |||
| repeatedIdentity, | |||
| repeatedAttrebute, | |||
| categorySelectorLoader, | |||
| formattedCategories, | |||
| formattedBrands, | |||
| brandSelectorLoader, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -15,6 +15,8 @@ export default { | |||
| VueSelect, | |||
| }, | |||
| setup() { | |||
| const brandSelectorLoader = ref(false); | |||
| const categorySelectorLoader = ref(false); | |||
| let searchTimeout = null; | |||
| const selectedProductType = ref(""); | |||
| const categories = ref([]); | |||
| @@ -208,15 +210,15 @@ export default { | |||
| const handleSearch = async (searchTerm) => { | |||
| if (searchTerm.length < 3) return; | |||
| categorySelectorLoader.value = true; | |||
| try { | |||
| const response = await ApiServiece.get( | |||
| `admin/categories?title=${searchTerm}` | |||
| ); | |||
| categories.value = response.data.data; | |||
| console.log(categories.value, "products"); | |||
| categorySelectorLoader.value = false; | |||
| } catch (error) { | |||
| console.error("Error fetching products:", error); | |||
| categorySelectorLoader.value = false; | |||
| categories.value = []; | |||
| } | |||
| }; | |||
| @@ -232,14 +234,15 @@ export default { | |||
| const handleBrandSearch = async (searchTerm) => { | |||
| if (searchTerm.length < 3) return; | |||
| brandSelectorLoader.value = true; | |||
| try { | |||
| const response = await ApiServiece.get( | |||
| `admin/brands?title=${searchTerm}` | |||
| ); | |||
| brands.value = response.data.data; | |||
| brandSelectorLoader.value = false; | |||
| } catch (error) { | |||
| console.error("Error fetching products:", error); | |||
| brandSelectorLoader.value = false; | |||
| brands.value = []; | |||
| } | |||
| }; | |||
| @@ -279,6 +282,8 @@ export default { | |||
| formattedBrands, | |||
| handleBrandSearch, | |||
| selectedBrand, | |||
| categorySelectorLoader, | |||
| brandSelectorLoader, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -317,6 +322,7 @@ export default { | |||
| --vs-min-height: 18px; | |||
| margin-right: 7px; | |||
| " | |||
| :isLoading="categorySelectorLoader" | |||
| v-model="selectedcategory" | |||
| :options="formattedCategories" | |||
| placeholder="دسته ای را انتخاب کنید" | |||
| @@ -330,6 +336,7 @@ export default { | |||
| margin-right: 7px; | |||
| " | |||
| v-model="selectedBrand" | |||
| :isLoading="brandSelectorLoader" | |||
| :options="formattedBrands" | |||
| placeholder="برندی را انتخاب کنید" | |||
| @search="handleBrandSearch" | |||
| @@ -638,8 +645,6 @@ export default { | |||
| font-weight: bold; | |||
| } | |||
| .Product-Image { | |||
| width: 50px; | |||
| height: 50px; | |||
| @@ -681,8 +686,6 @@ export default { | |||
| color: #fff; | |||
| } | |||
| td, | |||
| table[dir="rtl"] td, | |||
| table[dir="rtl"] th { | |||