| @@ -1 +1,2 @@ | |||
| VUE_APP_ROOT_URL="http://192.168.1.198:8000/api/v1/" | |||
| VUE_APP_ROOT_URL="https://api.novinplast.org/api/v1/" | |||
| COLOR_ATTRIBUTE_ID = "1" | |||
| @@ -39,6 +39,7 @@ | |||
| "form-wizard-vue3": "^1.1.0", | |||
| "fslightbox": "^3.4.1", | |||
| "is": "^3.3.0", | |||
| "jalaali-js": "^1.2.7", | |||
| "jalali-moment": "^3.3.11", | |||
| "jquery": "^3.7.1", | |||
| "moment": "^2.30.1", | |||
| @@ -64,6 +65,7 @@ | |||
| "vue3-datepicker": "^0.4.0", | |||
| "vue3-google-map": "^0.18.0", | |||
| "vue3-persian-datetime-picker": "^1.2.2", | |||
| "vue3-select2-component": "^0.1.7", | |||
| "vue3-toastify": "^0.2.5", | |||
| "vuex": "^4.1.0", | |||
| "yarn": "^1.22.21" | |||
| @@ -11982,6 +11984,12 @@ | |||
| "dev": true, | |||
| "license": "MIT" | |||
| }, | |||
| "node_modules/select2": { | |||
| "version": "4.0.13", | |||
| "resolved": "https://registry.npmjs.org/select2/-/select2-4.0.13.tgz", | |||
| "integrity": "sha512-1JeB87s6oN/TDxQQYCvS5EFoQyvV6eYMZZ0AeA4tdFDYWN3BAGZ8npr17UBFddU0lgAt3H0yjX3X6/ekOj1yjw==", | |||
| "license": "MIT" | |||
| }, | |||
| "node_modules/selfsigned": { | |||
| "version": "2.4.1", | |||
| "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", | |||
| @@ -13689,6 +13697,16 @@ | |||
| "moment-jalaali": "^0.9.4" | |||
| } | |||
| }, | |||
| "node_modules/vue3-select2-component": { | |||
| "version": "0.1.7", | |||
| "resolved": "https://registry.npmjs.org/vue3-select2-component/-/vue3-select2-component-0.1.7.tgz", | |||
| "integrity": "sha512-8UPZmFl02I47XwW+j8ICmHyAND8wfbavlvMrqh10wwdSVER1aF2kI9XCmfIrNlVQvxSbvc0mJjWQAHHSCW9dkw==", | |||
| "license": "MIT", | |||
| "dependencies": { | |||
| "jquery": "^3.3.1", | |||
| "select2": "^4.0.7-rc.0" | |||
| } | |||
| }, | |||
| "node_modules/vue3-toastify": { | |||
| "version": "0.2.5", | |||
| "resolved": "https://registry.npmjs.org/vue3-toastify/-/vue3-toastify-0.2.5.tgz", | |||
| @@ -39,6 +39,7 @@ | |||
| "form-wizard-vue3": "^1.1.0", | |||
| "fslightbox": "^3.4.1", | |||
| "is": "^3.3.0", | |||
| "jalaali-js": "^1.2.7", | |||
| "jalali-moment": "^3.3.11", | |||
| "jquery": "^3.7.1", | |||
| "moment": "^2.30.1", | |||
| @@ -64,6 +65,7 @@ | |||
| "vue3-datepicker": "^0.4.0", | |||
| "vue3-google-map": "^0.18.0", | |||
| "vue3-persian-datetime-picker": "^1.2.2", | |||
| "vue3-select2-component": "^0.1.7", | |||
| "vue3-toastify": "^0.2.5", | |||
| "vuex": "^4.1.0", | |||
| "yarn": "^1.22.21" | |||
| @@ -30,7 +30,6 @@ export default { | |||
| const logoutUser = async () => { | |||
| try { | |||
| const result = await Swal.fire({ | |||
| title: "آیا مطمئن هستید؟", | |||
| text: "شما از سیستم خارج خواهید شد.", | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| @@ -51,6 +50,10 @@ export default { | |||
| } | |||
| }; | |||
| const gotoAccount = () => { | |||
| router.push({ name: "profile" }); | |||
| }; | |||
| onMounted(() => { | |||
| updateLogo(); | |||
| @@ -68,7 +71,7 @@ export default { | |||
| }); | |||
| }); | |||
| return { currentLogo, user, logoutUser }; | |||
| return { currentLogo, user, logoutUser, gotoAccount }; | |||
| }, | |||
| components: { | |||
| ChevronDownIcon, | |||
| @@ -134,7 +137,6 @@ export default { | |||
| let collapses = document.querySelectorAll(".navbar-content .collapse"); | |||
| collapses.forEach((collapse) => { | |||
| // Hide sibling collapses on `show.bs.collapse` | |||
| collapse.addEventListener("show.bs.collapse", (e) => { | |||
| e.stopPropagation(); | |||
| let closestCollapse = collapse.parentElement.closest(".collapse"); | |||
| @@ -246,6 +248,17 @@ export default { | |||
| <span class="pc-mtext">کاربران</span></router-link | |||
| > | |||
| </li> | |||
| <li | |||
| class="pc-item" | |||
| :class="{ active: this.$route.path === '/banners' }" | |||
| > | |||
| <router-link to="/banners" class="pc-link"> | |||
| <span class="pc-micon"> | |||
| <i class="ph-duotone ph-flag"></i> | |||
| </span> | |||
| <span class="pc-mtext">بنر ها</span></router-link | |||
| > | |||
| </li> | |||
| <li class="pc-item" :class="{ active: this.$route.path === '/brands' }"> | |||
| <router-link to="/brands" class="pc-link"> | |||
| <span class="pc-micon"> | |||
| @@ -266,6 +279,17 @@ export default { | |||
| <span class="pc-mtext">ویژگی ها</span></router-link | |||
| > | |||
| </li> | |||
| <li | |||
| class="pc-item" | |||
| :class="{ active: this.$route.path === '/idenities' }" | |||
| > | |||
| <router-link to="/idenities" class="pc-link"> | |||
| <span class="pc-micon"> | |||
| <i class="ph-duotone ph-barcode"></i> | |||
| </span> | |||
| <span class="pc-mtext">مشخصات</span></router-link | |||
| > | |||
| </li> | |||
| <li class="pc-item" :class="{ active: this.$route.path === '/blogs' }"> | |||
| <router-link to="/blogs" class="pc-link"> | |||
| <span class="pc-micon"> | |||
| @@ -296,13 +320,44 @@ export default { | |||
| <span class="pc-mtext">تخفیف ها</span></router-link | |||
| > | |||
| </li> | |||
| <li class="pc-item" :class="{ active: this.$route.path === '/orders' }"> | |||
| <router-link to="/orders" class="pc-link"> | |||
| <li class="pc-item pc-hasmenu"> | |||
| <BLink | |||
| class="pc-link" | |||
| data-bs-toggle="collapse" | |||
| href="#collapse26" | |||
| role="button" | |||
| aria-expanded="false" | |||
| aria-controls="collapse26" | |||
| > | |||
| <span class="pc-micon"> | |||
| <i class="ph-duotone ph-shopping-cart"></i> | |||
| </span> | |||
| <span class="pc-mtext">سفارشات</span></router-link | |||
| > | |||
| <span class="pc-mtext">سفارشات</span | |||
| ><span class="pc-arrow"> | |||
| <ChevronDownIcon></ChevronDownIcon> | |||
| </span> | |||
| </BLink> | |||
| <div class="collapse" id="collapse26"> | |||
| <ul class="pc-submenu"> | |||
| <li | |||
| class="pc-item" | |||
| :class="{ active: this.$route.path === '/orders' }" | |||
| > | |||
| <router-link to="/orders" class="pc-link"> | |||
| <span class="pc-mtext">سفارشات</span></router-link | |||
| > | |||
| </li> | |||
| <li | |||
| class="pc-item" | |||
| :class="{ active: this.$route.path === '/approvedOrders' }" | |||
| > | |||
| <router-link to="/approvedOrders" class="pc-link"> | |||
| <span class="pc-mtext">آیتم های تایید شده</span></router-link | |||
| > | |||
| </li> | |||
| </ul> | |||
| </div> | |||
| </li> | |||
| <li | |||
| class="pc-item" | |||
| @@ -315,10 +370,7 @@ export default { | |||
| <span class="pc-mtext">نظرات</span></router-link | |||
| > | |||
| </li> | |||
| <li | |||
| class="pc-item" | |||
| :class="{ active: this.$route.path === '/faqs' }" | |||
| > | |||
| <li class="pc-item" :class="{ active: this.$route.path === '/faqs' }"> | |||
| <router-link to="/faqs" class="pc-link"> | |||
| <span class="pc-micon"> | |||
| <i class="ph-duotone ph-file-text"></i> | |||
| @@ -365,6 +417,15 @@ export default { | |||
| </div> | |||
| </li> | |||
| <li class="pc-item" :class="{ active: this.$route.path === '/calls' }"> | |||
| <router-link to="/calls" class="pc-link"> | |||
| <span class="pc-micon"> | |||
| <i class="ph-duotone ph-clock"></i> | |||
| </span> | |||
| <span class="pc-mtext">پیگیری</span></router-link | |||
| > | |||
| </li> | |||
| <!-- other --> | |||
| </ul> | |||
| </simplebar> | |||
| @@ -392,7 +453,7 @@ export default { | |||
| </span> | |||
| </template> | |||
| <BRow xl="6"> | |||
| <BCol xl="6"> | |||
| <BCol @click="gotoAccount()" xl="6"> | |||
| <BDropdownItem class="pc-user-links p-0"> | |||
| <i class="ph-duotone ph-user"></i> | |||
| <br /> | |||
| @@ -128,7 +128,6 @@ | |||
| <script> | |||
| import { ref } from "vue"; | |||
| import Swal from "sweetalert2"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| @@ -226,12 +225,10 @@ export default { | |||
| }) | |||
| .catch((error) => { | |||
| console.error(error); | |||
| Swal.fire({ | |||
| icon: "error", | |||
| title: "خطا", | |||
| text: `افزودن برند با مشکل مواجه شد: ${ | |||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||
| }`, | |||
| toast.error("!مشکلی در ایجاد برند پیش آمد", { | |||
| position: "top-right", | |||
| autoClose: 1000 | |||
| }); | |||
| }) | |||
| .finally(() => { | |||
| @@ -60,13 +60,7 @@ | |||
| alt="Image Preview" | |||
| class="img-fluid rounded shadow-sm Image-Preview" | |||
| /> | |||
| <button | |||
| type="button" | |||
| @click="removeImage()" | |||
| class="delete-btn" | |||
| > | |||
| <i class="fa fa-trash f-16"></i> | |||
| </button> | |||
| </div> | |||
| <small v-if="errors.localImage" class="text-danger"> | |||
| @@ -129,7 +123,7 @@ | |||
| <script> | |||
| import { ref, toRef, watch } from "vue"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import Swal from "sweetalert2"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| @@ -206,15 +200,7 @@ export default { | |||
| (newVal) => (localId.value = newVal) | |||
| ); | |||
| const removeImage = () => { | |||
| localImage.value = null; | |||
| imagePreview.value = null; | |||
| const fileInput = document.querySelector('input[type="file"]'); | |||
| if (fileInput) { | |||
| fileInput.value = ""; | |||
| } | |||
| }; | |||
| const validateForm = () => { | |||
| errors.value = {}; | |||
| @@ -265,12 +251,9 @@ export default { | |||
| }) | |||
| .catch((error) => { | |||
| console.error(error); | |||
| Swal.fire({ | |||
| icon: "error", | |||
| title: "خطا", | |||
| text: `ویرایش برند با مشکل مواجه شد: ${ | |||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||
| }`, | |||
| toast.error("!ویرایش برند با مشکل مواجه شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .finally(() => { | |||
| @@ -288,7 +271,7 @@ export default { | |||
| localImage, | |||
| handleImageChange, | |||
| imagePreview, | |||
| removeImage, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -63,8 +63,9 @@ | |||
| class="form-select" | |||
| v-model="role" | |||
| @change="clearError('role')" | |||
| placeholder="نوع کاربر" | |||
| > | |||
| <option disabled value="">نوع کاربر</option> | |||
| <option value="admin">مدیر</option> | |||
| <option value="client">مشتری</option> | |||
| </select> | |||
| @@ -140,7 +141,7 @@ | |||
| <script> | |||
| import { ref } from "vue"; | |||
| import Swal from "sweetalert2"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| @@ -204,13 +205,10 @@ export default { | |||
| }) | |||
| .catch((error) => { | |||
| console.error(error); | |||
| Swal.fire({ | |||
| icon: "error", | |||
| title: "خطا", | |||
| text: `افزودن کاربر با مشکل مواجه شد: ${ | |||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||
| }`, | |||
| confirmButtonText: "باشه", | |||
| toast.error("!افزودن کاربر با مشکل مواجه شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .finally(() => { | |||
| @@ -100,7 +100,6 @@ | |||
| <script> | |||
| import { ref, toRef, watch } from "vue"; | |||
| import Swal from "sweetalert2"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| @@ -145,7 +144,8 @@ export default { | |||
| console.log(localAttributeValues.value); | |||
| formData.append("title", colorName.value); | |||
| formData.append("code", colorCode.value); | |||
| formData.append("attribute_id", localAttributeValues.value[0].id); | |||
| console.log(localAttributeValues) | |||
| formData.append("attribute_id", 1); | |||
| ApiServiece.post(`admin/attribute-values`, formData) | |||
| .then((resp) => { | |||
| @@ -163,12 +163,9 @@ export default { | |||
| }) | |||
| .catch((error) => { | |||
| console.error(error); | |||
| Swal.fire({ | |||
| icon: "error", | |||
| title: "خطا", | |||
| text: `افزودن ویژگی با مشکل مواجه شد: ${ | |||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||
| }`, | |||
| toast.success("!مشکلی در ایجاد ویژگی پیش آمد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .finally(() => { | |||
| @@ -98,7 +98,7 @@ | |||
| <script> | |||
| import { ref, toRef, watch } from "vue"; | |||
| import Swal from "sweetalert2"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| @@ -190,12 +190,9 @@ export default { | |||
| }) | |||
| .catch((error) => { | |||
| console.error(error); | |||
| Swal.fire({ | |||
| icon: "error", | |||
| title: "خطا", | |||
| text: `ویرایش ویژگی با مشکل مواجه شد: ${ | |||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||
| }`, | |||
| toast.success("!مشکلی در ویرایش ویژگی پیش آمد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .finally(() => { | |||
| @@ -107,25 +107,19 @@ | |||
| <script> | |||
| import { iconData } from "../../../views/live-preview/icon/data"; | |||
| import { ref,} from "vue"; | |||
| import Swal from "sweetalert2"; | |||
| import { ref } from "vue"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| export default { | |||
| props: { | |||
| }, | |||
| props: {}, | |||
| setup(props, { emit }) { | |||
| const title = ref(); | |||
| const selectedIcon = ref(); | |||
| const errors = ref({}); | |||
| const loading = ref(false); | |||
| const clearError = (field) => { | |||
| errors.value[field] = ""; | |||
| }; | |||
| @@ -164,12 +158,9 @@ export default { | |||
| }) | |||
| .catch((error) => { | |||
| console.error(error); | |||
| Swal.fire({ | |||
| icon: "error", | |||
| title: "خطا", | |||
| text: `!افزودن دسته با مشکل مواجه شد: ${ | |||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||
| }`, | |||
| toast.error("!مشکلی در اضافه کردن دسته پیش آمد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .finally(() => { | |||
| @@ -59,14 +59,14 @@ | |||
| </b-dropdown-item> | |||
| </b-dropdown> | |||
| <div v-if="selectedIcon" class="mt-2"> | |||
| <div v-if="localIcon" class="mt-2"> | |||
| <label class="form-label">آیکن انتخاب شده:</label> | |||
| <div class="selected-icon-container"> | |||
| <i :class="`ph-duotone ${selectedIcon}`"></i> | |||
| <i :class="`ph-duotone ${localIcon}`"></i> | |||
| </div> | |||
| </div> | |||
| <div v-if="!selectedIcon" class="mt-2"> | |||
| <div v-if="!localIcon" class="mt-2"> | |||
| <label class="form-label">آیکن انتخاب شده:</label> | |||
| <div class="selected-icon-container"> | |||
| <i :class="`ph-duotone ${localIcon}`"></i> | |||
| @@ -113,7 +113,6 @@ | |||
| <script> | |||
| import { iconData } from "../../../views/live-preview/icon/data"; | |||
| import { ref, toRef, watch } from "vue"; | |||
| import Swal from "sweetalert2"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| @@ -136,7 +135,6 @@ export default { | |||
| setup(props, { emit }) { | |||
| const localTitle = toRef(props.title); | |||
| const localIcon = ref(props.icon); | |||
| const selectedIcon = ref(); | |||
| const localId = toRef(props.id); | |||
| const errors = ref({}); | |||
| const loading = ref(false); | |||
| @@ -164,22 +162,21 @@ export default { | |||
| errors.value = {}; | |||
| if (!localTitle.value) | |||
| errors.value.localTitle = "وارد کردن عنوان ضروری می باشد"; | |||
| if (!selectedIcon.value) errors.value.icon = "انتخاب آیکن ضروری است"; | |||
| if (!localIcon.value) errors.value.icon = "انتخاب آیکن ضروری است"; | |||
| return Object.keys(errors.value).length === 0; | |||
| }; | |||
| const setSelectedIcon = (icon) => { | |||
| selectedIcon.value = icon; | |||
| localIcon.value = icon; | |||
| }; | |||
| const editCat = () => { | |||
| console.log(selectedIcon.value); | |||
| if (!validateForm()) return; | |||
| loading.value = true; | |||
| const formData = new FormData(); | |||
| formData.append("title", localTitle.value); | |||
| formData.append("icon", selectedIcon.value); | |||
| formData.append("icon", localIcon.value); | |||
| ApiServiece.put(`admin/blog-categories/${localId.value}`, formData) | |||
| .then(() => { | |||
| toast.success("!دسته با موفقیت ویرایش شد", { | |||
| @@ -195,12 +192,9 @@ export default { | |||
| }) | |||
| .catch((error) => { | |||
| console.error(error); | |||
| Swal.fire({ | |||
| icon: "error", | |||
| title: "خطا", | |||
| text: `!ویرایش دسته با مشکل مواجه شد: ${ | |||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||
| }`, | |||
| toast.error("!ویرایش دسته با مشکل مواجه شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .finally(() => { | |||
| @@ -214,7 +208,6 @@ export default { | |||
| clearError, | |||
| editCat, | |||
| localTitle, | |||
| selectedIcon, | |||
| iconData, | |||
| setSelectedIcon, | |||
| localIcon, | |||
| @@ -98,11 +98,11 @@ | |||
| <BRow class="g-3"> | |||
| <!-- Brand Description --> | |||
| <BCol lg="12"> | |||
| <BCol lg="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">انتخاب پدر</label> | |||
| <select v-model="selectedPaernt" class="form-control"> | |||
| <option value="" disabled selected>انتخاب کنید</option> | |||
| <select v-model="selectedPaernt" class="form-control" placeholder="انتخاب کنید"> | |||
| <option | |||
| v-for="parent in localParents" | |||
| :key="parent.id" | |||
| @@ -113,6 +113,40 @@ | |||
| </select> | |||
| </div> | |||
| </BCol> | |||
| <BCol lg="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">انتخاب آیکن</label> | |||
| <b-dropdown | |||
| variant="outline-primary" | |||
| class="w-100" | |||
| @change="clearError('icon')" | |||
| > | |||
| <b-dropdown-item | |||
| v-for="(icon, index) in iconData" | |||
| :key="index" | |||
| :value="icon.component" | |||
| class="icon-item" | |||
| @click="setSelectedIcon(icon.component)" | |||
| > | |||
| <div class="icon-container"> | |||
| <i :class="`ph-duotone ${icon.component}`"></i> | |||
| </div> | |||
| </b-dropdown-item> | |||
| </b-dropdown> | |||
| <div v-if="selectedIcon" class="mt-2"> | |||
| <label class="form-label">آیکن انتخاب شده:</label> | |||
| <div class="selected-icon-container"> | |||
| <i :class="`ph-duotone ${selectedIcon}`"></i> | |||
| </div> | |||
| </div> | |||
| <small v-if="errors.icon" class="text-danger"> | |||
| {{ errors.icon }} | |||
| </small> | |||
| </div> | |||
| </BCol> | |||
| </BRow> | |||
| <!-- Submit Buttons --> | |||
| @@ -146,8 +180,8 @@ | |||
| </template> | |||
| <script> | |||
| import { iconData } from "../../../views/live-preview/icon/data"; | |||
| import { ref, toRef, watch } from "vue"; | |||
| import Swal from "sweetalert2"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| @@ -160,6 +194,7 @@ export default { | |||
| }, | |||
| }, | |||
| setup(props, { emit }) { | |||
| const selectedIcon = ref(); | |||
| const selectedPaernt = ref(); | |||
| const localParents = toRef(props.parents); | |||
| const image = ref(null); | |||
| @@ -213,12 +248,17 @@ export default { | |||
| } | |||
| }; | |||
| const setSelectedIcon = (icon) => { | |||
| selectedIcon.value = icon; | |||
| }; | |||
| const validateForm = () => { | |||
| errors.value = {}; | |||
| if (!description.value) | |||
| errors.value.description = "وارد کردن توضیحات ضروری می باشد"; | |||
| if (!title.value) errors.value.title = "وارد کردن عنوان ضروری می باشد"; | |||
| if (!image.value) errors.value.image = "یک عکس انتخاب نمایید"; | |||
| if (!selectedIcon.value) errors.value.icon = "انتخاب آیکن ضروری است"; | |||
| return Object.keys(errors.value).length === 0; | |||
| }; | |||
| @@ -234,6 +274,7 @@ export default { | |||
| formData.append("title", title.value); | |||
| formData.append("description", description.value); | |||
| formData.append("image", image.value); | |||
| formData.append("icon", selectedIcon.value); | |||
| if (selectedPaernt.value) { | |||
| formData.append("parent_id", selectedPaernt.value); | |||
| } | |||
| @@ -258,12 +299,9 @@ export default { | |||
| }) | |||
| .catch((error) => { | |||
| console.error(error); | |||
| Swal.fire({ | |||
| icon: "error", | |||
| title: "خطا", | |||
| text: `!افزودن دسته با مشکل مواجه شد: ${ | |||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||
| }`, | |||
| toast.error("!افزودن دسته با مشکل مواجه شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .finally(() => { | |||
| @@ -284,6 +322,9 @@ export default { | |||
| description, | |||
| localParents, | |||
| selectedPaernt, | |||
| setSelectedIcon, | |||
| iconData, | |||
| selectedIcon, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -367,4 +408,31 @@ export default { | |||
| .delete-btn:focus { | |||
| outline: none; | |||
| } | |||
| .icon-item { | |||
| display: inline-block; | |||
| width: 33%; | |||
| padding: 5px; | |||
| text-align: center; | |||
| } | |||
| .icon-container i { | |||
| font-size: 2rem; | |||
| margin-right: 10px; | |||
| } | |||
| .selected-icon-container { | |||
| display: flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| margin-top: 10px; | |||
| } | |||
| .selected-icon-container i { | |||
| font-size: 2.5rem; | |||
| } | |||
| .icon-container { | |||
| display: flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| margin: 8px; | |||
| } | |||
| </style> | |||
| @@ -10,7 +10,7 @@ | |||
| <div class="modal-dialog modal-sm" role="document"> | |||
| <div class="modal-content"> | |||
| <div class="modal-header"> | |||
| <h5 class="modal-title" id="exampleModalLabel">ویرایش برند</h5> | |||
| <h5 class="modal-title" id="exampleModalLabel">ویرایش دسته</h5> | |||
| <button | |||
| type="button" | |||
| @@ -60,13 +60,6 @@ | |||
| alt="Image Preview" | |||
| class="img-fluid rounded shadow-sm Image-Preview" | |||
| /> | |||
| <button | |||
| type="button" | |||
| @click="removeImage()" | |||
| class="delete-btn" | |||
| > | |||
| <i class="fa fa-trash f-16"></i> | |||
| </button> | |||
| </div> | |||
| <small v-if="errors.localImage" class="text-danger"> | |||
| @@ -98,11 +91,14 @@ | |||
| <BRow class="g-3"> | |||
| <!-- Brand Description --> | |||
| <BCol lg="12"> | |||
| <BCol lg="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">انتخاب پدر</label> | |||
| <select v-model="localParent" class="form-control"> | |||
| <option value="" disabled selected>انتخاب کنید</option> | |||
| <select | |||
| v-model="localParent" | |||
| class="form-control" | |||
| placeholder="انتخاب کنید" | |||
| > | |||
| <option | |||
| v-for="parent in allLocalParents" | |||
| :key="parent.id" | |||
| @@ -113,6 +109,46 @@ | |||
| </select> | |||
| </div> | |||
| </BCol> | |||
| <BCol lg="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">انتخاب آیکن</label> | |||
| <b-dropdown | |||
| variant="outline-primary" | |||
| class="w-100" | |||
| @change="clearError('icon')" | |||
| > | |||
| <b-dropdown-item | |||
| v-for="(icon, index) in iconData" | |||
| :key="index" | |||
| :value="icon.component" | |||
| class="icon-item" | |||
| @click="setSelectedIcon(icon.component)" | |||
| > | |||
| <div class="icon-container"> | |||
| <i :class="`ph-duotone ${icon.component}`"></i> | |||
| </div> | |||
| </b-dropdown-item> | |||
| </b-dropdown> | |||
| <div v-if="localIcon" class="mt-2"> | |||
| <label class="form-label">آیکن انتخاب شده:</label> | |||
| <div class="selected-icon-container"> | |||
| <i :class="`ph-duotone ${localIcon}`"></i> | |||
| </div> | |||
| </div> | |||
| <div v-if="!localIcon" class="mt-2"> | |||
| <label class="form-label">آیکن انتخاب شده:</label> | |||
| <div class="selected-icon-container"> | |||
| <i :class="`ph-duotone ${localIcon}`"></i> | |||
| </div> | |||
| </div> | |||
| <small v-if="errors.icon" class="text-danger"> | |||
| {{ errors.icon }} | |||
| </small> | |||
| </div> | |||
| </BCol> | |||
| </BRow> | |||
| <!-- Submit Buttons --> | |||
| @@ -148,9 +184,10 @@ | |||
| <script> | |||
| import { ref, toRef, watch } from "vue"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import Swal from "sweetalert2"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import { iconData } from "../../../views/live-preview/icon/data"; | |||
| export default { | |||
| props: { | |||
| @@ -178,9 +215,14 @@ export default { | |||
| type: Array, | |||
| Required: true, | |||
| }, | |||
| icon: { | |||
| type: String, | |||
| Required: true, | |||
| }, | |||
| }, | |||
| setup(props, { emit }) { | |||
| const localIcon = toRef(props.icon); | |||
| const imagePreview = ref(null); | |||
| const allLocalParents = toRef(props.allParents); | |||
| const localParent = toRef(props.parent); | |||
| @@ -190,6 +232,7 @@ export default { | |||
| const image = ref(null); | |||
| const localId = toRef(props.id); | |||
| const errors = ref({}); | |||
| const loading = ref(false); | |||
| const handleImageChange = (event) => { | |||
| @@ -214,6 +257,10 @@ export default { | |||
| } | |||
| }; | |||
| const setSelectedIcon = (icon) => { | |||
| localIcon.value = icon; | |||
| }; | |||
| watch( | |||
| () => props.title, | |||
| (newVal) => (localTitle.value = newVal) | |||
| @@ -245,15 +292,10 @@ export default { | |||
| (newVal) => (allLocalParents.value = newVal) | |||
| ); | |||
| const removeImage = () => { | |||
| localImage.value = null; | |||
| imagePreview.value = null; | |||
| const fileInput = document.querySelector('input[type="file"]'); | |||
| if (fileInput) { | |||
| fileInput.value = ""; | |||
| } | |||
| }; | |||
| watch( | |||
| () => props.icon, | |||
| (newVal) => (localIcon.value = newVal) | |||
| ); | |||
| const validateForm = () => { | |||
| errors.value = {}; | |||
| @@ -264,6 +306,7 @@ export default { | |||
| if (!localImage.value && !imagePreview.value) { | |||
| errors.value.localImage = "یک عکس انتخاب نمایید"; | |||
| } | |||
| if (!localIcon.value) errors.value.icon = "انتخاب آیکن ضروری است"; | |||
| return Object.keys(errors.value).length === 0; | |||
| }; | |||
| @@ -272,12 +315,14 @@ export default { | |||
| }; | |||
| const editCat = () => { | |||
| console.log(localId.value); | |||
| if (!validateForm()) return; | |||
| loading.value = true; | |||
| const formData = new FormData(); | |||
| formData.append("title", localTitle.value); | |||
| formData.append("description", localDesc.value); | |||
| formData.append("icon", localIcon.value); | |||
| if (image.value) { | |||
| formData.append("image", image.value); | |||
| @@ -307,12 +352,9 @@ export default { | |||
| }) | |||
| .catch((error) => { | |||
| console.error(error); | |||
| Swal.fire({ | |||
| icon: "error", | |||
| title: "خطا", | |||
| text: `ویرایش دسته با مشکل مواجه شد: ${ | |||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||
| }`, | |||
| toast.error("!ویراش دسته با مشکل مواجه شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .finally(() => { | |||
| @@ -330,9 +372,12 @@ export default { | |||
| localImage, | |||
| handleImageChange, | |||
| imagePreview, | |||
| removeImage, | |||
| iconData, | |||
| localParent, | |||
| allLocalParents, | |||
| setSelectedIcon, | |||
| localIcon, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -516,4 +561,33 @@ export default { | |||
| .delete-btn:focus { | |||
| outline: none; | |||
| } | |||
| .selected-icon-container { | |||
| display: flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| margin-top: 10px; | |||
| } | |||
| .selected-icon-container i { | |||
| font-size: 2.5rem; | |||
| } | |||
| .icon-container { | |||
| display: flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| margin: 8px; | |||
| } | |||
| .icon-container i { | |||
| font-size: 2rem; | |||
| margin-right: 10px; | |||
| } | |||
| .icon-item { | |||
| display: inline-block; | |||
| width: 33%; | |||
| padding: 5px; | |||
| text-align: center; | |||
| } | |||
| </style> | |||
| @@ -1,12 +1,13 @@ | |||
| <template> | |||
| <div | |||
| class="modal fade" | |||
| id="showDescription" | |||
| tabindex="-1" | |||
| role="dialog" | |||
| aria-labelledby="exampleModalLabel" | |||
| aria-hidden="true" | |||
| > | |||
| <div | |||
| class="modal fade" | |||
| id="showDescription" | |||
| tabindex="-1" | |||
| role="dialog" | |||
| aria-labelledby="exampleModalLabel" | |||
| aria-hidden="true" | |||
| :inert="localDesc" | |||
| > | |||
| <div class="modal-dialog modal-lg" role="document"> | |||
| <div class="modal-content"> | |||
| <div class="modal-header"> | |||
| @@ -0,0 +1,175 @@ | |||
| <template> | |||
| <div | |||
| class="modal fade" | |||
| id="showText" | |||
| tabindex="-1" | |||
| role="dialog" | |||
| aria-labelledby="exampleModalLabel" | |||
| aria-hidden="true" | |||
| > | |||
| <div class="modal-dialog modal-lg" role="document"> | |||
| <div class="modal-content"> | |||
| <div class="modal-header"> | |||
| <h5 class="modal-title" id="exampleModalLabel"> | |||
| <i class="fas fa-clipboard-list"></i> توضیح کامل | |||
| </h5> | |||
| <button | |||
| type="button" | |||
| class="btn-close" | |||
| data-bs-dismiss="modal" | |||
| aria-label="Close" | |||
| ></button> | |||
| </div> | |||
| <div class="modal-body"> | |||
| <div class="subject-container"> | |||
| <textarea | |||
| disabled | |||
| class="subject-text" | |||
| v-model="localDesc" | |||
| ></textarea> | |||
| </div> | |||
| </div> | |||
| <div class="modal-footer"> | |||
| <button | |||
| type="button" | |||
| class="btn btn-secondary" | |||
| data-bs-dismiss="modal" | |||
| > | |||
| بستن | |||
| </button> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import "vue3-toastify/dist/index.css"; | |||
| import { watch, ref } from "vue"; | |||
| export default { | |||
| props: { | |||
| desc: { | |||
| type: String, | |||
| required: true, | |||
| }, | |||
| }, | |||
| setup(props) { | |||
| const localDesc = ref(); | |||
| watch( | |||
| () => props.desc, | |||
| (newVal) => (localDesc.value = newVal) | |||
| ); | |||
| return { | |||
| localDesc, | |||
| }; | |||
| }, | |||
| }; | |||
| </script> | |||
| <style scoped> | |||
| .modal-dialog { | |||
| max-width: 600px; /* Larger size for better content visibility */ | |||
| margin-top: 10vh; /* Center vertically */ | |||
| } | |||
| .modal-content { | |||
| border-radius: 16px; /* More rounded corners */ | |||
| box-shadow: 0 6px 30px rgba(0, 0, 0, 0.1); /* Larger shadow for more depth */ | |||
| background: linear-gradient( | |||
| to bottom right, | |||
| #f5f7fb, | |||
| #e0e8ed | |||
| ); /* Soft gradient background */ | |||
| } | |||
| .modal-header { | |||
| border-bottom: none; /* Remove default border */ | |||
| } | |||
| .modal-title { | |||
| color: #007bff; | |||
| font-weight: 600; | |||
| font-size: 1.75rem; | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| .modal-title i { | |||
| margin-right: 12px; | |||
| font-size: 1.75rem; | |||
| } | |||
| .btn-close { | |||
| background: none; | |||
| border: none; | |||
| font-size: 1.75rem; | |||
| color: #007bff; | |||
| transition: color 0.3s ease; | |||
| } | |||
| .btn-close:hover { | |||
| color: #0056b3; | |||
| } | |||
| .modal-body { | |||
| padding: 25px; | |||
| } | |||
| .subject-container { | |||
| display: flex; | |||
| align-items: center; | |||
| background: #ffffff; | |||
| border-radius: 12px; | |||
| padding: 15px; | |||
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |||
| margin-top: 10px; | |||
| } | |||
| .subject-container i { | |||
| color: #007bff; | |||
| margin-right: 15px; | |||
| font-size: 1.75rem; | |||
| } | |||
| .subject-text { | |||
| color: #333; | |||
| font-weight: 500; | |||
| border: 2px solid #007bff; | |||
| width: 100%; | |||
| height: 180px; | |||
| padding: 12px; | |||
| border-radius: 10px; | |||
| font-size: 1rem; | |||
| background-color: #f9f9f9; | |||
| box-sizing: border-box; | |||
| resize: none; | |||
| transition: border-color 0.3s; | |||
| } | |||
| .subject-text:focus { | |||
| border-color: #0056b3; /* Highlight border on focus */ | |||
| outline: none; | |||
| } | |||
| .modal-footer { | |||
| display: flex; | |||
| justify-content: flex-end; | |||
| margin-top: 1.5rem; | |||
| } | |||
| .btn-secondary { | |||
| background-color: #6c757d; | |||
| color: #fff; | |||
| padding: 10px 16px; | |||
| border-radius: 8px; | |||
| transition: background 0.3s; | |||
| font-size: 1.1rem; | |||
| } | |||
| .btn-secondary:hover { | |||
| background-color: #5a6268; | |||
| } | |||
| </style> | |||
| @@ -61,8 +61,9 @@ | |||
| class="form-select" | |||
| v-model="localRole" | |||
| @change="clearError('role')" | |||
| placeholder="نوع کاربر" | |||
| > | |||
| <option disabled value="">نوع کاربر</option> | |||
| <option value="admin">مدیر</option> | |||
| <option value="client">مشتری</option> | |||
| </select> | |||
| @@ -121,7 +122,6 @@ | |||
| <script> | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import { ref, toRef, watch } from "vue"; | |||
| import Swal from "sweetalert2"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| @@ -217,13 +217,10 @@ export default { | |||
| }) | |||
| .catch((error) => { | |||
| console.error(error); | |||
| Swal.fire({ | |||
| icon: "error", | |||
| title: "خطا", | |||
| text: `ویرایش کاربر با مشکل مواجه شد: ${ | |||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||
| }`, | |||
| confirmButtonText: "باشه", | |||
| toast.error("!ویرایش کاربر با مشکل مواحه شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| onClose: () => emit("user-updated"), | |||
| }); | |||
| }) | |||
| .finally(() => { | |||
| @@ -0,0 +1,130 @@ | |||
| <template> | |||
| <div | |||
| class="modal fade" | |||
| id="catBanner" | |||
| tabindex="-1" | |||
| role="dialog" | |||
| aria-labelledby="exampleModalLabel" | |||
| aria-hidden="true" | |||
| > | |||
| <div class="modal-dialog modal-lg" role="document"> | |||
| <div class="modal-content"> | |||
| <div class="modal-header"> | |||
| <h5 class="modal-title" id="exampleModalLabel"> | |||
| <i class="fa fa-info-circle me-2"></i> بنر دسته ها | |||
| </h5> | |||
| </div> | |||
| <div class="modal-body"> | |||
| <div class="subject-container"> | |||
| <img | |||
| src="../../../assets/custom/دسته بندی ها.png" | |||
| alt="Guidance Image" | |||
| class="img-fluid rounded" | |||
| /> | |||
| </div> | |||
| </div> | |||
| <div class="modal-footer"> | |||
| <div class="w-100 d-flex justify-content-center"> | |||
| <button | |||
| type="button" | |||
| class="btn btn-secondary" | |||
| data-bs-dismiss="modal" | |||
| > | |||
| بستن | |||
| </button> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| export default { | |||
| setup() { | |||
| return {}; | |||
| }, | |||
| }; | |||
| </script> | |||
| <style scoped> | |||
| .modal-dialog { | |||
| max-width: 700px; /* Increase the size of the modal for better visibility */ | |||
| margin-top: 15vh; /* Center vertically with some padding */ | |||
| } | |||
| .modal-content { | |||
| border-radius: 16px; /* Rounded corners */ | |||
| box-shadow: 0 6px 30px rgba(0, 0, 0, 0.15); /* Larger shadow for depth */ | |||
| background: #ffffff; /* White background */ | |||
| border: none; | |||
| } | |||
| .modal-header { | |||
| border-bottom: 1px solid #ddd; /* Soft border at the bottom */ | |||
| padding: 1rem 1.5rem; | |||
| } | |||
| .modal-title { | |||
| color: #007bff; | |||
| font-weight: 600; | |||
| font-size: 1.75rem; | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| .modal-title i { | |||
| margin-right: 12px; | |||
| font-size: 2rem; | |||
| } | |||
| .btn-close { | |||
| background: none; | |||
| border: none; | |||
| font-size: 1.75rem; | |||
| color: #007bff; | |||
| transition: color 0.3s ease; | |||
| } | |||
| .btn-close:hover { | |||
| color: #0056b3; | |||
| } | |||
| .modal-body { | |||
| padding: 2rem; | |||
| text-align: center; /* Center the image */ | |||
| } | |||
| .subject-container { | |||
| background: #f9f9f9; | |||
| padding: 25px; | |||
| border-radius: 12px; | |||
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |||
| } | |||
| .subject-container img { | |||
| max-width: 100%; | |||
| max-height: 400px; | |||
| object-fit: cover; | |||
| border-radius: 12px; | |||
| } | |||
| .modal-footer { | |||
| display: flex; | |||
| justify-content: flex-end; | |||
| padding: 1rem 1.5rem; | |||
| } | |||
| .btn-secondary { | |||
| background-color: #6c757d; | |||
| color: #fff; | |||
| padding: 10px 16px; | |||
| border-radius: 8px; | |||
| transition: background 0.3s; | |||
| font-size: 1.1rem; | |||
| } | |||
| .btn-secondary:hover { | |||
| background-color: #5a6268; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,130 @@ | |||
| <template> | |||
| <div | |||
| class="modal fade" | |||
| id="mainPageBanner" | |||
| tabindex="-1" | |||
| role="dialog" | |||
| aria-labelledby="exampleModalLabel" | |||
| aria-hidden="true" | |||
| > | |||
| <div class="modal-dialog modal-lg" role="document"> | |||
| <div class="modal-content"> | |||
| <div class="modal-header"> | |||
| <h5 class="modal-title" id="exampleModalLabel"> | |||
| <i class="fa fa-info-circle me-2"></i> بنر صفحه اصلی | |||
| </h5> | |||
| </div> | |||
| <div class="modal-body"> | |||
| <div class="subject-container"> | |||
| <img | |||
| src="../../../assets/custom/صفحه اصلی.png" | |||
| alt="Guidance Image" | |||
| class="img-fluid rounded" | |||
| /> | |||
| </div> | |||
| </div> | |||
| <div class="modal-footer"> | |||
| <div class="w-100 d-flex justify-content-center"> | |||
| <button | |||
| type="button" | |||
| class="btn btn-secondary" | |||
| data-bs-dismiss="modal" | |||
| > | |||
| بستن | |||
| </button> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| export default { | |||
| setup() { | |||
| return {}; | |||
| }, | |||
| }; | |||
| </script> | |||
| <style scoped> | |||
| .modal-dialog { | |||
| max-width: 700px; /* Increase the size of the modal for better visibility */ | |||
| margin-top: 15vh; /* Center vertically with some padding */ | |||
| } | |||
| .modal-content { | |||
| border-radius: 16px; /* Rounded corners */ | |||
| box-shadow: 0 6px 30px rgba(0, 0, 0, 0.15); /* Larger shadow for depth */ | |||
| background: #ffffff; /* White background */ | |||
| border: none; | |||
| } | |||
| .modal-header { | |||
| border-bottom: 1px solid #ddd; /* Soft border at the bottom */ | |||
| padding: 1rem 1.5rem; | |||
| } | |||
| .modal-title { | |||
| color: #007bff; | |||
| font-weight: 600; | |||
| font-size: 1.75rem; | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| .modal-title i { | |||
| margin-right: 12px; | |||
| font-size: 2rem; | |||
| } | |||
| .btn-close { | |||
| background: none; | |||
| border: none; | |||
| font-size: 1.75rem; | |||
| color: #007bff; | |||
| transition: color 0.3s ease; | |||
| } | |||
| .btn-close:hover { | |||
| color: #0056b3; | |||
| } | |||
| .modal-body { | |||
| padding: 2rem; | |||
| text-align: center; /* Center the image */ | |||
| } | |||
| .subject-container { | |||
| background: #f9f9f9; | |||
| padding: 25px; | |||
| border-radius: 12px; | |||
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |||
| } | |||
| .subject-container img { | |||
| max-width: 100%; | |||
| max-height: 400px; | |||
| object-fit: cover; | |||
| border-radius: 12px; | |||
| } | |||
| .modal-footer { | |||
| display: flex; | |||
| justify-content: flex-end; | |||
| padding: 1rem 1.5rem; | |||
| } | |||
| .btn-secondary { | |||
| background-color: #6c757d; | |||
| color: #fff; | |||
| padding: 10px 16px; | |||
| border-radius: 8px; | |||
| transition: background 0.3s; | |||
| font-size: 1.1rem; | |||
| } | |||
| .btn-secondary:hover { | |||
| background-color: #5a6268; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,290 @@ | |||
| <template> | |||
| <div | |||
| class="modal fade" | |||
| id="addIdentity" | |||
| tabindex="-1" | |||
| role="dialog" | |||
| aria-labelledby="exampleModalLabel" | |||
| aria-hidden="false" | |||
| > | |||
| <div class="modal-dialog modal-sm" role="document"> | |||
| <div class="modal-content"> | |||
| <div class="modal-header"> | |||
| <h5 class="modal-title" id="exampleModalLabel"> | |||
| اضافه کردن مشخصه جدید | |||
| </h5> | |||
| <button | |||
| type="button" | |||
| class="btn-close" | |||
| data-bs-dismiss="modal" | |||
| aria-label="Close" | |||
| ></button> | |||
| </div> | |||
| <div class="modal-body"> | |||
| <form @submit.prevent="addIdentity"> | |||
| <BRow class="g-3"> | |||
| <!-- Brand Title --> | |||
| <BCol lg="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">عنوان مشخصه</label> | |||
| <input | |||
| v-model="title" | |||
| @input="clearError('title')" | |||
| type="text" | |||
| class="form-control" | |||
| placeholder="عنوان مشخصه" | |||
| :class="{ 'is-invalid': errors.title }" | |||
| /> | |||
| <small v-if="errors.title" class="text-danger"> | |||
| {{ errors.title }} | |||
| </small> | |||
| </div> | |||
| </BCol> | |||
| <BCol lg="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">انتخاب دسته</label> | |||
| <div class="color-picker-wrapper"> | |||
| <select | |||
| class="form-control selector" | |||
| @change="clearError(`selectedCat`)" | |||
| v-model="selectedCat" | |||
| :class="{ 'is-invalid': errors.selectedCat }" | |||
| placeholder="انتخاب دسته" | |||
| > | |||
| <option | |||
| v-for="cat in localCat" | |||
| :key="cat.id" | |||
| :value="cat.id" | |||
| > | |||
| {{ cat.title }} | |||
| </option> | |||
| </select> | |||
| </div> | |||
| <small v-if="errors.selectedCat" class="text-danger"> | |||
| {{ errors.selectedCat }} | |||
| </small> | |||
| </div> | |||
| </BCol> | |||
| </BRow> | |||
| <!-- Submit Buttons --> | |||
| <div | |||
| class="d-flex justify-content-end gap-2" | |||
| style="margin-top: 20px" | |||
| > | |||
| <button | |||
| type="button" | |||
| class="btn btn-secondary" | |||
| data-bs-dismiss="modal" | |||
| id="close" | |||
| > | |||
| بستن | |||
| </button> | |||
| <button type="submit" class="btn btn-primary" :disabled="loading"> | |||
| <span | |||
| v-if="loading" | |||
| class="spinner-border spinner-border-sm" | |||
| role="status" | |||
| aria-hidden="true" | |||
| ></span> | |||
| ذخیره | |||
| </button> | |||
| </div> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import { ref, toRef, watch } from "vue"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| export default { | |||
| props: { | |||
| cats: { | |||
| type: Array, | |||
| Required: true, | |||
| }, | |||
| }, | |||
| setup(props, { emit }) { | |||
| const localCat = toRef(props.cats); | |||
| const selectedCat = ref(); | |||
| const title = ref(); | |||
| const errors = ref({}); | |||
| const loading = ref(false); | |||
| watch( | |||
| () => props.cats, | |||
| (newVal) => (localCat.value = newVal) | |||
| ); | |||
| const validateForm = () => { | |||
| errors.value = {}; | |||
| if (!title.value) | |||
| errors.value.title = "وارد کردن عنوان مشخصه ضروری می باشد"; | |||
| if (!selectedCat.value) | |||
| errors.value.selectedCat = "انتخاب دسته ضروری می باشد"; | |||
| return Object.keys(errors.value).length === 0; | |||
| }; | |||
| const clearError = (field) => { | |||
| errors.value[field] = ""; | |||
| }; | |||
| const addIdentity = () => { | |||
| if (!validateForm()) return; | |||
| loading.value = true; | |||
| const formData = new FormData(); | |||
| 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, | |||
| }); | |||
| }) | |||
| .then(() => { | |||
| setTimeout(() => { | |||
| document.getElementById("close").click(); | |||
| emit("attribute-updated"); | |||
| }, 500); | |||
| }) | |||
| .catch((error) => { | |||
| console.error(error); | |||
| toast.error("!اضافه کردن مشخصه با مشکل مواجه شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .finally(() => { | |||
| loading.value = false; | |||
| }); | |||
| }; | |||
| return { | |||
| errors, | |||
| loading, | |||
| clearError, | |||
| addIdentity, | |||
| localCat, | |||
| title, | |||
| selectedCat, | |||
| }; | |||
| }, | |||
| }; | |||
| </script> | |||
| <style scoped> | |||
| .modal-dialog { | |||
| max-width: 50%; | |||
| } | |||
| .modal-content { | |||
| padding: 20px; | |||
| } | |||
| .modal-body { | |||
| padding: 20px; | |||
| padding: 1rem 1.5rem; | |||
| } | |||
| .form-group { | |||
| margin-bottom: 1rem; | |||
| } | |||
| .input-group { | |||
| margin-top: 0.5rem; | |||
| } | |||
| .modal-dialog { | |||
| max-width: 50%; | |||
| } | |||
| .modal-content { | |||
| padding: 1.5rem; | |||
| } | |||
| .modal-header { | |||
| border-bottom: 1px solid #dee2e6; | |||
| padding-bottom: 1rem; | |||
| } | |||
| .form-group { | |||
| margin-bottom: 1.5rem; | |||
| } | |||
| .input-group { | |||
| margin-top: 0.5rem; | |||
| } | |||
| .Image-Preview { | |||
| min-width: 100px; | |||
| max-height: 100px; | |||
| max-width: 100px; | |||
| object-fit: cover; | |||
| border-radius: 8px; | |||
| border: 1px solid #ddd; | |||
| } | |||
| .delete-btn { | |||
| display: flex; | |||
| align-items: center; | |||
| padding: 3px; | |||
| font-size: 10px; | |||
| background-color: #e74c3c; | |||
| color: white; | |||
| border: none; | |||
| border-radius: 5px; | |||
| cursor: pointer; | |||
| transition: background-color 0.3s ease, transform 0.2s ease; | |||
| gap: 8px; /* Space between icon and text */ | |||
| margin-right: 100px; | |||
| } | |||
| .delete-btn:hover { | |||
| background-color: #c0392b; | |||
| transform: scale(1.05); | |||
| } | |||
| .delete-btn:active { | |||
| background-color: #a93226; | |||
| } | |||
| .delete-btn:focus { | |||
| outline: none; | |||
| } | |||
| .color-picker-wrapper { | |||
| position: relative; | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| .color-picker { | |||
| width: 300px; | |||
| height: 45px; | |||
| padding: 0; | |||
| border-radius: 10px; | |||
| border: none; | |||
| cursor: pointer; | |||
| } | |||
| .color-display { | |||
| width: 30px; | |||
| height: 30px; | |||
| margin-left: 10px; | |||
| border-radius: 50%; | |||
| border: 2px solid #ccc; | |||
| display: inline-block; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,316 @@ | |||
| <template> | |||
| <div | |||
| class="modal fade" | |||
| id="editIdentity" | |||
| tabindex="-1" | |||
| role="dialog" | |||
| aria-labelledby="exampleModalLabel" | |||
| aria-hidden="false" | |||
| > | |||
| <div class="modal-dialog modal-sm" role="document"> | |||
| <div class="modal-content"> | |||
| <div class="modal-header"> | |||
| <h5 class="modal-title" id="exampleModalLabel">ویرایش مشخصه</h5> | |||
| <button | |||
| type="button" | |||
| class="btn-close" | |||
| data-bs-dismiss="modal" | |||
| aria-label="Close" | |||
| ></button> | |||
| </div> | |||
| <div class="modal-body"> | |||
| <form @submit.prevent="editIdentity"> | |||
| <BRow class="g-3"> | |||
| <!-- Brand Title --> | |||
| <BCol lg="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">عنوان مشخصه</label> | |||
| <input | |||
| v-model="localTitle" | |||
| @input="clearError('localTitle')" | |||
| type="text" | |||
| class="form-control" | |||
| placeholder="عنوان مشخصه" | |||
| :class="{ 'is-invalid': errors.localTitle }" | |||
| /> | |||
| <small v-if="errors.localTitle" class="text-danger"> | |||
| {{ errors.localTitle }} | |||
| </small> | |||
| </div> | |||
| </BCol> | |||
| <BCol lg="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">انتخاب دسته</label> | |||
| <div class="color-picker-wrapper"> | |||
| <select | |||
| class="form-control selector" | |||
| @change="clearError(`localCat`)" | |||
| v-model="localCatId" | |||
| :class="{ 'is-invalid': errors.localCat }" | |||
| placeholder="انتخاب دسته" | |||
| > | |||
| <option | |||
| v-for="cat in localCat" | |||
| :key="cat.id" | |||
| :value="cat.id" | |||
| > | |||
| {{ cat.title }} | |||
| </option> | |||
| </select> | |||
| </div> | |||
| <small v-if="errors.selectedCat" class="text-danger"> | |||
| {{ errors.selectedCat }} | |||
| </small> | |||
| </div> | |||
| </BCol> | |||
| </BRow> | |||
| <!-- Submit Buttons --> | |||
| <div | |||
| class="d-flex justify-content-end gap-2" | |||
| style="margin-top: 20px" | |||
| > | |||
| <button | |||
| type="button" | |||
| class="btn btn-secondary" | |||
| data-bs-dismiss="modal" | |||
| id="close" | |||
| > | |||
| بستن | |||
| </button> | |||
| <button type="submit" class="btn btn-primary" :disabled="loading"> | |||
| <span | |||
| v-if="loading" | |||
| class="spinner-border spinner-border-sm" | |||
| role="status" | |||
| aria-hidden="true" | |||
| ></span> | |||
| ویرایش | |||
| </button> | |||
| </div> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import { ref, toRef, watch } from "vue"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| export default { | |||
| props: { | |||
| title: { | |||
| type: String, | |||
| Required: true, | |||
| }, | |||
| catId: { | |||
| type: String, | |||
| Required: true, | |||
| }, | |||
| id: { | |||
| type: String, | |||
| Required: true, | |||
| }, | |||
| cats: { | |||
| type: Array, | |||
| Required: true, | |||
| }, | |||
| }, | |||
| setup(props, { emit }) { | |||
| const localCat = toRef(props.cats); | |||
| const localTitle = toRef(props.title); | |||
| const localCatId = toRef(props.catId); | |||
| const localId = toRef(props.id); | |||
| const errors = ref({}); | |||
| const loading = ref(false); | |||
| watch( | |||
| () => props.cats, | |||
| (newVal) => (localCat.value = newVal) | |||
| ); | |||
| watch( | |||
| () => props.title, | |||
| (newVal) => (localTitle.value = newVal) | |||
| ); | |||
| watch( | |||
| () => props.id, | |||
| (newVal) => (localId.value = newVal) | |||
| ); | |||
| watch( | |||
| () => props.catId, | |||
| (newVal) => (localCatId.value = newVal) | |||
| ); | |||
| const validateForm = () => { | |||
| errors.value = {}; | |||
| if (!localTitle.value) | |||
| errors.value.localTitle = "وارد کردن عنوان مشخصه ضروری می باشد"; | |||
| if (!localCat.value) errors.value.localCat = "انتخاب دسته ضروری می باشد"; | |||
| return Object.keys(errors.value).length === 0; | |||
| }; | |||
| const clearError = (field) => { | |||
| errors.value[field] = ""; | |||
| }; | |||
| const editIdentity = () => { | |||
| if (!validateForm()) return; | |||
| loading.value = true; | |||
| const formData = new FormData(); | |||
| formData.append("category_id", localCatId.value); | |||
| formData.append("title", localTitle.value); | |||
| ApiServiece.put(`admin/attributes/${localId.value}`, formData) | |||
| .then((resp) => { | |||
| console.log(resp); | |||
| toast.success("!مشخصه با موفقیت ویرایش شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .then(() => { | |||
| setTimeout(() => { | |||
| document.getElementById("close").click(); | |||
| emit("attribute-updated"); | |||
| }, 500); | |||
| }) | |||
| .catch((error) => { | |||
| console.error(error); | |||
| toast.error("!ویرایش مشخصه با مشکل مواجه شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .finally(() => { | |||
| loading.value = false; | |||
| }); | |||
| }; | |||
| return { | |||
| errors, | |||
| loading, | |||
| clearError, | |||
| editIdentity, | |||
| localCat, | |||
| localTitle, | |||
| localId, | |||
| localCatId, | |||
| }; | |||
| }, | |||
| }; | |||
| </script> | |||
| <style scoped> | |||
| .modal-dialog { | |||
| max-width: 50%; | |||
| } | |||
| .modal-content { | |||
| padding: 20px; | |||
| } | |||
| .modal-body { | |||
| padding: 20px; | |||
| padding: 1rem 1.5rem; | |||
| } | |||
| .form-group { | |||
| margin-bottom: 1rem; | |||
| } | |||
| .input-group { | |||
| margin-top: 0.5rem; | |||
| } | |||
| .modal-dialog { | |||
| max-width: 50%; | |||
| } | |||
| .modal-content { | |||
| padding: 1.5rem; | |||
| } | |||
| .modal-header { | |||
| border-bottom: 1px solid #dee2e6; | |||
| padding-bottom: 1rem; | |||
| } | |||
| .form-group { | |||
| margin-bottom: 1.5rem; | |||
| } | |||
| .input-group { | |||
| margin-top: 0.5rem; | |||
| } | |||
| .Image-Preview { | |||
| min-width: 100px; | |||
| max-height: 100px; | |||
| max-width: 100px; | |||
| object-fit: cover; | |||
| border-radius: 8px; | |||
| border: 1px solid #ddd; | |||
| } | |||
| .delete-btn { | |||
| display: flex; | |||
| align-items: center; | |||
| padding: 3px; | |||
| font-size: 10px; | |||
| background-color: #e74c3c; | |||
| color: white; | |||
| border: none; | |||
| border-radius: 5px; | |||
| cursor: pointer; | |||
| transition: background-color 0.3s ease, transform 0.2s ease; | |||
| gap: 8px; /* Space between icon and text */ | |||
| margin-right: 100px; | |||
| } | |||
| .delete-btn:hover { | |||
| background-color: #c0392b; | |||
| transform: scale(1.05); | |||
| } | |||
| .delete-btn:active { | |||
| background-color: #a93226; | |||
| } | |||
| .delete-btn:focus { | |||
| outline: none; | |||
| } | |||
| .color-picker-wrapper { | |||
| position: relative; | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| .color-picker { | |||
| width: 300px; | |||
| height: 45px; | |||
| padding: 0; | |||
| border-radius: 10px; | |||
| border: none; | |||
| cursor: pointer; | |||
| } | |||
| .color-display { | |||
| width: 30px; | |||
| height: 30px; | |||
| margin-left: 10px; | |||
| border-radius: 50%; | |||
| border: 2px solid #ccc; | |||
| display: inline-block; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,164 @@ | |||
| <template> | |||
| <div | |||
| class="tab-pane fade show active" | |||
| id="user-set-profile" | |||
| role="tabpanel" | |||
| aria-labelledby="user-set-profile-tab" | |||
| > | |||
| <BCard no-body> | |||
| <BCardHeader> | |||
| <h5>اطلاعات حساب</h5> | |||
| </BCardHeader> | |||
| <BCardBody class="card-body"> | |||
| <BRow> | |||
| <BCol class="col-sm-6"> | |||
| <div class="mb-3"> | |||
| <label class="form-label">نام</label> | |||
| <input | |||
| type="text" | |||
| v-model="name" | |||
| :class="{ 'is-invalid': errors.name }" | |||
| class="form-control" | |||
| placeholder="نام" | |||
| @input="clearError('name')" | |||
| /> | |||
| <small v-if="errors.name" class="text-danger"> | |||
| {{ errors.name }} | |||
| </small> | |||
| </div> | |||
| </BCol> | |||
| <BCol class="col-sm-6"> | |||
| <div class="mb-3"> | |||
| <label class="form-label">شماره تماس</label> | |||
| <input | |||
| type="text" | |||
| :class="{ 'is-invalid': errors.mobile }" | |||
| v-model="mobile" | |||
| class="form-control" | |||
| placeholder="شماره تماس" | |||
| @input="clearError('mobile')" | |||
| /> | |||
| <small v-if="errors.mobile" class="text-danger"> | |||
| {{ errors.mobile }} | |||
| </small> | |||
| </div> | |||
| </BCol> | |||
| <BCol class="col-sm-6"> | |||
| <div class="mb-3"> | |||
| <label class="form-label">رمز عبور</label> | |||
| <input | |||
| type="password" | |||
| v-model="password" | |||
| class="form-control" | |||
| placeholder="رمز عبور" | |||
| /> | |||
| </div> | |||
| </BCol> | |||
| </BRow> | |||
| </BCardBody> | |||
| <BcardFooter> | |||
| <div class="text-center btn-page"> | |||
| <div @click="editProfile" class="btn btn-primary" :disabled="loading"> | |||
| <span | |||
| v-if="loading" | |||
| class="spinner-border spinner-border-sm" | |||
| role="status" | |||
| aria-hidden="true" | |||
| ></span> | |||
| <span v-if="!loading">ویرایش</span> | |||
| </div> | |||
| </div> | |||
| </BcardFooter> | |||
| </BCard> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import { toRef, watch, ref } from "vue"; | |||
| import moment from "jalali-moment"; | |||
| export default { | |||
| props: { | |||
| user: { | |||
| type: String, | |||
| required: true, | |||
| }, | |||
| }, | |||
| setup(props) { | |||
| const loading = ref(false); | |||
| const errors = ref({}); | |||
| const localUser = toRef(props.user); | |||
| const name = ref(props.user.name); | |||
| const mobile = ref(props.user.mobile); | |||
| const password = ref(); | |||
| const validateForm = () => { | |||
| errors.value = {}; | |||
| if (!name.value) errors.value.name = "وارد کردن نام ضروری می باشد"; | |||
| if (!mobile.value) errors.value.mobile = "وارد کردن موبایل ضروری می باشد"; | |||
| return Object.keys(errors.value).length === 0; | |||
| }; | |||
| const clearError = (field) => { | |||
| errors.value[field] = ""; | |||
| }; | |||
| const convertToJalali = (date) => { | |||
| return moment(date, "YYYY-MM-DD HH:mm:ss") | |||
| .locale("fa") | |||
| .format("YYYY/MM/DD"); | |||
| }; | |||
| watch( | |||
| () => props.user, | |||
| (newVal) => { | |||
| if (newVal) { | |||
| localUser.value = newVal; | |||
| name.value = localUser.value.name; | |||
| mobile.value = localUser.value.mobile; | |||
| } | |||
| } | |||
| ); | |||
| const editProfile = () => { | |||
| if (!validateForm()) return; | |||
| loading.value = true; | |||
| const formData = new FormData(); | |||
| formData.append("name", name.value); | |||
| formData.append("mobile", mobile.value); | |||
| if (password.value) { | |||
| formData.append("password", password.value); | |||
| } | |||
| ApiServiece.put("client/me", formData) | |||
| .then(() => { | |||
| toast.success("!پروفایل با موفقیت ویرایش شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| loading.value = false; | |||
| }) | |||
| .catch(() => { | |||
| loading.value = false; | |||
| toast.error("!مشکلی در ویرایش پروفایل ایجاد شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }); | |||
| }; | |||
| return { | |||
| convertToJalali, | |||
| localUser, | |||
| name, | |||
| mobile, | |||
| editProfile, | |||
| clearError, | |||
| errors, | |||
| password, | |||
| loading, | |||
| }; | |||
| }, | |||
| }; | |||
| </script> | |||
| @@ -0,0 +1,221 @@ | |||
| <template> | |||
| <div | |||
| class="tab-pane fade" | |||
| id="user-set-information" | |||
| role="tabpanel" | |||
| aria-labelledby="user-set-information-tab" | |||
| > | |||
| <BCard no-body> | |||
| <BCardHeader> | |||
| <h5>اضافه کردن آدرس</h5> | |||
| </BCardHeader> | |||
| <BCardBody class="card-body"> | |||
| <BRow> | |||
| <BCol class="col-sm-6"> | |||
| <div class="mb-3"> | |||
| <label class="form-label">طول جغرافیایی</label> | |||
| <input | |||
| type="text" | |||
| v-model="lat" | |||
| class="form-control" | |||
| :class="{ 'is-invalid': errors.lat }" | |||
| placeholder="طول جغرافیای " | |||
| @input="clearError('lat')" | |||
| /> | |||
| <small v-if="errors.lat" class="text-danger"> | |||
| {{ errors.lat }} | |||
| </small> | |||
| </div> | |||
| </BCol> | |||
| <BCol class="col-sm-6"> | |||
| <div class="mb-3"> | |||
| <label class="form-label">عرض جغرافیایی</label> | |||
| <input | |||
| type="text" | |||
| v-model="long" | |||
| class="form-control" | |||
| placeholder="عرض جغرافیایی" | |||
| :class="{ 'is-invalid': errors.long }" | |||
| @input="clearError('long')" | |||
| /> | |||
| <small v-if="errors.long" class="text-danger"> | |||
| {{ errors.long }} | |||
| </small> | |||
| </div> | |||
| </BCol> | |||
| <BCol class="col-sm-6"> | |||
| <div class="mb-3"> | |||
| <label class="form-label">استان</label> | |||
| <input | |||
| v-model="city" | |||
| type="text" | |||
| class="form-control" | |||
| :class="{ 'is-invalid': errors.city }" | |||
| placeholder="استان" | |||
| @input="clearError('city')" | |||
| /> | |||
| <small v-if="errors.city" class="text-danger"> | |||
| {{ errors.city }} | |||
| </small> | |||
| </div> | |||
| </BCol> | |||
| <BCol class="col-sm-6"> | |||
| <div class="mb-3"> | |||
| <label class="form-label">شهر</label> | |||
| <input | |||
| v-model="town" | |||
| type="text" | |||
| class="form-control" | |||
| placeholder="شهر" | |||
| :class="{ 'is-invalid': errors.town }" | |||
| @input="clearError('town')" | |||
| /> | |||
| <small v-if="errors.town" class="text-danger"> | |||
| {{ errors.town }} | |||
| </small> | |||
| </div> | |||
| </BCol> | |||
| <BCol class="col-sm-6"> | |||
| <div class="mb-3"> | |||
| <label class="form-label">کد پستی</label> | |||
| <input | |||
| v-model="postcode" | |||
| type="text" | |||
| class="form-control" | |||
| placeholder="کد پستی" | |||
| @input="clearError('postcode')" | |||
| :class="{ 'is-invalid': errors.postcode }" | |||
| /> | |||
| <small v-if="errors.postcode" class="text-danger"> | |||
| {{ errors.postcode }} | |||
| </small> | |||
| </div> | |||
| </BCol> | |||
| <BCol class="col-sm-6"> | |||
| <div class="mb-3"> | |||
| <label class="form-label">عنوان</label> | |||
| <input | |||
| v-model="title" | |||
| type="text" | |||
| class="form-control" | |||
| placeholder="کد پستی" | |||
| /> | |||
| </div> | |||
| </BCol> | |||
| <BCol class="col-sm-12"> | |||
| <div class="mb-3"> | |||
| <label class="form-label">آدرس</label> | |||
| <textarea | |||
| v-model="address" | |||
| class="form-control" | |||
| placeholder="آدرس" | |||
| @input="clearError('address')" | |||
| :class="{ 'is-invalid': errors.address }" | |||
| > | |||
| </textarea> | |||
| <small v-if="errors.address" class="text-danger"> | |||
| {{ errors.address }} | |||
| </small> | |||
| </div> | |||
| </BCol> | |||
| </BRow> | |||
| </BCardBody> | |||
| <BcardFooter> | |||
| <div class="text-center btn-page"> | |||
| <div | |||
| @click="createAddress" | |||
| class="btn btn-primary" | |||
| :disabled="loading" | |||
| > | |||
| <span | |||
| v-if="loading" | |||
| class="spinner-border spinner-border-sm" | |||
| role="status" | |||
| aria-hidden="true" | |||
| ></span> | |||
| <span v-if="!loading">ثبت</span> | |||
| </div> | |||
| </div> | |||
| </BcardFooter> | |||
| </BCard> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import { ref } from "vue"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| export default { | |||
| setup() { | |||
| const loading = ref(false); | |||
| const title = ref(); | |||
| const address = ref(); | |||
| const lat = ref(); | |||
| const long = ref(); | |||
| const city = ref(); | |||
| const town = ref(); | |||
| const postcode = ref(); | |||
| const errors = ref({}); | |||
| const validateForm = () => { | |||
| errors.value = {}; | |||
| if (!address.value) | |||
| errors.value.address = "وارد کردن آدرس الزامی می باشد"; | |||
| if (!lat.value) | |||
| errors.value.lat = "وارد کردن عرض جغرافیایی الزامی می باشد"; | |||
| if (!long.value) | |||
| errors.value.long = "وارد کردن طول جغرافیایی الزامی می باشد"; | |||
| if (!city.value) errors.value.city = "وارد کردن استان الزامی می باشد"; | |||
| if (!town.value) errors.value.town = "وارد کردن استان الزامی می باشد"; | |||
| if (!postcode.value) | |||
| errors.value.postcode = "وارد کردن کد پستی الزامی می باشد"; | |||
| return Object.keys(errors.value).length === 0; | |||
| }; | |||
| const clearError = (field) => { | |||
| errors.value[field] = ""; | |||
| }; | |||
| const createAddress = () => { | |||
| if (!validateForm()) return; | |||
| loading.value = true; | |||
| const formData = new FormData(); | |||
| formData.append("title", title.value); | |||
| formData.append("address", address.value); | |||
| formData.append("location[lat]", lat.value); | |||
| formData.append("location[lng]", long.value); | |||
| formData.append("city", city.value); | |||
| formData.append("town", town.value); | |||
| formData.append("postcode", postcode.value); | |||
| ApiServiece.post(`wholesale/my-addresses`, formData) | |||
| .then(() => { | |||
| loading.value = false; | |||
| toast.success("!آدرس با موفقیت اضافه شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .catch(() => { | |||
| loading.value = false; | |||
| toast.error("!مشکلی در ایجاد آدرس بوجود آمد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }); | |||
| }; | |||
| return { | |||
| clearError, | |||
| errors, | |||
| createAddress, | |||
| address, | |||
| lat, | |||
| long, | |||
| postcode, | |||
| town, | |||
| city, | |||
| title, | |||
| loading, | |||
| }; | |||
| }, | |||
| }; | |||
| </script> | |||
| @@ -0,0 +1,146 @@ | |||
| <script> | |||
| import moment from "jalali-moment"; | |||
| import { watch, toRef } from "vue"; | |||
| export default { | |||
| name: "PRODUCT-LIST", | |||
| props: { | |||
| addresses: { | |||
| type: String, | |||
| required: true, | |||
| }, | |||
| }, | |||
| setup(props) { | |||
| const convertToJalali = (date) => { | |||
| return moment(date, "YYYY-MM-DD HH:mm:ss") | |||
| .locale("fa") | |||
| .format("YYYY/MM/DD"); | |||
| }; | |||
| const localAddresses = toRef(props.addresses); | |||
| watch( | |||
| () => props.addresses, | |||
| (newVal) => (localAddresses.value = newVal) | |||
| ); | |||
| return { | |||
| localAddresses, | |||
| convertToJalali, | |||
| }; | |||
| }, | |||
| }; | |||
| </script> | |||
| <template> | |||
| <div | |||
| class="tab-pane fade" | |||
| id="addressList" | |||
| role="tabpanel" | |||
| aria-labelledby="user-list-address-tab" | |||
| > | |||
| <BRow> | |||
| <BCol class="col-sm-12"> | |||
| <BCard no-body class="table-card"> | |||
| <BCardBody> | |||
| <div class="table-responsive"> | |||
| <table | |||
| v-if="localAddresses.length > 0" | |||
| class="table table-hover tbl-product" | |||
| id="pc-dt-simple" | |||
| > | |||
| <thead> | |||
| <tr> | |||
| <th>آدرس</th> | |||
| <th>استان</th> | |||
| <th class="text-end">شهر</th> | |||
| <th class="text-end">کد پستی</th> | |||
| <th class="text-center">عنوان</th> | |||
| <th class="text-center">تاریخ ایجاد</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| <!-- Iterate over localAddresses --> | |||
| <tr v-for="addres in localAddresses" :key="addres.id"> | |||
| <td> | |||
| <BRow> | |||
| <BCol> | |||
| <h6 class="mb-1"> | |||
| {{ addres.address.slice(0, 20) }}... | |||
| </h6> | |||
| <p class="text-muted f-12 mb-0"> | |||
| عرض جغرافیایی: | |||
| {{ addres.location.lat }} | طول جغرافیایی: | |||
| {{ addres.location.lng }} | |||
| </p> | |||
| </BCol> | |||
| </BRow> | |||
| </td> | |||
| <td>{{ addres.city }}</td> | |||
| <td class="text-end">{{ addres.town }}</td> | |||
| <td class="text-end">{{ addres.postcode }}</td> | |||
| <td v-if="!addres.title" class="text-center"> | |||
| <i | |||
| class="ph-duotone ph-x-circle text-danger f-24" | |||
| data-bs-toggle="tooltip" | |||
| data-bs-title="danger" | |||
| ></i> | |||
| </td> | |||
| <td v-if="addres.title" class="text-center"> | |||
| {{ addres.title }} | |||
| </td> | |||
| <td class="text-center"> | |||
| <div class="prod-action-links"> | |||
| <ul class="list-inline me-auto mb-0"> | |||
| <li | |||
| class="list-inline-item align-bottom" | |||
| data-bs-toggle="tooltip" | |||
| title="Edit" | |||
| > | |||
| <router-link | |||
| to="/add-product" | |||
| class="avtar avtar-xs btn-link-success btn-pc-default" | |||
| > | |||
| <i class="ti ti-edit-circle f-18"></i> | |||
| </router-link> | |||
| </li> | |||
| <li | |||
| class="list-inline-item align-bottom" | |||
| data-bs-toggle="tooltip" | |||
| title="Delete" | |||
| > | |||
| <a | |||
| href="#" | |||
| class="avtar avtar-xs btn-link-danger btn-pc-default" | |||
| > | |||
| <i class="ti ti-trash f-18"></i> | |||
| </a> | |||
| </li> | |||
| </ul> | |||
| </div> | |||
| </td> | |||
| </tr> | |||
| </tbody> | |||
| </table> | |||
| <!-- Message when no addresses are available --> | |||
| <div v-else class="text-center p-5"> | |||
| <BRow> | |||
| <BCol | |||
| class="col-lg-12 d-flex justify-content-center align-items-center" | |||
| > | |||
| <p class="text-muted">هیچ آدرسی برای نمایش وجود ندارد.</p> | |||
| </BCol> | |||
| <BCol | |||
| class="col-lg-10 d-flex justify-content-center align-items-center ms-5" | |||
| > | |||
| <img | |||
| class="img-fluid" | |||
| src="@/assets/images/pages/img-connection-lost.png" | |||
| alt="img" | |||
| /> | |||
| </BCol> | |||
| </BRow> | |||
| </div> | |||
| </div> | |||
| </BCardBody> | |||
| </BCard> | |||
| </BCol> | |||
| </BRow> | |||
| </div> | |||
| </template> | |||
| @@ -1,4 +1,5 @@ | |||
| <script> | |||
| import rightBar from "./right-bar.vue"; | |||
| export default { | |||
| name: "NAVBAR", | |||
| components: {}, | |||
| @@ -7,10 +8,15 @@ export default { | |||
| isFullscreen: false, | |||
| isSidebarHidden: false, | |||
| currentMode: "light", | |||
| rightBar, | |||
| show: false, | |||
| }; | |||
| }, | |||
| mounted() { | |||
| const savedMode = localStorage.getItem("themeMode"); | |||
| if (savedMode) { | |||
| this.changeMode(savedMode); // Apply the saved theme mode | |||
| } | |||
| // Add event listener for keydown events | |||
| document.addEventListener("keydown", this.handleKeyDown); | |||
| }, | |||
| @@ -43,6 +49,7 @@ export default { | |||
| }, | |||
| changeMode(mode) { | |||
| this.currentMode = mode; | |||
| localStorage.setItem("themeMode", mode); | |||
| if (mode === "dark") { | |||
| document.body.setAttribute("data-pc-theme", "dark"); | |||
| document.body.setAttribute("data-topbar", "dark"); | |||
| @@ -90,8 +97,45 @@ export default { | |||
| <i class="ti ti-menu-2"></i> | |||
| </a> | |||
| </li> | |||
| </ul> | |||
| </div> | |||
| <BDropdown | |||
| variant="transparent" | |||
| class="pc-h-item" | |||
| toggle-class="text-reset dropdown-btn pc-head-link arrow-none p-0" | |||
| menu-class="dropdown-menu-end" | |||
| aria-haspopup="true" | |||
| :offset="{ alignmentAxis: -150, crossAxis: 0, mainAxis: 20 }" | |||
| > | |||
| <template #button-content | |||
| ><span class="text-muted pc-head-link" | |||
| ><i | |||
| :class=" | |||
| currentMode === 'dark' | |||
| ? 'ph-duotone ph-moon' | |||
| : currentMode === 'light' | |||
| ? 'ph-duotone ph-sun-dim' | |||
| : 'ph-duotone ph-cpu' | |||
| " | |||
| ></i | |||
| ></span> | |||
| </template> | |||
| <a href="#" class="dropdown-item" @click.prevent="changeMode('dark')"> | |||
| <i class="ph-duotone ph-moon"></i> | |||
| <span>تاریک</span> | |||
| </a> | |||
| <a | |||
| href="#" | |||
| class="dropdown-item" | |||
| @click.prevent="changeMode('light')" | |||
| > | |||
| <i class="ph-duotone ph-sun-dim"></i> | |||
| <span>روشن</span> | |||
| </a> | |||
| </BDropdown> | |||
| <rightBar /> | |||
| </div> | |||
| </template> | |||
| @@ -1,3 +1,4 @@ | |||
| // پنل ادمین | |||
| export default [ | |||
| { | |||
| path: "/", | |||
| @@ -165,6 +166,16 @@ export default [ | |||
| }, | |||
| component: () => import("../views/live-preview/pages/orders/orders.vue"), | |||
| }, | |||
| { | |||
| path: "/allOrdersItems", | |||
| name: "allOrdersItems", | |||
| meta: { | |||
| title: "سفارسات", | |||
| requiresAuth: true, | |||
| }, | |||
| component: () => | |||
| import("../views/live-preview/pages/orders/allOrdersItems.vue"), | |||
| }, | |||
| { | |||
| path: "/singleOrder/:id", | |||
| name: "singleOrder", | |||
| @@ -175,6 +186,16 @@ export default [ | |||
| component: () => | |||
| import("../views/live-preview/pages/orders/singleOrder.vue"), | |||
| }, | |||
| { | |||
| path: "/approvedOrders", | |||
| name: "approvedOrders", | |||
| meta: { | |||
| title: "سفارسات", | |||
| requiresAuth: true, | |||
| }, | |||
| component: () => | |||
| import("../views/live-preview/pages/orders/approvedOrders.vue"), | |||
| }, | |||
| { | |||
| path: "/comments", | |||
| name: "comments", | |||
| @@ -203,6 +224,64 @@ export default [ | |||
| }, | |||
| component: () => import("../views/live-preview/pages/faqs/editFaqs.vue"), | |||
| }, | |||
| { | |||
| path: "/profile", | |||
| name: "profile", | |||
| meta: { | |||
| title: "پروفایل", | |||
| requiresAuth: true, | |||
| }, | |||
| component: () => import("../views/live-preview/pages/profile/profile.vue"), | |||
| }, | |||
| { | |||
| path: "/banners", | |||
| name: "banners", | |||
| meta: { | |||
| title: "بنر ", | |||
| requiresAuth: true, | |||
| }, | |||
| component: () => import("../views/live-preview/pages/banners/banners.vue"), | |||
| }, | |||
| { | |||
| path: "/addBanner", | |||
| name: "addBanner", | |||
| meta: { | |||
| title: "بنر ", | |||
| requiresAuth: true, | |||
| }, | |||
| component: () => | |||
| import("../views/live-preview/pages/banners/addBanner.vue"), | |||
| }, | |||
| { | |||
| path: "/editBanner/:id", | |||
| name: "editBanner", | |||
| meta: { | |||
| title: "بنر ", | |||
| requiresAuth: true, | |||
| }, | |||
| component: () => | |||
| import("../views/live-preview/pages/banners/editBanner.vue"), | |||
| }, | |||
| { | |||
| path: "/idenities", | |||
| name: "idenities", | |||
| meta: { | |||
| title: "مشخضات", | |||
| requiresAuth: true, | |||
| }, | |||
| component: () => | |||
| import("../views/live-preview/pages/identity/idenities.vue"), | |||
| }, | |||
| { | |||
| path: "/calls", | |||
| name: "calls", | |||
| meta: { | |||
| title: "پیگیری ها", | |||
| requiresAuth: true, | |||
| }, | |||
| component: () => import("../views/live-preview/pages/calls/calls.vue"), | |||
| }, | |||
| { | |||
| path: "/", | |||
| name: "live-preview", | |||
| @@ -39,7 +39,7 @@ export default { | |||
| const getAttributes = () => { | |||
| filterLoading.value = true; | |||
| ApiServiece.get( | |||
| `admin/attribute-values?title=${encodeURIComponent( | |||
| `admin/attribute-values?attribute_id=1&title=${encodeURIComponent( | |||
| searchQuery.value || "" | |||
| )}&code=${encodeURIComponent(searchQuery.value || "")} | |||
| &paginate=${paginate.value || 10}&page=${page.value || 1} | |||
| @@ -156,6 +156,7 @@ export default { | |||
| const getAttributeValues = () => { | |||
| ApiServiece.get(`admin/attributes`).then((resp) => { | |||
| console.log(resp) | |||
| attributeValues.value = resp.data.data; | |||
| }); | |||
| }; | |||
| @@ -195,7 +196,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 bg-primary text-white" | |||
| class="card-header d-flex justify-content-between align-items-center p-3 " | |||
| dir="rtl" | |||
| > | |||
| <div class="d-flex align-items-center"> | |||
| @@ -0,0 +1,539 @@ | |||
| <template> | |||
| <Layout> | |||
| <BRow> | |||
| <BCol sm="12"> | |||
| <BCard no-body> | |||
| <BCardHeader> | |||
| <div class="d-flex justify-content-between align-items-center"> | |||
| <h5>ایجاد بنر</h5> | |||
| <!-- Help and Modal buttons --> | |||
| <div> | |||
| <button | |||
| data-bs-toggle="modal" | |||
| data-bs-target="#mainPageBanner" | |||
| class="btn btn-info btn-sm mx-2" | |||
| @click="showHelp" | |||
| > | |||
| <i class="fa fa-question-circle"></i> راهنمایی بنر صفحه اصلی | |||
| </button> | |||
| <button | |||
| data-bs-toggle="modal" | |||
| data-bs-target="#catBanner" | |||
| class="btn btn-warning btn-sm mx-2" | |||
| @click="showModal" | |||
| > | |||
| <i class="fa fa-info-circle"></i> راهنمایی بنر دسته ها | |||
| </button> | |||
| </div> | |||
| </div> | |||
| </BCardHeader> | |||
| <BCardBody> | |||
| <BRow class="g-3"> | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">عنوان</label> | |||
| <input | |||
| type="text" | |||
| v-model="title" | |||
| class="form-control" | |||
| placeholder="عنوان بنر" | |||
| :class="{ 'is-invalid': errors.title }" | |||
| @input="clearError('title')" | |||
| /> | |||
| </div> | |||
| <small v-if="errors.title" class="text-danger"> | |||
| {{ errors.title }} | |||
| </small> | |||
| </BCol> | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">پنل نمایش</label> | |||
| <select | |||
| class="form-select" | |||
| aria-label="Default select example" | |||
| v-model="pannel" | |||
| @change="clearError('pannel')" | |||
| :class="{ 'is-invalid': errors.pannel }" | |||
| placeholder="انتخاب پنل" | |||
| > | |||
| <option value="wholesale">پنل عمده فروشی</option> | |||
| <option value="web">وب سایت واپلیکیشن</option> | |||
| </select> | |||
| </div> | |||
| <small v-if="errors.pannel" class="text-danger"> | |||
| {{ errors.pannel }} | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="pannel === 'web'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">نمایش در</label> | |||
| <select | |||
| v-model="pageType" | |||
| :class="{ 'is-invalid': errors.pageType }" | |||
| class="form-select" | |||
| aria-label="Default select example" | |||
| placeholder="انتخاب صفحه" | |||
| @change="clearError('pageType')" | |||
| > | |||
| <option value="main">صفحه اصلی</option> | |||
| <option value="cat">صفحه دسته</option> | |||
| </select> | |||
| </div> | |||
| <small v-if="errors.pageType" class="text-danger"> | |||
| {{ errors.pageType }} | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="pageType === 'cat' && pannel === 'web'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">صفحه دسته</label> | |||
| <select | |||
| v-model="selectedCatPage" | |||
| class="form-select" | |||
| :class="{ 'is-invalid': errors.selectedCatPage }" | |||
| aria-label="Default select example" | |||
| @change="clearError('selectedCatPage')" | |||
| placeholder="انتخاب صفحه دسته" | |||
| > | |||
| <option :value="cat.id" v-for="cat in cats" :key="cat.id"> | |||
| {{ cat.title }} | |||
| </option> | |||
| </select> | |||
| </div> | |||
| <small v-if="errors.selectedCatPage" class="text-danger"> | |||
| {{ errors.selectedCatPage }} | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="pannel != 'wholesale'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">انتخاب صفحه فرود</label> | |||
| <select | |||
| class="form-select" | |||
| aria-label="Default select example" | |||
| v-model="landingType" | |||
| @change="clearError('landingType')" | |||
| :class="{ 'is-invalid': errors.landingType }" | |||
| placeholder="انتخاب صفحه فرود" | |||
| > | |||
| <option value="product">صفحه محصولات</option> | |||
| <option value="cat">صفحه دسته ها</option> | |||
| </select> | |||
| </div> | |||
| <small v-if="errors.landingType" class="text-danger"> | |||
| {{ errors.landingType }} | |||
| </small> | |||
| </BCol> | |||
| <BCol | |||
| v-if="landingType === 'product'" | |||
| sm="6" | |||
| class="mt-3" | |||
| style="margin-top: 30px" | |||
| > | |||
| <label for="token">صفحه محصول</label> | |||
| <Select2 | |||
| id="token" | |||
| v-model="selectedLandingProduct" | |||
| :options="formattedUsers" | |||
| :settings="{ settingOption: value, settingOption: value }" | |||
| style="height: 60px" | |||
| /> | |||
| <small v-if="errors.selectedLandingProduct" class="text-danger"> | |||
| {{ errors.selectedLandingProduct }} | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="landingType === 'cat'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">صفحه کدام دسته ؟</label> | |||
| <select | |||
| class="form-select" | |||
| aria-label="Default select example" | |||
| v-model="selectedLandingCat" | |||
| @change="clearError('selectedLandingCat')" | |||
| :class="{ 'is-invalid': errors.selectedLandingCat }" | |||
| placeholder="انتخاب صفحه دسته" | |||
| > | |||
| <option :value="cat.id" v-for="cat in cats" :key="cat.id"> | |||
| {{ cat.title }} | |||
| </option> | |||
| </select> | |||
| </div> | |||
| <small v-if="errors.selectedLandingCat" class="text-danger"> | |||
| {{ errors.selectedLandingCat }} | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="pageType === 'main' && pannel === 'web'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">موقعیت در صفحه اصلی</label> | |||
| <select | |||
| class="form-select" | |||
| aria-label="Default select example" | |||
| v-model="selectedLoc" | |||
| @change="clearError('selectedLoc')" | |||
| :class="{ 'is-invalid': errors.selectedLoc }" | |||
| placeholder="موقعیت بنر" | |||
| > | |||
| <option value="A">َA-Slideshow</option> | |||
| <option value="B">B-Banner</option> | |||
| <option value="C">C-Banner</option> | |||
| <option value="D">D-Banner</option> | |||
| <option value="F">E-Banner</option> | |||
| <option value="G">F-Banner</option> | |||
| <option value="H">G-Banner</option> | |||
| <option value="G">H-Banner</option> | |||
| <option value="I">I-Banner</option> | |||
| <option value="J">J-Banner</option> | |||
| </select> | |||
| </div> | |||
| <small v-if="errors.selectedLoc" class="text-danger"> | |||
| {{ errors.selectedLoc }} | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="pageType === 'cat' && pannel === 'web'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">موقعیت در صفحه دسته ها</label> | |||
| <select | |||
| class="form-select" | |||
| aria-label="Default select example" | |||
| v-model="selectedLoc" | |||
| @change="clearError('selectedLoc')" | |||
| :class="{ 'is-invalid': errors.selectedLoc }" | |||
| placeholder="موقعیت بنر" | |||
| > | |||
| <option value="A">َA-Slideshow</option> | |||
| <option value="B">B-Banner</option> | |||
| <option value="C">C-Banner</option> | |||
| <option value="D">D-Banner</option> | |||
| <option value="E">E-Banner</option> | |||
| <option value="F">F-Banner</option> | |||
| <option value="G">G-Banner</option> | |||
| <option value="H">H-Banner</option> | |||
| <option value="I">I-Banner</option> | |||
| </select> | |||
| </div> | |||
| <small v-if="errors.selectedLoc" class="text-danger"> | |||
| {{ errors.selectedLoc }} | |||
| </small> | |||
| </BCol> | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">تصویر بنر</label> | |||
| <input | |||
| type="file" | |||
| accept="image/*" | |||
| @change="handleImageUpload" | |||
| class="form-control" | |||
| :class="{ 'is-invalid': errors.image }" | |||
| /> | |||
| <div v-if="imagePreview" class="mt-2"> | |||
| <img | |||
| :src="imagePreview" | |||
| alt="Image Preview" | |||
| class="img-fluid rounded shadow-sm Image-Preview" | |||
| /> | |||
| </div> | |||
| <small v-if="errors.image" class="text-danger"> | |||
| {{ errors.image }} | |||
| </small> | |||
| </div> | |||
| </BCol> | |||
| </BRow> | |||
| </BCardBody> | |||
| <BCardFooter> | |||
| <div class="d-flex justify-content-center"> | |||
| <div class="text-center"> | |||
| <button | |||
| type="submit" | |||
| class="btn btn-primary" | |||
| @click.prevent="submitForm" | |||
| :disabled="loading" | |||
| > | |||
| <span v-if="loading"> | |||
| <i class="fa fa-spinner fa-spin"></i> بارگذاری... | |||
| </span> | |||
| <span v-else>ایجاد</span> | |||
| </button> | |||
| </div> | |||
| </div> | |||
| </BCardFooter> | |||
| </BCard> | |||
| </BCol> | |||
| </BRow> | |||
| <mainPageBanner /> | |||
| <catBanner /> | |||
| </Layout> | |||
| </template> | |||
| <script> | |||
| import Select2 from "vue3-select2-component"; | |||
| import catBanner from "@/components/modals/helperModals/catBanner.vue"; | |||
| import mainPageBanner from "@/components/modals/helperModals/mainPageBanner.vue"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import { ref, onMounted, computed } from "vue"; | |||
| import Layout from "@/layout/custom.vue"; | |||
| export default { | |||
| name: "SAMPLE-PAGE", | |||
| components: { | |||
| Layout, | |||
| mainPageBanner, | |||
| catBanner, | |||
| Select2, | |||
| }, | |||
| setup() { | |||
| const title = ref(); | |||
| const pageType = ref(); | |||
| const products = ref([]); | |||
| const cats = ref([]); | |||
| const landingType = ref(); | |||
| const selectedCatPage = ref(); | |||
| const selectedLandingCat = ref(); | |||
| const selectedLandingProduct = ref(); | |||
| const selectedLoc = ref(); | |||
| const pannel = ref(); | |||
| const image = ref(); | |||
| const imagePreview = ref(); | |||
| const loading = ref(false); | |||
| const errors = ref({}); | |||
| const getCats = () => { | |||
| ApiServiece.get(`admin/categories`) | |||
| .then((resp) => { | |||
| cats.value = resp.data.data; | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| }); | |||
| }; | |||
| const getProduct = () => { | |||
| ApiServiece.get(`admin/products`) | |||
| .then((resp) => { | |||
| products.value = resp.data.data; | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| }); | |||
| }; | |||
| const handleImageUpload = (event) => { | |||
| const file = event.target.files[0]; | |||
| if (file) { | |||
| errors.value.image = null; | |||
| image.value = file; | |||
| const reader = new FileReader(); | |||
| reader.onload = () => { | |||
| imagePreview.value = reader.result; | |||
| }; | |||
| reader.readAsDataURL(file); | |||
| } | |||
| }; | |||
| const formattedUsers = computed(() => { | |||
| return products.value.map((product) => ({ | |||
| id: product.id, | |||
| text: product.title, | |||
| })); | |||
| }); | |||
| const validateForm = () => { | |||
| errors.value = {}; | |||
| if (!title.value) errors.value.title = "وارد کردن عنوان بنر الزامی است"; | |||
| if (!pageType.value && pannel.value != "wholesale") | |||
| errors.value.pageType = "مشخص کنید بنر کجا نشان داده شود"; | |||
| if (pageType.value === "cat" && !selectedCatPage.value) | |||
| errors.value.selectedCatPage = "صفحه دسته را انتخاب کنید"; | |||
| if (!landingType.value && pannel.value != 'wholesale') | |||
| errors.value.landingType = "صفحه فرود را انتخاب نمایید"; | |||
| if ( | |||
| landingType.value === "cat" && | |||
| !selectedLandingCat.value && | |||
| pannel.value != "wholesale" | |||
| ) | |||
| errors.value.selectedLandingCat = "صفحه فرود دسته را انتخاب کنید"; | |||
| if ( | |||
| landingType.value === "product" && | |||
| !selectedLandingProduct.value && | |||
| pannel.value != "wholesale" | |||
| ) | |||
| errors.value.selectedLandingProduct = "صفحه فرود محصول را انتخاب کنید"; | |||
| if (!selectedLoc.value && pannel.value != "wholesale") | |||
| errors.value.selectedLoc = "موقعیت بنر را انتخاب کنید"; | |||
| if (!pannel.value) errors.value.pannel = "پنل نمایش بنر را انتخاب کنید"; | |||
| if (!image.value) errors.value.image = "عکس بنر را وارد نمایید"; | |||
| return Object.keys(errors.value).length === 0; | |||
| }; | |||
| const clearError = (field) => { | |||
| errors.value[field] = ""; | |||
| }; | |||
| onMounted(() => { | |||
| getCats(); | |||
| getProduct(); | |||
| }); | |||
| const submitForm = () => { | |||
| console.log(errors.value); | |||
| if (!validateForm()) return; | |||
| loading.value = true; | |||
| const formData = new FormData(); | |||
| formData.append("title", title.value); | |||
| if (pageType.value === "cat") { | |||
| formData.append("page_id", selectedCatPage.value); | |||
| } | |||
| if (landingType.value === "product" && pannel.value != "wholesale") { | |||
| formData.append("product_id", selectedLandingProduct.value); | |||
| } | |||
| if (landingType.value === "cat" && pannel.value != "wholesale") { | |||
| formData.append("category_id", selectedLandingCat.value); | |||
| } | |||
| if (selectedLoc.value === "A") { | |||
| formData.append("type", "slider"); | |||
| } | |||
| if (selectedLoc.value !== "A") { | |||
| formData.append("type", "banner"); | |||
| } | |||
| if (pannel.value === "wholesale") { | |||
| formData.append("type", "slider"); | |||
| formData.append("location", "A"); | |||
| } | |||
| if (selectedLoc.value) { | |||
| formData.append("location", selectedLoc.value); | |||
| } | |||
| formData.append("panel", pannel.value); | |||
| formData.append("image", image.value); | |||
| formData.append("sort", 1); | |||
| ApiServiece.post(`admin/banners`, formData, { | |||
| headers: { | |||
| "content-type": "multipart", | |||
| Authorization: `Bearer ${localStorage.getItem("token")}`, | |||
| }, | |||
| }) | |||
| .then((resp) => { | |||
| toast.success("!بنر با موفقیت اضافه شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| console.log(resp); | |||
| loading.value = false; | |||
| }) | |||
| .catch((error) => { | |||
| console.log(error.response.message); | |||
| toast.error(`${error.response.data.message}`, { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }); | |||
| }; | |||
| return { | |||
| cats, | |||
| errors, | |||
| title, | |||
| products, | |||
| selectedCatPage, | |||
| submitForm, | |||
| clearError, | |||
| pageType, | |||
| formattedUsers, | |||
| landingType, | |||
| selectedLandingCat, | |||
| selectedLandingProduct, | |||
| selectedLoc, | |||
| pannel, | |||
| handleImageUpload, | |||
| image, | |||
| imagePreview, | |||
| loading, | |||
| }; | |||
| }, | |||
| }; | |||
| </script> | |||
| <style scoped> | |||
| .ql-editor { | |||
| direction: rtl; | |||
| text-align: right; | |||
| } | |||
| .ql-editor::before { | |||
| content: attr(placeholder); | |||
| direction: rtl !important; | |||
| text-align: right; | |||
| } | |||
| .Image-Preview { | |||
| min-width: 200px; | |||
| max-height: 200px; | |||
| min-height: 200px; | |||
| max-width: 200px; | |||
| object-fit: cover; | |||
| border-radius: 8px; | |||
| border: 1px solid #ddd; | |||
| } | |||
| .delete-btn { | |||
| display: flex; | |||
| align-items: center; | |||
| padding: 3px; | |||
| font-size: 10px; | |||
| background-color: #e74c3c; | |||
| color: white; | |||
| border: none; | |||
| border-radius: 5px; | |||
| cursor: pointer; | |||
| transition: background-color 0.3s ease, transform 0.2s ease; | |||
| gap: 8px; | |||
| margin-right: 200px; | |||
| } | |||
| .delete-btn:hover { | |||
| background-color: #c0392b; | |||
| transform: scale(1.05); | |||
| } | |||
| .delete-btn:active { | |||
| background-color: #a93226; | |||
| } | |||
| .delete-btn:focus { | |||
| outline: none; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,473 @@ | |||
| <script> | |||
| import Layout from "@/layout/custom.vue"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import moment from "jalali-moment"; | |||
| import { onMounted, ref, watch, computed } from "vue"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import Swal from "sweetalert2"; | |||
| import showDescription from "@/components/modals/commonModals/showDescription.vue"; | |||
| export default { | |||
| name: "BORDER", | |||
| components: { | |||
| Layout, | |||
| showDescription, | |||
| }, | |||
| setup() { | |||
| const searchPage = ref(); | |||
| const currentPage = ref(1); | |||
| const totalPages = ref(1); | |||
| const paginate = ref(20); | |||
| const page = ref(1); | |||
| const catDescription = ref(); | |||
| const filterLoading = ref(false); | |||
| const searchQuery = ref(""); | |||
| const banners = ref(); | |||
| const catTitle = ref(); | |||
| const catId = ref(); | |||
| const catParent = ref(); | |||
| const catImage = ref(); | |||
| const convertToJalali = (date) => { | |||
| return moment(date, "YYYY-MM-DD HH:mm:ss") | |||
| .locale("fa") | |||
| .format("YYYY/MM/DD"); | |||
| }; | |||
| watch(searchQuery, () => { | |||
| getBanners(); | |||
| }); | |||
| const getBanners = () => { | |||
| filterLoading.value = true; | |||
| ApiServiece.get( | |||
| `admin/banners?paginate=${paginate.value || 10}&page=${page.value || 1}` | |||
| ) | |||
| .then((resp) => { | |||
| filterLoading.value = false; | |||
| banners.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) { | |||
| for (let i = 1; i <= totalPages.value; i++) { | |||
| pages.push(i); | |||
| } | |||
| } else { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| } | |||
| } | |||
| return pages; | |||
| }); | |||
| function handlePageInput() { | |||
| if (searchPage.value < 1) { | |||
| searchPage.value = 1; | |||
| } else if (searchPage.value > totalPages.value) { | |||
| searchPage.value = totalPages.value; | |||
| } | |||
| if (searchPage.value >= 1 && searchPage.value <= totalPages.value) { | |||
| page.value = searchPage.value; | |||
| } | |||
| } | |||
| const nextPage = () => { | |||
| if (currentPage.value < totalPages.value) { | |||
| page.value++; | |||
| getBanners(); | |||
| } | |||
| }; | |||
| const prevPage = () => { | |||
| if (currentPage.value > 1) { | |||
| page.value--; | |||
| getBanners(); | |||
| } | |||
| }; | |||
| const handleCatUpdated = () => { | |||
| getBanners(); | |||
| }; | |||
| const deleteBanner = (id, title) => { | |||
| Swal.fire({ | |||
| text: `می خواهید بنر ${title} را حذف کنید؟`, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| cancelButtonColor: "#d33", | |||
| confirmButtonText: "بله!", | |||
| cancelButtonText: "خیر", | |||
| }).then((result) => { | |||
| if (result.isConfirmed) { | |||
| ApiServiece.delete(`admin/banners/${id}`) | |||
| .then(() => { | |||
| toast.success("!بنر با موفقیت حذف شد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| getBanners() | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| toast.error("!مشکلی در حذف کردن بنر پیش آمد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| }); | |||
| } | |||
| }); | |||
| }; | |||
| const descriptionModal = (desc) => { | |||
| catDescription.value = desc; | |||
| }; | |||
| watch(searchQuery, () => { | |||
| getBanners(); | |||
| }); | |||
| watch(page, () => { | |||
| getBanners(); | |||
| }); | |||
| const restoreBanner = (id, title) => { | |||
| Swal.fire({ | |||
| text: `می خواهید بنر ${title} را بازیابی کنید؟`, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| cancelButtonColor: "#d33", | |||
| confirmButtonText: "بله!", | |||
| cancelButtonText: "خیر", | |||
| }).then((result) => { | |||
| if (result.isConfirmed) { | |||
| ApiServiece.put(`admin/banners/${id}/restore`) | |||
| .then(() => { | |||
| toast.success("!بنر با موفقیت بازیابی شد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| }) | |||
| .then(() => { | |||
| getBanners(); | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| toast.error("!مشکلی در بازیابی بنر پیش آمد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| }); | |||
| } | |||
| }); | |||
| }; | |||
| onMounted(() => { | |||
| getBanners(); | |||
| }); | |||
| return { | |||
| banners, | |||
| convertToJalali, | |||
| handleCatUpdated, | |||
| restoreBanner, | |||
| deleteBanner, | |||
| searchQuery, | |||
| filterLoading, | |||
| descriptionModal, | |||
| catDescription, | |||
| catTitle, | |||
| catParent, | |||
| catImage, | |||
| catId, | |||
| currentPage, | |||
| totalPages, | |||
| nextPage, | |||
| prevPage, | |||
| page, | |||
| handlePageInput, | |||
| searchPage, | |||
| visiblePages, | |||
| }; | |||
| }, | |||
| }; | |||
| </script> | |||
| <template> | |||
| <Layout> | |||
| <BRow> | |||
| <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" | |||
| 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> | |||
| </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> | |||
| <td>موقعیت</td> | |||
| <td>نوع</td> | |||
| <th>تاریخ ایجاد</th> | |||
| <th>عملیات</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| <tr v-for="banner in banners" :key="banner.id"> | |||
| <td v-if="banner?.image"> | |||
| <img | |||
| :src="banner?.image" | |||
| alt="Brand Image" | |||
| class="Brand-Image" | |||
| /> | |||
| </td> | |||
| <td v-if="!banner.image">ندارد</td> | |||
| <td> | |||
| <div | |||
| type="button" | |||
| data-bs-toggle="modal" | |||
| data-bs-target="#showDescription" | |||
| @click="descriptionModal(call.text)" | |||
| class="subject-box" | |||
| aria-haspopup="dialog" | |||
| aria-controls="showDescription" | |||
| > | |||
| <i class="fas fa-comments subject-icon"></i> | |||
| <span class="subject-text"> | |||
| {{ call.text?.slice(0, 20) }} | |||
| {{ call.text?.length > 20 ? "..." : "" }} | |||
| </span> | |||
| </div> | |||
| </td> | |||
| <td>{{ banner.location }}</td> | |||
| <td v-if="banner.type === 'slider'">اسلایدر</td> | |||
| <td v-if="banner.type === 'banner'">بنر</td> | |||
| <td>{{ convertToJalali(banner.created_at) }}</td> | |||
| <td> | |||
| <router-link | |||
| :to="`/editBanner/${banner?.id}`" | |||
| class="btn btn-sm btn-outline-warning me-1" | |||
| > | |||
| ویرایش | |||
| </router-link> | |||
| <button | |||
| v-if="!banner.deleted_at" | |||
| @click="deleteBanner(banner.id, banner.title)" | |||
| class="btn btn-sm btn-outline-danger" | |||
| > | |||
| حذف | |||
| </button> | |||
| <button | |||
| v-else | |||
| @click="restoreBanner(banner?.id, banner?.title)" | |||
| class="btn btn-sm btn-outline-success" | |||
| > | |||
| بازیابی | |||
| </button> | |||
| </td> | |||
| </tr> | |||
| </tbody> | |||
| </table> | |||
| </div> | |||
| </div> | |||
| <div | |||
| v-else | |||
| class="filter-loader card table-card user-profile-list" | |||
| ></div> | |||
| </div> | |||
| </div> | |||
| <showDescription :desc="catDescription" /> | |||
| </BRow> | |||
| <BRow> | |||
| <BCol sm="12"> | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| class="page-item" | |||
| :class="{ active: currentPage === n }" | |||
| > | |||
| <a | |||
| class="page-link" | |||
| href="javascript:void(0)" | |||
| @click="page = n" | |||
| > | |||
| {{ n }} | |||
| </a> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| </li> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| > | |||
| <span class="page-link" @click="nextPage">بعدی</span> | |||
| </li> | |||
| </ul> | |||
| </nav> | |||
| </div> | |||
| </BCol> | |||
| <BCol sm="4"> | |||
| <div class="ms-0 search-number"> | |||
| <input | |||
| v-model="searchPage" | |||
| type="text" | |||
| class="form-control" | |||
| placeholder="برو به صفحه" | |||
| :max="totalPages" | |||
| min="1" | |||
| @input="handlePageInput" | |||
| /> | |||
| </div> | |||
| </BCol> | |||
| </BRow> | |||
| </Layout> | |||
| </template> | |||
| <style scoped> | |||
| .card { | |||
| transition: transform 0.3s ease; | |||
| } | |||
| .card:hover { | |||
| transform: translateY(-3px); | |||
| } | |||
| .table th, | |||
| .table td { | |||
| vertical-align: middle; | |||
| text-align: center; | |||
| } | |||
| .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; | |||
| } | |||
| .Brand-Image { | |||
| width: 100px; | |||
| height: 100px; | |||
| object-fit: cover; | |||
| border-radius: 8px; | |||
| border: 1px solid #ddd; | |||
| } | |||
| .subject-box { | |||
| padding: 8px 14px; | |||
| background: linear-gradient(135deg, #fff3e0, #ffe0b2); | |||
| color: #ef6c00; | |||
| font-weight: 600; | |||
| border-radius: 10px; | |||
| box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1); | |||
| display: inline-flex; | |||
| align-items: center; | |||
| gap: 8px; | |||
| transition: transform 0.2s ease, box-shadow 0.2s ease; | |||
| } | |||
| .subject-box i { | |||
| color: #ef6c00; | |||
| font-size: 1rem; | |||
| } | |||
| .subject-box:hover { | |||
| transform: translateY(-2px); | |||
| box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.15); | |||
| } | |||
| .search-number { | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| .search-number input { | |||
| width: 150px; | |||
| padding: 0.5rem; | |||
| font-size: 1rem; | |||
| border-radius: 0.375rem; | |||
| margin-bottom: 7px; | |||
| border: 1px solid #ced4da; | |||
| transition: border-color 0.3s ease, box-shadow 0.3s ease; | |||
| } | |||
| .search-number input:focus { | |||
| border-color: #007bff; | |||
| box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.25); | |||
| } | |||
| .search-number input::placeholder { | |||
| color: #6c757d; | |||
| } | |||
| .search-number input:disabled { | |||
| background-color: #f8f9fa; | |||
| cursor: not-allowed; | |||
| } | |||
| .pagination { | |||
| display: flex; | |||
| flex-wrap: wrap; | |||
| gap: 5px; | |||
| } | |||
| .page-item { | |||
| flex: 0 0 auto; | |||
| } | |||
| .page-link { | |||
| cursor: pointer; | |||
| user-select: none; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,577 @@ | |||
| <template> | |||
| <Layout> | |||
| <BRow> | |||
| <BCol sm="12"> | |||
| <BCard no-body> | |||
| <BCardHeader> | |||
| <div class="d-flex justify-content-between align-items-center"> | |||
| <h5>ویرایش بنر</h5> | |||
| <!-- Help and Modal buttons --> | |||
| <div> | |||
| <button | |||
| data-bs-toggle="modal" | |||
| data-bs-target="#mainPageBanner" | |||
| class="btn btn-info btn-sm mx-2" | |||
| @click="showHelp" | |||
| > | |||
| <i class="fa fa-question-circle"></i> راهنمایی بنر صفحه اصلی | |||
| </button> | |||
| <button | |||
| data-bs-toggle="modal" | |||
| data-bs-target="#catBanner" | |||
| class="btn btn-warning btn-sm mx-2" | |||
| @click="showModal" | |||
| > | |||
| <i class="fa fa-info-circle"></i> راهنمایی بنر دسته ها | |||
| </button> | |||
| </div> | |||
| </div> | |||
| </BCardHeader> | |||
| <BCardBody> | |||
| <BRow class="g-3"> | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">عنوان</label> | |||
| <input | |||
| type="text" | |||
| v-model="title" | |||
| class="form-control" | |||
| placeholder="عنوان بنر" | |||
| :class="{ 'is-invalid': errors.title }" | |||
| @input="clearError('title')" | |||
| /> | |||
| </div> | |||
| <small v-if="errors.title" class="text-danger"> | |||
| {{ errors.title }} | |||
| </small> | |||
| </BCol> | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">پنل نمایش</label> | |||
| <select | |||
| class="form-select" | |||
| aria-label="Default select example" | |||
| v-model="pannel" | |||
| @change="clearError('pannel')" | |||
| :class="{ 'is-invalid': errors.pannel }" | |||
| placeholder="انخاب پنل" | |||
| > | |||
| <option value="wholesale">پنل عمده فروشی</option> | |||
| <option value="web">وب سایت واپلیکیشن</option> | |||
| </select> | |||
| </div> | |||
| <small v-if="errors.pannel" class="text-danger"> | |||
| {{ errors.pannel }} | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="pannel === 'web'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">نمایش در</label> | |||
| <select | |||
| v-model="pageType" | |||
| :class="{ 'is-invalid': errors.pageType }" | |||
| class="form-select" | |||
| aria-label="Default select example" | |||
| @change="clearError('pageType')" | |||
| placeholder="انخاب صفحه اصلی" | |||
| > | |||
| <option value="main">صفحه اصلی</option> | |||
| <option value="cat">صفحه دسته</option> | |||
| </select> | |||
| </div> | |||
| <small v-if="errors.pageType" class="text-danger"> | |||
| {{ errors.pageType }} | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="pageType === 'cat' && pannel === 'web'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">صفحه دسته</label> | |||
| <select | |||
| v-model="selectedCatPage" | |||
| class="form-select" | |||
| :class="{ 'is-invalid': errors.selectedCatPage }" | |||
| aria-label="Default select example" | |||
| @change="clearError('selectedCatPage')" | |||
| placeholder="انخاب صفحه دسته" | |||
| > | |||
| <option :value="cat.id" v-for="cat in cats" :key="cat.id"> | |||
| {{ cat.title }} | |||
| </option> | |||
| </select> | |||
| </div> | |||
| <small v-if="errors.selectedCatPage" class="text-danger"> | |||
| {{ errors.selectedCatPage }} | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="pannel == 'web'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">انتخاب صفحه فرود</label> | |||
| <select | |||
| class="form-select" | |||
| aria-label="Default select example" | |||
| v-model="landingType" | |||
| @change="clearError('landingType')" | |||
| :class="{ 'is-invalid': errors.landingType }" | |||
| placeholder="انتخاب صفحه فرود" | |||
| > | |||
| <option value="product">صفحه محصولات</option> | |||
| <option value="cat">صفحه دسته ها</option> | |||
| </select> | |||
| </div> | |||
| <small v-if="errors.landingType" class="text-danger"> | |||
| {{ errors.landingType }} | |||
| </small> | |||
| </BCol> | |||
| <BCol | |||
| v-if="landingType === 'product'" | |||
| sm="6" | |||
| class="mt-3" | |||
| style="margin-top: 30px" | |||
| > | |||
| <label for="token">صفحه محصول</label> | |||
| <Select2 | |||
| id="token" | |||
| v-model="selectedLandingProduct" | |||
| :options="formattedUsers" | |||
| :settings="{ settingOption: value, settingOption: value }" | |||
| style="height: 60px" | |||
| /> | |||
| <small v-if="errors.selectedLandingProduct" class="text-danger"> | |||
| {{ errors.selectedLandingProduct }} | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="landingType === 'cat'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">صفحه کدام دسته ؟</label> | |||
| <select | |||
| class="form-select" | |||
| aria-label="Default select example" | |||
| v-model="selectedLandingCat" | |||
| @change="clearError('selectedLandingCat')" | |||
| :class="{ 'is-invalid': errors.selectedLandingCat }" | |||
| placeholder="انتخاب صفحه دسته" | |||
| > | |||
| <option :value="cat.id" v-for="cat in cats" :key="cat.id"> | |||
| {{ cat.title }} | |||
| </option> | |||
| </select> | |||
| </div> | |||
| <small v-if="errors.selectedLandingCat" class="text-danger"> | |||
| {{ errors.selectedLandingCat }} | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="pageType === 'main' && pannel === 'web'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">موقعیت در صفحه اصلی</label> | |||
| <select | |||
| class="form-select" | |||
| aria-label="Default select example" | |||
| v-model="selectedLoc" | |||
| @change="clearError('selectedLoc')" | |||
| :class="{ 'is-invalid': errors.selectedLoc }" | |||
| placeholder="موقعیت بنر" | |||
| > | |||
| <option value="A">َA-Slideshow</option> | |||
| <option value="B">B-Banner</option> | |||
| <option value="C">C-Banner</option> | |||
| <option value="D">D-Banner</option> | |||
| <option value="F">E-Banner</option> | |||
| <option value="G">F-Banner</option> | |||
| <option value="H">G-Banner</option> | |||
| <option value="G">H-Banner</option> | |||
| <option value="I">I-Banner</option> | |||
| <option value="J">J-Banner</option> | |||
| </select> | |||
| </div> | |||
| <small v-if="errors.selectedLoc" class="text-danger"> | |||
| {{ errors.selectedLoc }} | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="pageType === 'cat' && pannel === 'web'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">موقعیت در صفحه دسته ها</label> | |||
| <select | |||
| class="form-select" | |||
| aria-label="Default select example" | |||
| v-model="selectedLoc" | |||
| @change="clearError('selectedLoc')" | |||
| :class="{ 'is-invalid': errors.selectedLoc }" | |||
| placeholder="موقعیت بنر" | |||
| > | |||
| <option value="A">َA-Slideshow</option> | |||
| <option value="B">B-Banner</option> | |||
| <option value="C">C-Banner</option> | |||
| <option value="D">D-Banner</option> | |||
| <option value="E">E-Banner</option> | |||
| <option value="F">F-Banner</option> | |||
| <option value="G">G-Banner</option> | |||
| <option value="H">H-Banner</option> | |||
| <option value="I">I-Banner</option> | |||
| </select> | |||
| </div> | |||
| <small v-if="errors.selectedLoc" class="text-danger"> | |||
| {{ errors.selectedLoc }} | |||
| </small> | |||
| </BCol> | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">تصویر بنر</label> | |||
| <input | |||
| type="file" | |||
| accept="image/*" | |||
| @change="handleImageUpload" | |||
| class="form-control" | |||
| :class="{ 'is-invalid': errors.imagePreview }" | |||
| /> | |||
| <div v-if="imagePreview" class="mt-2"> | |||
| <img | |||
| :src="imagePreview" | |||
| alt="Image Preview" | |||
| class="img-fluid rounded shadow-sm Image-Preview" | |||
| /> | |||
| </div> | |||
| <small v-if="errors.imagePreview" class="text-danger"> | |||
| {{ errors.imagePreview }} | |||
| </small> | |||
| </div> | |||
| </BCol> | |||
| </BRow> | |||
| </BCardBody> | |||
| <BCardFooter> | |||
| <div class="d-flex justify-content-center"> | |||
| <div class="text-center"> | |||
| <button | |||
| type="submit" | |||
| class="btn btn-primary" | |||
| @click.prevent="submitForm" | |||
| :disabled="loading" | |||
| > | |||
| <span v-if="loading"> | |||
| <i class="fa fa-spinner fa-spin"></i> ویرایش... | |||
| </span> | |||
| <span v-else>ویرایش</span> | |||
| </button> | |||
| </div> | |||
| </div> | |||
| </BCardFooter> | |||
| </BCard> | |||
| </BCol> | |||
| </BRow> | |||
| <mainPageBanner /> | |||
| <catBanner /> | |||
| </Layout> | |||
| </template> | |||
| <script> | |||
| import { useRoute } from "vue-router"; | |||
| import Select2 from "vue3-select2-component"; | |||
| import catBanner from "@/components/modals/helperModals/catBanner.vue"; | |||
| import mainPageBanner from "@/components/modals/helperModals/mainPageBanner.vue"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import { ref, onMounted, computed } from "vue"; | |||
| import Layout from "@/layout/custom.vue"; | |||
| export default { | |||
| name: "SAMPLE-PAGE", | |||
| components: { | |||
| Layout, | |||
| mainPageBanner, | |||
| catBanner, | |||
| Select2, | |||
| }, | |||
| setup() { | |||
| const route = useRoute(); | |||
| const title = ref(); | |||
| const pageType = ref(); | |||
| const products = ref([]); | |||
| const cats = ref([]); | |||
| const landingType = ref(); | |||
| const selectedCatPage = ref(); | |||
| const selectedLandingCat = ref(); | |||
| const selectedLandingProduct = ref(); | |||
| const selectedLoc = ref(); | |||
| const pannel = ref(); | |||
| const image = ref(); | |||
| const imagePreview = ref(); | |||
| const loading = ref(false); | |||
| const errors = ref({}); | |||
| const getCats = () => { | |||
| ApiServiece.get(`admin/categories`) | |||
| .then((resp) => { | |||
| cats.value = resp.data.data; | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| }); | |||
| }; | |||
| const getProduct = () => { | |||
| ApiServiece.get(`admin/products`) | |||
| .then((resp) => { | |||
| products.value = resp.data.data; | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| }); | |||
| }; | |||
| const handleImageUpload = (event) => { | |||
| const file = event.target.files[0]; | |||
| if (file) { | |||
| errors.value.image = null; | |||
| image.value = file; | |||
| const reader = new FileReader(); | |||
| reader.onload = () => { | |||
| imagePreview.value = reader.result; | |||
| }; | |||
| reader.readAsDataURL(file); | |||
| } | |||
| }; | |||
| const formattedUsers = computed(() => { | |||
| return products.value.map((product) => ({ | |||
| id: product.id, | |||
| text: product.title, | |||
| })); | |||
| }); | |||
| const validateForm = () => { | |||
| errors.value = {}; | |||
| if (!title.value) errors.value.title = "وارد کردن عنوان بنر الزامی است"; | |||
| if (!pageType.value) | |||
| errors.value.pageType = "مشخص کنید بنر کجا نشان داده شود"; | |||
| if (pageType.value === "cat" && !selectedCatPage.value) | |||
| errors.value.selectedCatPage = "صفحه دسته را انتخاب کنید"; | |||
| if (!landingType.value) | |||
| errors.value.landingType = "صفحه فرود را انتخاب نمایید"; | |||
| if (landingType.value === "cat" && !selectedLandingCat.value) | |||
| errors.value.selectedLandingCat = "صفحه فرود دسته را انتخاب کنید"; | |||
| if (landingType.value === "product" && !selectedLandingProduct.value) | |||
| errors.value.selectedLandingProduct = "صفحه فرود محصول را انتخاب کنید"; | |||
| if (!selectedLoc.value) | |||
| errors.value.selectedLoc = "موقعیت بنر را انتخاب کنید"; | |||
| if (!pannel.value) errors.value.pannel = "پنل نمایش بنر را انتخاب کنید"; | |||
| if (!imagePreview.value) | |||
| errors.value.imagePreview = "عکس بنر را وارد نمایید"; | |||
| return Object.keys(errors.value).length === 0; | |||
| }; | |||
| const clearError = (field) => { | |||
| errors.value[field] = ""; | |||
| }; | |||
| const getBanner = () => { | |||
| ApiServiece.get(`admin/banners/${route.params.id}`) | |||
| .then((resp) => { | |||
| const data = resp.data.data; | |||
| title.value = data?.title; | |||
| pannel.value = data?.panel; | |||
| imagePreview.value = data?.image; | |||
| selectedLoc.value = data?.location; | |||
| selectedLandingCat.value = data?.category_id; | |||
| selectedLandingProduct.value = data?.product_id; | |||
| console.log(data) | |||
| if (data.page_id) { | |||
| pageType.value = "cat"; | |||
| selectedCatPage.value = data?.page_id; | |||
| console.log(data); | |||
| } | |||
| if (!data.page_id) { | |||
| pageType.value = "main"; | |||
| } | |||
| if (selectedLandingProduct.value) { | |||
| landingType.value = "product"; | |||
| } | |||
| if (selectedLandingCat.value) { | |||
| landingType.value = "cat"; | |||
| console.log("Asd") | |||
| } | |||
| if(!selectedCatPage.value && !selectedLandingProduct.value){ | |||
| pannel.value = "wholesale" | |||
| } | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| }); | |||
| }; | |||
| onMounted(() => { | |||
| getCats(); | |||
| getProduct(); | |||
| getBanner(); | |||
| }); | |||
| const submitForm = () => { | |||
| if (!validateForm()) return; | |||
| loading.value = true; | |||
| const formData = new FormData(); | |||
| formData.append("title", title.value); | |||
| if (pageType.value === "cat") { | |||
| formData.append("page_id", selectedCatPage.value); | |||
| } | |||
| if (landingType.value === "product") { | |||
| formData.append("product_id", selectedLandingProduct.value); | |||
| } | |||
| if (landingType.value === "cat") { | |||
| formData.append("category_id", selectedLandingCat.value); | |||
| } | |||
| if (selectedLoc.value === "A") { | |||
| formData.append("type", "slider"); | |||
| } | |||
| if (selectedLoc.value !== "A") { | |||
| formData.append("type", "banner"); | |||
| } | |||
| if (pannel.value === "wholesale") { | |||
| formData.append("type", "slider"); | |||
| formData.append("location", "A"); | |||
| } | |||
| formData.append("location", selectedLoc.value); | |||
| formData.append("panel", pannel.value); | |||
| if (image.value) { | |||
| formData.append("image", image.value); | |||
| } | |||
| formData.append("sort", 1); | |||
| formData.append("_method", "put"); | |||
| ApiServiece.post(`admin/banners/${route.params.id}`, formData, { | |||
| headers: { | |||
| "content-type": "multipart", | |||
| Authorization: `Bearer ${localStorage.getItem("token")}`, | |||
| }, | |||
| }) | |||
| .then((resp) => { | |||
| loading.value = false; | |||
| toast.success("!بنر با موفقیت ویرایش شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| console.log(resp); | |||
| }) | |||
| .catch((error) => { | |||
| loading.value = false; | |||
| console.log(error.response.message); | |||
| toast.error(`${error.response.data.message}`, { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }); | |||
| }; | |||
| return { | |||
| cats, | |||
| errors, | |||
| title, | |||
| products, | |||
| selectedCatPage, | |||
| submitForm, | |||
| clearError, | |||
| pageType, | |||
| formattedUsers, | |||
| landingType, | |||
| selectedLandingCat, | |||
| selectedLandingProduct, | |||
| selectedLoc, | |||
| pannel, | |||
| handleImageUpload, | |||
| image, | |||
| imagePreview, | |||
| loading, | |||
| }; | |||
| }, | |||
| }; | |||
| </script> | |||
| <style scoped> | |||
| .ql-editor { | |||
| direction: rtl; | |||
| text-align: right; | |||
| } | |||
| .ql-editor::before { | |||
| content: attr(placeholder); | |||
| direction: rtl !important; | |||
| text-align: right; | |||
| } | |||
| .Image-Preview { | |||
| min-width: 200px; | |||
| max-height: 200px; | |||
| min-height: 200px; | |||
| max-width: 200px; | |||
| object-fit: cover; | |||
| border-radius: 8px; | |||
| border: 1px solid #ddd; | |||
| } | |||
| .delete-btn { | |||
| display: flex; | |||
| align-items: center; | |||
| padding: 3px; | |||
| font-size: 10px; | |||
| background-color: #e74c3c; | |||
| color: white; | |||
| border: none; | |||
| border-radius: 5px; | |||
| cursor: pointer; | |||
| transition: background-color 0.3s ease, transform 0.2s ease; | |||
| gap: 8px; | |||
| margin-right: 200px; | |||
| } | |||
| .delete-btn:hover { | |||
| background-color: #c0392b; | |||
| transform: scale(1.05); | |||
| } | |||
| .delete-btn:active { | |||
| background-color: #a93226; | |||
| } | |||
| .delete-btn:focus { | |||
| outline: none; | |||
| } | |||
| </style> | |||
| @@ -137,6 +137,7 @@ export default { | |||
| }; | |||
| const editModalData = (id, title, icon) => { | |||
| catId.value = id; | |||
| catTitle.value = title; | |||
| catIcon.value = icon; | |||
| @@ -183,7 +184,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 bg-primary text-white" | |||
| class="card-header d-flex justify-content-between align-items-center p-3" | |||
| dir="rtl" | |||
| > | |||
| <div class="d-flex align-items-center"> | |||
| @@ -27,12 +27,12 @@ | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">اسلاگ</label> | |||
| <label class="form-label">کلمه کلیدی</label> | |||
| <input | |||
| type="text" | |||
| v-model="slug" | |||
| class="form-control" | |||
| placeholder="اسلاگ بلاگ" | |||
| placeholder="کلمه کلیدی بلاگ" | |||
| :class="{ 'is-invalid': errors.slug }" | |||
| @input="clearError('slug')" | |||
| /> | |||
| @@ -98,9 +98,10 @@ | |||
| :class="{ 'is-invalid': errors.blogCat }" | |||
| v-model="blogCat" | |||
| class="form-control" | |||
| @input="clearError('blogCat')" | |||
| @change="clearError('blogCat')" | |||
| placeholder="انتخاب دسته بلاگ" | |||
| > | |||
| <option value="" disabled selected>انتخاب دسته بلاگ</option> | |||
| <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | |||
| {{ cat.title }} | |||
| </option> | |||
| @@ -165,7 +166,7 @@ | |||
| </template> | |||
| <script> | |||
| import Swal from "sweetalert2"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| @@ -232,7 +233,7 @@ export default { | |||
| const validateForm = () => { | |||
| errors.value = {}; | |||
| if (!title.value) errors.value.title = "وارد کردن عنوان بلاگ الزامی است"; | |||
| if (!slug.value) errors.value.slug = "وارد کردن اسلاگ بلاگ ضروری می باشد"; | |||
| if (!slug.value) errors.value.slug = "وارد کردن کلمه کلیدی بلاگ ضروری می باشد"; | |||
| if (!summary.value) | |||
| errors.value.summary = "وارد کردن خلاصه بلاگ ضروری می باشد"; | |||
| if (!blogCat.value) | |||
| @@ -313,12 +314,9 @@ export default { | |||
| .catch((error) => { | |||
| console.error(error); | |||
| Swal.fire({ | |||
| icon: "error", | |||
| title: "خطا", | |||
| text: `!افزودن بلاگ با مشکل مواجه شد: ${ | |||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||
| }`, | |||
| toast.error("!مشکلی در اضافه کردن بلاگ پیش آمد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .finally(() => { | |||
| @@ -73,7 +73,7 @@ export default { | |||
| const deleteBlog = (id, title) => { | |||
| Swal.fire({ | |||
| text: `می خواهید بلاگ ${title} را حذف کنید ؟`, | |||
| text: `می خواهید بلاگ ${title} را حذف کنید؟`, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| @@ -162,7 +162,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 bg-primary text-white" | |||
| class="card-header d-flex justify-content-between align-items-center p-3 " | |||
| dir="rtl" | |||
| > | |||
| <div class="d-flex align-items-center"> | |||
| @@ -188,7 +188,7 @@ export default { | |||
| <tr> | |||
| <th>عکس</th> | |||
| <th>عنوان</th> | |||
| <td>اسلاگ</td> | |||
| <td>کلمه کلیدی</td> | |||
| <th>تاریخ ایجاد</th> | |||
| <th>عملیات</th> | |||
| </tr> | |||
| @@ -28,12 +28,12 @@ | |||
| <!-- Second Input Field (Slug) --> | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">اسلاگ</label> | |||
| <label class="form-label">کلمه کلیدی</label> | |||
| <input | |||
| type="text" | |||
| v-model="slug" | |||
| class="form-control" | |||
| placeholder="اسلاگ بلاگ" | |||
| placeholder="کلمه کلیدی بلاگ" | |||
| :class="{ 'is-invalid': errors.slug }" | |||
| @input="clearError('slug')" | |||
| /> | |||
| @@ -94,8 +94,9 @@ | |||
| v-model="blogCat" | |||
| class="form-control" | |||
| @input="clearError('blogCat')" | |||
| placeholder="انتخاب دسته بلاگ" | |||
| > | |||
| <option value="" disabled selected>انتخاب دسته بلاگ</option> | |||
| <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | |||
| {{ cat.title }} | |||
| </option> | |||
| @@ -161,7 +162,7 @@ | |||
| </template> | |||
| <script> | |||
| import Swal from "sweetalert2"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| @@ -220,7 +221,7 @@ export default { | |||
| const validateForm = () => { | |||
| errors.value = {}; | |||
| if (!title.value) errors.value.title = "وارد کردن عنوان بلاگ الزامی است"; | |||
| if (!slug.value) errors.value.slug = "وارد کردن اسلاگ بلاگ ضروری می باشد"; | |||
| if (!slug.value) errors.value.slug = "وارد کردن کلمه کلیدی بلاگ ضروری می باشد"; | |||
| if (!summary.value) | |||
| errors.value.summary = "وارد کردن خلاصه بلاگ ضروری می باشد"; | |||
| if (!blogCat.value) | |||
| @@ -229,7 +230,7 @@ export default { | |||
| errors.value.author = "وارد کردن نویسنده بلاگ ضروری می باشد"; | |||
| if (!editorContent.value) | |||
| errors.value.editorContent = "وارد کردن محتوای بلاگ ضروری می باشد"; | |||
| if (!image.value) errors.value.image = "وارد کردن عکس بلاگ ضروری می باشد"; | |||
| if (!imagePreview.value) errors.value.image = "وارد کردن عکس بلاگ ضروری می باشد"; | |||
| return Object.keys(errors.value).length === 0; | |||
| }; | |||
| @@ -313,12 +314,9 @@ export default { | |||
| .catch((error) => { | |||
| console.error(error); | |||
| Swal.fire({ | |||
| icon: "error", | |||
| title: "خطا", | |||
| text: `!ویرایش بلاگ با مشکل مواجه شد: ${ | |||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||
| }`, | |||
| toast.error("!مشکلی در ویرایش باگ پیش آمد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .finally(() => { | |||
| @@ -92,7 +92,7 @@ export default { | |||
| }; | |||
| const deleteBrand = (id, title) => { | |||
| Swal.fire({ | |||
| title: `می خواهید برند ${title} را حذف کنید ؟`, | |||
| text: `می خواهید برند ${title} را حذف کنید؟`, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| @@ -190,7 +190,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 text-white" | |||
| class="card-header d-flex justify-content-between align-items-center" | |||
| dir="rtl" | |||
| > | |||
| <div class="d-flex align-items-center"> | |||
| @@ -0,0 +1,546 @@ | |||
| <script> | |||
| import Layout from "@/layout/custom.vue"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import moment from "jalali-moment"; | |||
| import { onMounted, ref, watch, computed } from "vue"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import Swal from "sweetalert2"; | |||
| export default { | |||
| name: "BORDER", | |||
| components: { | |||
| Layout, | |||
| }, | |||
| setup() { | |||
| const searchPage = ref(); | |||
| const currentPage = ref(1); | |||
| const totalPages = ref(1); | |||
| const paginate = ref(5); | |||
| const page = ref(1); | |||
| const filterLoading = ref(false); | |||
| const selectedStatus = ref(); | |||
| const calls = ref(); | |||
| const callText = ref(); | |||
| const convertToJalali = (date) => { | |||
| return moment(date, "YYYY-MM-DD HH:mm:ss") | |||
| .locale("fa") | |||
| .format("YYYY/MM/DD"); | |||
| }; | |||
| watch(selectedStatus, () => { | |||
| getCalls(); | |||
| page.value = 1; | |||
| }); | |||
| const getCalls = () => { | |||
| filterLoading.value = true; | |||
| ApiServiece.get( | |||
| `admin/forms?status=${selectedStatus.value || ""}&paginate=${ | |||
| paginate.value || 10 | |||
| }&page=${page.value || 1}` | |||
| ) | |||
| .then((resp) => { | |||
| console.log(resp); | |||
| filterLoading.value = false; | |||
| calls.value = resp.data.data.data; | |||
| console.log(calls.value); | |||
| 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) { | |||
| for (let i = 1; i <= totalPages.value; i++) { | |||
| pages.push(i); | |||
| } | |||
| } else { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| } | |||
| } | |||
| return pages; | |||
| }); | |||
| const getStatusClass = (status) => { | |||
| const statusClasses = { | |||
| waiting: "badge-waiting", | |||
| answered: "badge-paid", | |||
| }; | |||
| return statusClasses[status] || "badge-secondary"; | |||
| }; | |||
| const getStatusLabel = (status) => { | |||
| const statusLabels = { | |||
| waiting: "در انتظار", | |||
| answered: "پاسخ داده شده", | |||
| }; | |||
| return statusLabels[status] || "نامشخص"; | |||
| }; | |||
| const deleteOrder = (id) => { | |||
| Swal.fire({ | |||
| text: `می خواهید سفارش ${id} را حذف کنید؟`, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| cancelButtonColor: "#d33", | |||
| confirmButtonText: "بله!", | |||
| cancelButtonText: "خیر", | |||
| }).then((result) => { | |||
| if (result.isConfirmed) { | |||
| ApiServiece.delete(`admin/orders/${id}`) | |||
| .then(() => { | |||
| toast.success("!سفارش با موفقیت حذف شد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| calls.value = calls.value.filter((call) => call.id != id); | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| toast.error("!مشکلی در حذف کردن سفارش پیش آمد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| }); | |||
| } | |||
| }); | |||
| }; | |||
| const changeStatus = (id, status) => { | |||
| Swal.fire({ | |||
| text: `آیا می خواهید وضعیت این پیام را به ${getStatusLabel( | |||
| status | |||
| )} تغییر دهید؟ `, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| cancelButtonColor: "#d33", | |||
| confirmButtonText: "بله!", | |||
| cancelButtonText: "خیر", | |||
| }).then((result) => { | |||
| if (result.isConfirmed) { | |||
| const formData = new FormData(); | |||
| formData.append("status", status); | |||
| ApiServiece.put(`admin/forms/${id}`, formData) | |||
| .then(() => { | |||
| toast.success("!تغییر وضعیت پیام با موفقیت انجام شد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| }) | |||
| .then(() => { | |||
| getCalls(); | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| toast.error("!مشکلی در تغییر وضعیت پیام پیش آمد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| }); | |||
| } | |||
| }); | |||
| }; | |||
| function handlePageInput() { | |||
| if (searchPage.value < 1) { | |||
| searchPage.value = 1; | |||
| } else if (searchPage.value > totalPages.value) { | |||
| searchPage.value = totalPages.value; | |||
| } | |||
| if (searchPage.value >= 1 && searchPage.value <= totalPages.value) { | |||
| page.value = searchPage.value; | |||
| } | |||
| } | |||
| watch(selectedStatus, () => { | |||
| getCalls(); | |||
| }); | |||
| watch(page, () => { | |||
| getCalls(); | |||
| }); | |||
| const nextPage = () => { | |||
| if (currentPage.value < totalPages.value) { | |||
| page.value++; | |||
| getCalls(); | |||
| } | |||
| }; | |||
| const prevPage = () => { | |||
| if (currentPage.value > 1) { | |||
| page.value--; | |||
| getCalls(); | |||
| } | |||
| }; | |||
| onMounted(() => { | |||
| getCalls(); | |||
| }); | |||
| return { | |||
| calls, | |||
| convertToJalali, | |||
| deleteOrder, | |||
| selectedStatus, | |||
| filterLoading, | |||
| changeStatus, | |||
| currentPage, | |||
| totalPages, | |||
| nextPage, | |||
| prevPage, | |||
| page, | |||
| handlePageInput, | |||
| searchPage, | |||
| visiblePages, | |||
| getStatusClass, | |||
| getStatusLabel, | |||
| callText, | |||
| }; | |||
| }, | |||
| }; | |||
| </script> | |||
| <template> | |||
| <Layout> | |||
| <BRow> | |||
| <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" | |||
| dir="rtl" | |||
| > | |||
| <div class="d-flex align-items-center"> | |||
| <label for="statusSelect" class="form-label me-2"> وضعیت </label> | |||
| <select | |||
| v-model="selectedStatus" | |||
| id="statusSelect" | |||
| class="form-control form-control-sm d-inline-block me-2" | |||
| style="width: 250px; border-radius: 15px" | |||
| > | |||
| <option value="answered">پاسخ داده شده</option> | |||
| <option value="waiting">در انتظار</option> | |||
| </select> | |||
| </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> | |||
| <th>پیام</th> | |||
| <th>وضعیت</th> | |||
| <th>تاریخ ایجاد</th> | |||
| <th>عملیات</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| <tr v-for="call in calls" :key="call.id"> | |||
| <td>{{ call.name }}</td> | |||
| <td v-if="call.email">{{ call.email }}</td> | |||
| <td v-if="!call.email"> | |||
| <i | |||
| class="fas fa-times-circle status-icon unavailable" | |||
| ></i> | |||
| </td> | |||
| <td>{{ call?.subject }}</td> | |||
| <td> | |||
| <textarea | |||
| :value="call.text" | |||
| disabled | |||
| readonly | |||
| style=" | |||
| width: 100%; | |||
| resize: none; | |||
| border: none; | |||
| background: transparent; | |||
| " | |||
| ></textarea> | |||
| </td> | |||
| <td> | |||
| <span class="badge" :class="getStatusClass(call.status)"> | |||
| {{ getStatusLabel(call.status) }} | |||
| </span> | |||
| </td> | |||
| <td>{{ convertToJalali(call?.created_at) }}</td> | |||
| <td> | |||
| <!-- <router-link | |||
| :to="`/singleOrder/${order?.id}`" | |||
| class="btn btn-sm btn-outline-primary me-1" | |||
| > | |||
| مشاهده | |||
| </router-link> --> | |||
| <button | |||
| class="btn btn-sm btn-outline-warning dropdown-toggle me-1" | |||
| type="button" | |||
| id="dropdownMenuButton" | |||
| data-bs-toggle="dropdown" | |||
| aria-expanded="false" | |||
| > | |||
| ویرایش وضعیت | |||
| </button> | |||
| <ul | |||
| class="dropdown-menu" | |||
| aria-labelledby="dropdownMenuButton" | |||
| > | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus(call?.id, 'waiting')" | |||
| ><span class="badge badge-waiting" | |||
| >در انتظار</span | |||
| ></a | |||
| > | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus(call?.id, 'answered')" | |||
| ><span class="badge badge-paid" | |||
| >پاسخ داده شده</span | |||
| ></a | |||
| > | |||
| </li> | |||
| </ul> | |||
| <!-- <button | |||
| @click="deleteOrder(order?.id)" | |||
| class="btn btn-sm btn-outline-danger" | |||
| > | |||
| حذف | |||
| </button> --> | |||
| </td> | |||
| </tr> | |||
| </tbody> | |||
| </table> | |||
| </div> | |||
| </div> | |||
| <div | |||
| v-else | |||
| class="filter-loader card table-card user-profile-list" | |||
| ></div> | |||
| </div> | |||
| </div> | |||
| </BRow> | |||
| <BRow> | |||
| <BCol sm="12"> | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <!-- Previous page --> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <!-- Page numbers with dots logic --> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <!-- Page numbers --> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| class="page-item" | |||
| :class="{ active: currentPage === n }" | |||
| > | |||
| <a | |||
| class="page-link" | |||
| href="javascript:void(0)" | |||
| @click="page = n" | |||
| > | |||
| {{ n }} | |||
| </a> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| </li> | |||
| <!-- Next page --> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| > | |||
| <span class="page-link" @click="nextPage">بعدی</span> | |||
| </li> | |||
| </ul> | |||
| </nav> | |||
| </div> | |||
| </BCol> | |||
| <BCol sm="4"> | |||
| <div class="ms-0 search-number"> | |||
| <input | |||
| v-model="searchPage" | |||
| type="text" | |||
| class="form-control" | |||
| placeholder="برو به صفحه" | |||
| :max="totalPages" | |||
| min="1" | |||
| @input="handlePageInput" | |||
| /> | |||
| </div> | |||
| </BCol> | |||
| </BRow> | |||
| </Layout> | |||
| </template> | |||
| <style scoped> | |||
| .card { | |||
| transition: transform 0.3s ease; | |||
| } | |||
| .card:hover { | |||
| transform: translateY(-3px); | |||
| } | |||
| .table th, | |||
| .table td { | |||
| vertical-align: middle; | |||
| text-align: center; | |||
| } | |||
| .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; | |||
| } | |||
| .subject-box { | |||
| padding: 8px 14px; | |||
| background: linear-gradient(135deg, #fff3e0, #ffe0b2); | |||
| color: #ef6c00; | |||
| font-weight: 600; | |||
| border-radius: 10px; | |||
| box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1); | |||
| display: inline-flex; | |||
| align-items: center; | |||
| gap: 8px; | |||
| transition: transform 0.2s ease, box-shadow 0.2s ease; | |||
| } | |||
| .subject-box i { | |||
| color: #ef6c00; | |||
| font-size: 1rem; | |||
| } | |||
| .subject-box:hover { | |||
| transform: translateY(-2px); | |||
| box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.15); | |||
| } | |||
| .search-number { | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| .search-number input { | |||
| width: 150px; | |||
| padding: 0.5rem; | |||
| font-size: 1rem; | |||
| border-radius: 0.375rem; | |||
| margin-bottom: 7px; | |||
| border: 1px solid #ced4da; | |||
| transition: border-color 0.3s ease, box-shadow 0.3s ease; | |||
| } | |||
| .search-number input:focus { | |||
| border-color: #007bff; | |||
| box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.25); | |||
| } | |||
| .search-number input::placeholder { | |||
| color: #6c757d; | |||
| } | |||
| .search-number input:disabled { | |||
| background-color: #f8f9fa; | |||
| cursor: not-allowed; | |||
| } | |||
| .pagination { | |||
| display: flex; | |||
| flex-wrap: wrap; | |||
| gap: 5px; | |||
| } | |||
| .page-item { | |||
| flex: 0 0 auto; | |||
| } | |||
| .page-link { | |||
| cursor: pointer; | |||
| user-select: none; | |||
| } | |||
| .badge { | |||
| display: inline-block; | |||
| padding: 5px 10px; | |||
| border-radius: 12px; | |||
| font-size: 0.85rem; | |||
| font-weight: 500; | |||
| text-transform: capitalize; | |||
| color: white; | |||
| } | |||
| .badge-waiting { | |||
| background-color: #ffc107; | |||
| } | |||
| .badge-paid { | |||
| background-color: #28a745; | |||
| } | |||
| .badge-un_paid { | |||
| background-color: #dc3545; | |||
| } | |||
| .badge-approved { | |||
| background-color: #17a2b8; | |||
| } | |||
| .badge-processing { | |||
| background-color: #007bff; | |||
| } | |||
| .badge-shipping { | |||
| background-color: #6f42c1; | |||
| } | |||
| .badge-delivered { | |||
| background-color: #20c997; | |||
| } | |||
| .badge-canceled { | |||
| background-color: #6c757d; | |||
| } | |||
| .dropdown-item { | |||
| cursor: pointer; | |||
| } | |||
| .status-icon.unavailable { | |||
| color: #dc3545; | |||
| } | |||
| </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"; | |||
| @@ -18,6 +18,7 @@ export default { | |||
| editCat, | |||
| }, | |||
| setup() { | |||
| const catIcon = ref(); | |||
| const searchPage = ref(); | |||
| const currentPage = ref(1); | |||
| const totalPages = ref(1); | |||
| @@ -41,9 +42,11 @@ export default { | |||
| }); | |||
| const getCats = () => { | |||
| filterLoading.value = true; | |||
| ApiServiece.get(`admin/categories?title=${searchQuery.value}&paginate=${ | |||
| ApiServiece.get( | |||
| `admin/categories?title=${searchQuery.value}&paginate=${ | |||
| paginate.value || 10 | |||
| }&page=${page.value || 1}`) | |||
| }&page=${page.value || 1}` | |||
| ) | |||
| .then((resp) => { | |||
| filterLoading.value = false; | |||
| cats.value = resp.data.data.data; | |||
| @@ -107,7 +110,7 @@ export default { | |||
| }; | |||
| const deleteCat = (id, title) => { | |||
| Swal.fire({ | |||
| title: `می خواهید دسته ${title} را حذف کنید ؟`, | |||
| text: `می خواهید دسته ${title} را حذف کنید؟`, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| @@ -122,7 +125,7 @@ export default { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| cats.value = cats.value.filter((cat) => cat.id !== id); | |||
| getCats(); | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| @@ -135,12 +138,13 @@ export default { | |||
| }); | |||
| }; | |||
| const editModalData = (id, title, desc, parent, img) => { | |||
| const editModalData = (id, title, desc, parent, img, icon) => { | |||
| catId.value = id; | |||
| catTitle.value = title; | |||
| catDescription.value = desc; | |||
| catParent.value = parent; | |||
| catImage.value = img; | |||
| catIcon.value = icon; | |||
| }; | |||
| const descriptionModal = (desc) => { | |||
| @@ -151,6 +155,38 @@ export default { | |||
| getCats(); | |||
| }); | |||
| const restoreCat = (id, title) => { | |||
| Swal.fire({ | |||
| text: `می خواهید دسته ${title} را بازیابی کنید؟`, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| cancelButtonColor: "#d33", | |||
| confirmButtonText: "بله!", | |||
| cancelButtonText: "خیر", | |||
| }).then((result) => { | |||
| if (result.isConfirmed) { | |||
| ApiServiece.put(`admin/categories/${id}/restore`) | |||
| .then(() => { | |||
| toast.success("!دسته با موفقیت بازیابی شد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| }) | |||
| .then(() => { | |||
| getCats(); | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| toast.error("!مشکلی در بازیابی دسته پیش آمد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| }); | |||
| } | |||
| }); | |||
| }; | |||
| onMounted(() => { | |||
| getCats(); | |||
| }); | |||
| @@ -176,6 +212,8 @@ export default { | |||
| handlePageInput, | |||
| searchPage, | |||
| visiblePages, | |||
| catIcon, | |||
| restoreCat, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -186,7 +224,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 bg-primary text-white" | |||
| class="card-header d-flex justify-content-between align-items-center p-3" | |||
| dir="rtl" | |||
| > | |||
| <div class="d-flex align-items-center"> | |||
| @@ -256,7 +294,8 @@ export default { | |||
| cat?.title, | |||
| cat.description, | |||
| cat?.parent?.id, | |||
| cat?.image | |||
| cat?.image, | |||
| cat?.icon | |||
| ) | |||
| " | |||
| data-bs-toggle="modal" | |||
| @@ -266,11 +305,19 @@ export default { | |||
| ویرایش | |||
| </button> | |||
| <button | |||
| v-if="!cat.deleted_at" | |||
| @click="deleteCat(cat.id, cat.title)" | |||
| class="btn btn-sm btn-outline-danger" | |||
| > | |||
| حذف | |||
| </button> | |||
| <button | |||
| v-else | |||
| @click="restoreCat(cat?.id, cat?.title)" | |||
| class="btn btn-sm btn-outline-success" | |||
| > | |||
| بازیابی | |||
| </button> | |||
| </td> | |||
| </tr> | |||
| </tbody> | |||
| @@ -291,82 +338,82 @@ export default { | |||
| :parent="catParent" | |||
| :image="catImage" | |||
| :allParents="cats" | |||
| :icon="catIcon" | |||
| @cat-updated="handleCatUpdated()" | |||
| /> | |||
| <showDescription :desc="catDescription" /> | |||
| </BRow> | |||
| <BRow> | |||
| <BCol sm="12"> | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <BCol sm="12"> | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| class="page-item" | |||
| :class="{ active: currentPage === n }" | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| class="page-item" | |||
| :class="{ active: currentPage === n }" | |||
| > | |||
| <a | |||
| class="page-link" | |||
| href="javascript:void(0)" | |||
| @click="page = n" | |||
| > | |||
| <a | |||
| class="page-link" | |||
| href="javascript:void(0)" | |||
| @click="page = n" | |||
| > | |||
| {{ n }} | |||
| </a> | |||
| </li> | |||
| {{ n }} | |||
| </a> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| </li> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| > | |||
| <span class="page-link" @click="nextPage">بعدی</span> | |||
| </li> | |||
| </ul> | |||
| </nav> | |||
| </div> | |||
| </BCol> | |||
| <BCol sm="4"> | |||
| <div class="ms-0 search-number"> | |||
| <input | |||
| v-model="searchPage" | |||
| type="text" | |||
| class="form-control" | |||
| placeholder="برو به صفحه" | |||
| :max="totalPages" | |||
| min="1" | |||
| @input="handlePageInput" | |||
| /> | |||
| </div> | |||
| </BCol> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| > | |||
| <span class="page-link" @click="nextPage">بعدی</span> | |||
| </li> | |||
| </ul> | |||
| </nav> | |||
| </div> | |||
| </BCol> | |||
| <BCol sm="4"> | |||
| <div class="ms-0 search-number"> | |||
| <input | |||
| v-model="searchPage" | |||
| type="text" | |||
| class="form-control" | |||
| placeholder="برو به صفحه" | |||
| :max="totalPages" | |||
| min="1" | |||
| @input="handlePageInput" | |||
| /> | |||
| </div> | |||
| </BCol> | |||
| </BRow> | |||
| </Layout> | |||
| </template> | |||
| @@ -80,7 +80,7 @@ export default { | |||
| const deleteDiscount = (id, title) => { | |||
| Swal.fire({ | |||
| text: `می خواهید تخفیف ${title} را حذف کنید ؟`, | |||
| text: `می خواهید تخفیف ${title} را حذف کنید؟`, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| @@ -125,11 +125,11 @@ export default { | |||
| const changeCommentStatus = (id, op) => { | |||
| let text, successMessage, errorMessage; | |||
| if (op === "confirmed") { | |||
| text = `آیای می خواهید این نظر را قبول کنید؟`; | |||
| text = ` می خواهید این نظر را قبول کنید؟`; | |||
| successMessage = "!نظر با موفقیت قبول شد"; | |||
| errorMessage = "!مشکلی در تغییر وضعیت نظر ایجاد شد"; | |||
| } else if (op === "rejected") { | |||
| text = `آیای می خواهید این نظر را رد کنید؟`; | |||
| text = `می خواهید این نظر را رد کنید؟`; | |||
| successMessage = "!نظر با موفقیت رد شد"; | |||
| errorMessage = "!مشکلی در تغییر وضعیت نظر ایجاد شد"; | |||
| } | |||
| @@ -197,7 +197,6 @@ export default { | |||
| deleteDiscount, | |||
| searchQuery, | |||
| filterLoading, | |||
| currentPage, | |||
| totalPages, | |||
| nextPage, | |||
| @@ -218,10 +217,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 bg-primary text-white" | |||
| 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"> | |||
| <input | |||
| v-model="searchQuery" | |||
| type="text" | |||
| @@ -229,13 +228,8 @@ 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" | |||
| > | |||
| افزودن تخفیف | |||
| </router-link> | |||
| </div> | |||
| </div> --> | |||
| </div> | |||
| <div v-if="!filterLoading" class="card-body table-border-style p-0"> | |||
| <div class="table-responsive"> | |||
| @@ -33,10 +33,8 @@ | |||
| class="form-control" | |||
| :class="{ 'is-invalid': errors.discountType }" | |||
| @change="clearError('discountType')" | |||
| placeholder="انتخاب نوع تخفیف" | |||
| > | |||
| <option value="" disabled> | |||
| لطفاً نوع تخفیف را انتخاب کنید | |||
| </option> | |||
| <option value="percentage">درصدی</option> | |||
| <option value="const">مبلغ ثابت</option> | |||
| </select> | |||
| @@ -105,10 +103,8 @@ | |||
| class="form-control" | |||
| :class="{ 'is-invalid': errors.whichPart }" | |||
| @select="clearError('whichPart')" | |||
| placeholder="انتخاب محل اعمال تخفبف" | |||
| > | |||
| <option value="" disabled> | |||
| لطفاً اعمال تخفیف را انتخاب کنید | |||
| </option> | |||
| <option value="cat">دسته</option> | |||
| <option value="product">محصول</option> | |||
| <option value="both">هردو</option> | |||
| @@ -127,8 +123,8 @@ | |||
| v-model="selectedCat" | |||
| class="form-control" | |||
| @select="clearError('selectedCat')" | |||
| placeholder="انتخاب دسته" | |||
| > | |||
| <option value="" disabled selected>انتخاب دسته</option> | |||
| <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | |||
| {{ cat.title }} | |||
| </option> | |||
| @@ -141,26 +137,19 @@ | |||
| <BCol | |||
| v-if="whichPart === 'product' || whichPart === 'both'" | |||
| md="6" | |||
| sm="6" | |||
| class="mt-3" | |||
| style="margin-top: 30px" | |||
| > | |||
| <div class="form-group"> | |||
| <label class="form-label">محصول</label> | |||
| <select | |||
| :class="{ 'is-invalid': errors.selectedProduct }" | |||
| v-model="selectedProduct" | |||
| class="form-control" | |||
| @select="clearError('selectedProduct')" | |||
| > | |||
| <option value="" disabled selected>انتخاب محصول</option> | |||
| <option | |||
| v-for="product in products" | |||
| :key="product.id" | |||
| :value="product.id" | |||
| > | |||
| {{ product.title }} | |||
| </option> | |||
| </select> | |||
| </div> | |||
| <label for="token"> انتخاب محصول </label> | |||
| <Select2 | |||
| id="token" | |||
| v-model="selectedProduct" | |||
| :options="formattedProducts" | |||
| :settings="{ settingOption: value, settingOption: value }" | |||
| style="height: 60px" | |||
| /> | |||
| <small v-if="errors.selectedProduct" class="text-danger"> | |||
| {{ errors.selectedProduct }} | |||
| </small> | |||
| @@ -171,7 +160,7 @@ | |||
| <label class="form-label"> تاریخ اعمال تخفیف </label> | |||
| <DatePicker | |||
| format="YYYY/MM/DD HH:mm:ss" | |||
| :format="'jYYYY/jMM/jDD HH:mm:ss'" | |||
| type="datetime" | |||
| v-model="startDate" | |||
| @input="handleStartDateInput" | |||
| @@ -187,7 +176,7 @@ | |||
| <label class="form-label"> تاریخ انقضای تخفیف </label> | |||
| <DatePicker | |||
| format="YYYY/MM/DD HH:mm:ss" | |||
| :format="'jYYYY/jMM/jDD HH:mm:ss'" | |||
| type="datetime" | |||
| v-model="expire" | |||
| @input="handleExpireDateInput" | |||
| @@ -209,7 +198,7 @@ | |||
| :disabled="loading" | |||
| > | |||
| <span v-if="loading"> | |||
| <i class="fa fa-spinner fa-spin"></i> بارگذاری... | |||
| <i class="fa fa-spinner fa-spin"></i> ایجاد... | |||
| </span> | |||
| <span v-else>ایجاد</span> | |||
| </button> | |||
| @@ -223,11 +212,12 @@ | |||
| </template> | |||
| <script> | |||
| import Select2 from "vue3-select2-component"; | |||
| import moment from "moment"; | |||
| 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"; | |||
| @@ -236,6 +226,7 @@ export default { | |||
| components: { | |||
| Layout, | |||
| DatePicker, | |||
| Select2, | |||
| }, | |||
| setup() { | |||
| const title = ref(); | |||
| @@ -275,18 +266,32 @@ export default { | |||
| const handleStartDateInput = () => { | |||
| if (startDate.value) { | |||
| startDate.value = moment(startDate.value).format("YYYY-MM-DD HH:mm:ss"); | |||
| startDate.value = moment( | |||
| startDate.value, | |||
| "jYYYY/jMM/jDD HH:mm:ss" | |||
| ).format("YYYY-MM-DD HH:mm:ss"); | |||
| } else { | |||
| clearError("startDate"); | |||
| } | |||
| clearError("expire"); | |||
| }; | |||
| const handleExpireDateInput = () => { | |||
| if (expire.value) { | |||
| expire.value = moment(expire.value).format("YYYY-MM-DD HH:mm:ss"); | |||
| expire.value = moment(expire.value, "jYYYY/jMM/jDD HH:mm:ss").format( | |||
| "YYYY-MM-DD HH:mm:ss" | |||
| ); | |||
| } else { | |||
| clearError("expire"); | |||
| } | |||
| clearError("expire"); | |||
| }; | |||
| const formattedProducts = computed(() => { | |||
| return products.value.map((product) => ({ | |||
| id: product.id, | |||
| text: product.title, | |||
| })); | |||
| }); | |||
| const validateForm = () => { | |||
| errors.value = {}; | |||
| if (!title.value) errors.value.title = "وارد کردن عنوان تخفیف الزامی است"; | |||
| @@ -348,6 +353,7 @@ export default { | |||
| ApiServiece.post(`/admin/discounts`, formData) | |||
| .then((resp) => { | |||
| loading.value = false; | |||
| toast.success("!تخفیف با موفقیت اضافه شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| @@ -355,6 +361,7 @@ export default { | |||
| console.log(resp); | |||
| }) | |||
| .catch((error) => { | |||
| loading.value = false; | |||
| console.log(error.response.message); | |||
| toast.error(`${error.response.data.message}`, { | |||
| position: "top-right", | |||
| @@ -381,6 +388,8 @@ export default { | |||
| handleExpireDateInput, | |||
| whichPart, | |||
| clearError, | |||
| loading, | |||
| formattedProducts, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -19,7 +19,7 @@ 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); | |||
| @@ -76,7 +76,7 @@ export default { | |||
| const deleteDiscount = (id, title) => { | |||
| Swal.fire({ | |||
| text: `می خواهید تخفیف ${title} را حذف کنید ؟`, | |||
| text: `می خواهید تخفیف ${title} را حذف کنید؟`, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| @@ -167,7 +167,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 text-white" | |||
| class="card-header d-flex justify-content-between align-items-center" | |||
| dir="rtl" | |||
| > | |||
| <div class="d-flex align-items-center"> | |||
| @@ -180,7 +180,7 @@ export default { | |||
| /> | |||
| <router-link | |||
| to="/addDiscount" | |||
| class="btn btn-light text-primary btn-sm px-3" | |||
| class="btn btn-light text-primary btn-sm px-3 " | |||
| > | |||
| افزودن تخفیف | |||
| </router-link> | |||
| @@ -203,7 +203,8 @@ export default { | |||
| <tbody> | |||
| <tr v-for="discount in discounts" :key="discount.id"> | |||
| <td>{{ discount?.title }}</td> | |||
| <td>{{ discount.type }}</td> | |||
| <td v-if="discount.type === 'const'">مبلغی</td> | |||
| <td v-if="discount.type === 'percentage'">درصدی</td> | |||
| <td>{{ discount.amount }}</td> | |||
| <td>{{ discount.min_order }}</td> | |||
| <td>{{ convertToJalali(discount?.starts_at) }}</td> | |||
| @@ -4,7 +4,7 @@ | |||
| <BCol sm="12"> | |||
| <BCard no-body> | |||
| <BCardHeader> | |||
| <h5>ایجاد تخفیف</h5> | |||
| <h5>ویرایش تخفیف</h5> | |||
| </BCardHeader> | |||
| <BCardBody> | |||
| <BRow class="g-3"> | |||
| @@ -34,10 +34,8 @@ | |||
| class="form-control" | |||
| :class="{ 'is-invalid': errors.discountType }" | |||
| @change="clearError('discountType')" | |||
| placeholder="انتخاب نوع اعمال تخفیف" | |||
| > | |||
| <option value="" disabled> | |||
| لطفاً نوع تخفیف را انتخاب کنید | |||
| </option> | |||
| <option value="percentage">درصدی</option> | |||
| <option value="const">مبلغ ثابت</option> | |||
| </select> | |||
| @@ -106,10 +104,8 @@ | |||
| class="form-control" | |||
| :class="{ 'is-invalid': errors.whichPart }" | |||
| @select="clearError('whichPart')" | |||
| placeholder="انتخاب محل اعمل تخفیف" | |||
| > | |||
| <option value="" disabled> | |||
| لطفاً اعمال تخفیف را انتخاب کنید | |||
| </option> | |||
| <option value="cat">دسته</option> | |||
| <option value="product">محصول</option> | |||
| <option value="both">هردو</option> | |||
| @@ -120,10 +116,7 @@ | |||
| </small> | |||
| </BCol> | |||
| <BCol | |||
| v-if="whichPart === 'cat' || whichPart === 'both'" | |||
| md="6" | |||
| > | |||
| <BCol v-if="whichPart === 'cat' || whichPart === 'both'" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">دسته</label> | |||
| <select | |||
| @@ -131,8 +124,8 @@ | |||
| v-model="selectedCat" | |||
| class="form-control" | |||
| @select="clearError('selectedCat')" | |||
| placeholder="انتخاب دسته" | |||
| > | |||
| <option value="" disabled selected>انتخاب دسته</option> | |||
| <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | |||
| {{ cat.title }} | |||
| </option> | |||
| @@ -154,8 +147,8 @@ | |||
| v-model="selectedProduct" | |||
| class="form-control" | |||
| @select="clearError('selectedProduct')" | |||
| placeholder="انتخاب محصول" | |||
| > | |||
| <option value="" disabled selected>انتخاب محصول</option> | |||
| <option | |||
| v-for="product in products" | |||
| :key="product.id" | |||
| @@ -175,7 +168,7 @@ | |||
| <label class="form-label"> تاریخ اعمال تخفیف </label> | |||
| <DatePicker | |||
| format="YYYY/MM/DD HH:mm:ss" | |||
| :format="'jYYYY/jMM/jDD HH:mm:ss'" | |||
| type="datetime" | |||
| v-model="startDate" | |||
| @input="handleStartDateInput" | |||
| @@ -191,7 +184,7 @@ | |||
| <label class="form-label"> تاریخ انقضای تخفیف </label> | |||
| <DatePicker | |||
| format="YYYY/MM/DD HH:mm:ss" | |||
| :format="'jYYYY/jMM/jDD HH:mm:ss'" | |||
| type="datetime" | |||
| v-model="expire" | |||
| @input="handleExpireDateInput" | |||
| @@ -213,9 +206,9 @@ | |||
| :disabled="loading" | |||
| > | |||
| <span v-if="loading"> | |||
| <i class="fa fa-spinner fa-spin"></i> بارگذاری... | |||
| <i class="fa fa-spinner fa-spin"></i> ویرایش... | |||
| </span> | |||
| <span v-else>ایجاد</span> | |||
| <span v-else>ویرایش</span> | |||
| </button> | |||
| </div> | |||
| </div> | |||
| @@ -290,13 +283,13 @@ export default { | |||
| minOrder.value = discount.value.min_order; | |||
| selectedCat.value = discount.value.category_id; | |||
| if (discount.value.category_id) { | |||
| whichPart.value === "cat"; | |||
| whichPart.value = "cat"; | |||
| } | |||
| console.log(discount.value.product_id); | |||
| selectedProduct.value = discount.value.product_id; | |||
| if (discount.value.product_id) { | |||
| whichPart.value === "product"; | |||
| whichPart.value = "product"; | |||
| } | |||
| startDate.value = discount.value.starts_at; | |||
| expire.value = discount.value.expires_at; | |||
| @@ -309,16 +302,23 @@ export default { | |||
| const handleStartDateInput = () => { | |||
| if (startDate.value) { | |||
| startDate.value = moment(startDate.value).format("YYYY-MM-DD HH:mm:ss"); | |||
| startDate.value = moment( | |||
| startDate.value, | |||
| "jYYYY/jMM/jDD HH:mm:ss" | |||
| ).format("YYYY-MM-DD HH:mm:ss"); | |||
| } else { | |||
| clearError("expire"); | |||
| } | |||
| clearError("expire"); | |||
| }; | |||
| const handleExpireDateInput = () => { | |||
| if (expire.value) { | |||
| expire.value = moment(expire.value).format("YYYY-MM-DD HH:mm:ss"); | |||
| expire.value = moment(expire.value, "jYYYY/jMM/jDD HH:mm:ss").format( | |||
| "YYYY-MM-DD HH:mm:ss" | |||
| ); | |||
| } else { | |||
| clearError("expire"); | |||
| } | |||
| clearError("expire"); | |||
| }; | |||
| const validateForm = () => { | |||
| @@ -330,12 +330,12 @@ export default { | |||
| errors.value.amount = "وارد کردن مقدار تخفیف الزامی می باشد"; | |||
| if (!minOrder.value) | |||
| errors.value.minOrder = "وارد کردن حداقل میزان تخفیف الزامی می باشد"; | |||
| if ( | |||
| if ( | |||
| !selectedCat.value && | |||
| (whichPart.value === "cat" || whichPart.value === "both") | |||
| ) | |||
| errors.value.selectedCat = "انتخاب دسته برای تخفیف الزامی می باشد"; | |||
| if ( | |||
| if ( | |||
| !selectedProduct.value && | |||
| (whichPart.value === "product" || whichPart.value === "both") | |||
| ) | |||
| @@ -369,11 +369,11 @@ 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" || whichPart.value === "both") { | |||
| formData.append("category_id", selectedCat.value); | |||
| } | |||
| if ( whichPart.value === "product" || whichPart.value === 'both') { | |||
| if (whichPart.value === "product" || whichPart.value === "both") { | |||
| formData.append("product_id", selectedProduct.value); | |||
| } | |||
| @@ -383,6 +383,7 @@ export default { | |||
| ApiServiece.post(`/admin/discounts`, formData) | |||
| .then((resp) => { | |||
| loading.value = false; | |||
| toast.success("!تخفیف با موفقیت اضافه شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| @@ -390,6 +391,7 @@ export default { | |||
| console.log(resp); | |||
| }) | |||
| .catch((error) => { | |||
| loading.value = false; | |||
| console.log(error.response.message); | |||
| toast.error(`${error.response.data.message}`, { | |||
| position: "top-right", | |||
| @@ -416,6 +418,7 @@ export default { | |||
| handleExpireDateInput, | |||
| whichPart, | |||
| clearError, | |||
| loading, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -27,7 +27,7 @@ export default { | |||
| faqs.value = resp.data.data; | |||
| text.value = faqs.value.text; | |||
| status.value = faqs.value.status; | |||
| answerText.value = faqs.value?.children[0]?.text; | |||
| answerText.value = faqs.value?.answer?.text; | |||
| console.log(resp.data.data); | |||
| if(faqs.value?.children.length > 0){ | |||
| isAnswerExist.value = true | |||
| @@ -79,7 +79,7 @@ export default { | |||
| const deleteDiscount = (id, title) => { | |||
| Swal.fire({ | |||
| text: `می خواهید تخفیف ${title} را حذف کنید ؟`, | |||
| text: `می خواهید تخفیف ${title} را حذف کنید؟`, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| @@ -170,10 +170,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 bg-primary text-white" | |||
| 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"> | |||
| <input | |||
| v-model="searchQuery" | |||
| type="text" | |||
| @@ -181,7 +181,7 @@ export default { | |||
| class="form-control form-control-sm d-inline-block me-2" | |||
| style="width: 250px; border-radius: 15px" | |||
| /> | |||
| </div> | |||
| </div> --> | |||
| </div> | |||
| <div v-if="!filterLoading" class="card-body table-border-style p-0"> | |||
| <div class="table-responsive"> | |||
| @@ -0,0 +1,451 @@ | |||
| <script> | |||
| import Layout from "@/layout/custom.vue"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import moment from "jalali-moment"; | |||
| import { onMounted, ref, watch, computed } from "vue"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import Swal from "sweetalert2"; | |||
| import addIdentity from "@/components/modals/identity/addIdentity.vue"; | |||
| import editIdentity from "@/components/modals/identity/editIdentity.vue"; | |||
| export default { | |||
| name: "BORDER", | |||
| components: { | |||
| Layout, | |||
| addIdentity, | |||
| editIdentity, | |||
| }, | |||
| setup() { | |||
| const cats = ref([]); | |||
| const searchPage = ref(); | |||
| const currentPage = ref(1); | |||
| const totalPages = ref(1); | |||
| const paginate = ref(20); | |||
| const page = ref(1); | |||
| const attributeValues = ref(); | |||
| const filterLoading = ref(false); | |||
| const searchQuery = ref(""); | |||
| const attributes = ref(); | |||
| const attributeTitle = ref(); | |||
| const attributeId = ref(); | |||
| const attrebuteCat = ref(); | |||
| const convertToJalali = (date) => { | |||
| return moment(date, "YYYY-MM-DD HH:mm:ss") | |||
| .locale("fa") | |||
| .format("YYYY/MM/DD"); | |||
| }; | |||
| watch(searchQuery, () => { | |||
| getAttributes(); | |||
| }); | |||
| const getAttributes = () => { | |||
| filterLoading.value = true; | |||
| ApiServiece.get( | |||
| `admin/attributes?title=${searchQuery.value || ""} | |||
| &paginate=${paginate.value || 10}&page=${page.value || 1} | |||
| ` | |||
| ) | |||
| .then((resp) => { | |||
| filterLoading.value = false; | |||
| console.log(resp.data); | |||
| attributes.value = resp.data.data.data; | |||
| currentPage.value = resp.data.data.current_page; | |||
| totalPages.value = resp.data.data.last_page; | |||
| console.log(attributes.value); | |||
| }) | |||
| .catch(() => { | |||
| filterLoading.value = false; | |||
| }); | |||
| }; | |||
| const getCategories = () => { | |||
| ApiServiece.get("admin/categories").then((resp) => { | |||
| console.log(resp.data.data); | |||
| cats.value = resp.data.data; | |||
| }); | |||
| }; | |||
| const handleAttributeUpdated = () => { | |||
| getAttributes(); | |||
| }; | |||
| const nextPage = () => { | |||
| if (currentPage.value < totalPages.value) { | |||
| page.value++; | |||
| getAttributes(); | |||
| } | |||
| }; | |||
| const prevPage = () => { | |||
| if (currentPage.value > 1) { | |||
| page.value--; | |||
| getAttributes(); | |||
| } | |||
| }; | |||
| function handlePageInput() { | |||
| if (searchPage.value < 1) { | |||
| searchPage.value = 1; | |||
| } else if (searchPage.value > totalPages.value) { | |||
| searchPage.value = totalPages.value; | |||
| } | |||
| if (searchPage.value >= 1 && searchPage.value <= totalPages.value) { | |||
| page.value = searchPage.value; | |||
| } | |||
| } | |||
| const visiblePages = computed(() => { | |||
| const pages = []; | |||
| if (totalPages.value <= 5) { | |||
| for (let i = 1; i <= totalPages.value; i++) { | |||
| pages.push(i); | |||
| } | |||
| } else { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| } | |||
| } | |||
| return pages; | |||
| }); | |||
| watch(page, () => { | |||
| getAttributes(); | |||
| }); | |||
| const deleteAttribute = (id, title) => { | |||
| Swal.fire({ | |||
| text: `می خواهید ویژگی ${title} را حذف کنید؟`, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| cancelButtonColor: "#d33", | |||
| confirmButtonText: "بله!", | |||
| cancelButtonText: "خیر", | |||
| }).then((result) => { | |||
| if (result.isConfirmed) { | |||
| ApiServiece.delete(`admin/attribute-values/${id}`) | |||
| .then(() => { | |||
| toast.success("!ویژگی با موفقیت حذف شد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| attributes.value = attributes.value.filter( | |||
| (attribute) => attribute.id !== id | |||
| ); | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| toast.error("!مشکلی در حذف کردن ویژگی پیش آمد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| }); | |||
| } | |||
| }); | |||
| }; | |||
| const editModalData = (id, title, cat) => { | |||
| attributeId.value = id; | |||
| attributeTitle.value = title; | |||
| attrebuteCat.value = cat; | |||
| }; | |||
| watch(searchQuery, () => { | |||
| getAttributes(); | |||
| }); | |||
| onMounted(() => { | |||
| getAttributes(); | |||
| getCategories(); | |||
| }); | |||
| return { | |||
| attributes, | |||
| convertToJalali, | |||
| handleAttributeUpdated, | |||
| editModalData, | |||
| deleteAttribute, | |||
| searchQuery, | |||
| filterLoading, | |||
| attributeId, | |||
| attrebuteCat, | |||
| attributeTitle, | |||
| attributeValues, | |||
| searchPage, | |||
| currentPage, | |||
| totalPages, | |||
| paginate, | |||
| page, | |||
| prevPage, | |||
| nextPage, | |||
| handlePageInput, | |||
| visiblePages, | |||
| getCategories, | |||
| cats, | |||
| }; | |||
| }, | |||
| }; | |||
| </script> | |||
| <template> | |||
| <Layout> | |||
| <BRow> | |||
| <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 " | |||
| 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" | |||
| /> | |||
| <button | |||
| data-bs-toggle="modal" | |||
| data-bs-target="#addIdentity" | |||
| class="btn btn-light text-primary btn-sm px-3" | |||
| > | |||
| افزودن مشخصه | |||
| </button> | |||
| </div> | |||
| </div> | |||
| <div v-if="!filterLoading" class="card-body table-border-style p-0"> | |||
| <div 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> | |||
| <th>عملیات</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| <tr v-for="attribute in attributes" :key="attribute.id"> | |||
| <td>{{ attribute.title }}</td> | |||
| <td | |||
| :style="{ | |||
| backgroundColor: attribute.code, | |||
| textAlign: 'center', | |||
| }" | |||
| ></td> | |||
| <td>{{ convertToJalali(attribute?.created_at) }}</td> | |||
| <td> | |||
| <button | |||
| @click=" | |||
| editModalData( | |||
| attribute?.id, | |||
| attribute?.title, | |||
| attribute.category_id | |||
| ) | |||
| " | |||
| data-bs-toggle="modal" | |||
| data-bs-target="#editIdentity" | |||
| class="btn btn-sm btn-outline-warning me-1" | |||
| > | |||
| ویرایش | |||
| </button> | |||
| <button | |||
| @click="deleteAttribute(attribute.id, attribute.title)" | |||
| class="btn btn-sm btn-outline-danger" | |||
| :disabled="attribute.id == 1" | |||
| > | |||
| حذف | |||
| </button> | |||
| </td> | |||
| </tr> | |||
| </tbody> | |||
| </table> | |||
| </div> | |||
| </div> | |||
| <div | |||
| v-else | |||
| class="filter-loader card table-card user-profile-list" | |||
| ></div> | |||
| </div> | |||
| </div> | |||
| <addIdentity @attribute-updated="handleAttributeUpdated()" :cats="cats" /> | |||
| <editIdentity | |||
| @attribute-updated="handleAttributeUpdated()" | |||
| :title="attributeTitle" | |||
| :catId="attrebuteCat" | |||
| :id="attributeId" | |||
| :cats="cats" | |||
| /> | |||
| </BRow> | |||
| <BRow> | |||
| <BCol sm="12"> | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| class="page-item" | |||
| :class="{ active: currentPage === n }" | |||
| > | |||
| <a | |||
| class="page-link" | |||
| href="javascript:void(0)" | |||
| @click="page = n" | |||
| > | |||
| {{ n }} | |||
| </a> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| </li> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| > | |||
| <span class="page-link" @click="nextPage">بعدی</span> | |||
| </li> | |||
| </ul> | |||
| </nav> | |||
| </div> | |||
| </BCol> | |||
| <BCol sm="4"> | |||
| <div class="ms-0 search-number"> | |||
| <input | |||
| v-model="searchPage" | |||
| type="text" | |||
| class="form-control" | |||
| placeholder="برو به صفحه" | |||
| :max="totalPages" | |||
| min="1" | |||
| @input="handlePageInput" | |||
| /> | |||
| </div> | |||
| </BCol> | |||
| </BRow> | |||
| </Layout> | |||
| </template> | |||
| <style scoped> | |||
| .card { | |||
| transition: transform 0.3s ease; | |||
| } | |||
| .card:hover { | |||
| transform: translateY(-3px); | |||
| } | |||
| .table th, | |||
| .table td { | |||
| vertical-align: middle; | |||
| text-align: center; | |||
| } | |||
| .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; | |||
| } | |||
| .Brand-Image { | |||
| width: 100px; | |||
| height: 100px; | |||
| object-fit: cover; | |||
| border-radius: 8px; | |||
| border: 1px solid #ddd; | |||
| } | |||
| .subject-box { | |||
| padding: 8px 14px; | |||
| background: linear-gradient(135deg, #fff3e0, #ffe0b2); | |||
| color: #ef6c00; | |||
| font-weight: 600; | |||
| border-radius: 10px; | |||
| box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1); | |||
| display: inline-flex; | |||
| align-items: center; | |||
| gap: 8px; | |||
| transition: transform 0.2s ease, box-shadow 0.2s ease; | |||
| } | |||
| .subject-box i { | |||
| color: #ef6c00; | |||
| font-size: 1rem; | |||
| } | |||
| .subject-box:hover { | |||
| transform: translateY(-2px); | |||
| box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.15); | |||
| } | |||
| .search-number { | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| .search-number input { | |||
| width: 150px; | |||
| padding: 0.5rem; | |||
| font-size: 1rem; | |||
| border-radius: 0.375rem; | |||
| margin-bottom: 7px; | |||
| border: 1px solid #ced4da; | |||
| transition: border-color 0.3s ease, box-shadow 0.3s ease; | |||
| } | |||
| .search-number input:focus { | |||
| border-color: #007bff; | |||
| box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.25); | |||
| } | |||
| .search-number input::placeholder { | |||
| color: #6c757d; | |||
| } | |||
| .search-number input:disabled { | |||
| background-color: #f8f9fa; | |||
| cursor: not-allowed; | |||
| } | |||
| .pagination { | |||
| display: flex; | |||
| flex-wrap: wrap; | |||
| gap: 5px; | |||
| } | |||
| .page-item { | |||
| flex: 0 0 auto; | |||
| } | |||
| .page-link { | |||
| cursor: pointer; | |||
| user-select: none; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,304 @@ | |||
| <script> | |||
| import Layout from "@/layout/custom.vue"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import { onMounted, ref, watch, computed } from "vue"; | |||
| import moment from "jalali-moment"; | |||
| export default { | |||
| name: "PRODUCT-LIST", | |||
| components: { | |||
| Layout, | |||
| }, | |||
| setup() { | |||
| const searchPage = ref(); | |||
| const currentPage = ref(1); | |||
| const totalPages = ref(1); | |||
| const paginate = ref(20); | |||
| const page = ref(1); | |||
| const filterLoading = ref(false); | |||
| const allOrders = ref([]); | |||
| const getAllOrders = () => { | |||
| filterLoading.value = true; | |||
| ApiServiece.get( | |||
| `admin/orders?paginate=${paginate.value || 10}&page=${page.value || 1}` | |||
| ) | |||
| .then((resp) => { | |||
| console.log(resp); | |||
| allOrders.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 convertToJalali = (date) => { | |||
| return moment(date, "YYYY-MM-DD HH:mm:ss") | |||
| .locale("fa") | |||
| .format("YYYY/MM/DD"); | |||
| }; | |||
| const getStatusLabel = (status) => { | |||
| const statusLabels = { | |||
| waiting: "در انتظار", | |||
| paid: "پرداختشده", | |||
| un_paid: "پرداختنشده", | |||
| approved: "تأییدشده", | |||
| processing: "در حال پردازش", | |||
| shipping: "در حال ارسال", | |||
| delivered: "تحویلشده", | |||
| canceled: "لغوشده", | |||
| }; | |||
| return statusLabels[status] || "نامشخص"; | |||
| }; | |||
| const visiblePages = computed(() => { | |||
| const pages = []; | |||
| if (totalPages.value <= 5) { | |||
| for (let i = 1; i <= totalPages.value; i++) { | |||
| pages.push(i); | |||
| } | |||
| } else { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| } | |||
| } | |||
| return pages; | |||
| }); | |||
| function handlePageInput() { | |||
| if (searchPage.value < 1) { | |||
| searchPage.value = 1; | |||
| } else if (searchPage.value > totalPages.value) { | |||
| searchPage.value = totalPages.value; | |||
| } | |||
| if (searchPage.value >= 1 && searchPage.value <= totalPages.value) { | |||
| page.value = searchPage.value; | |||
| } | |||
| } | |||
| watch(page, () => { | |||
| getAllOrders(); | |||
| }); | |||
| const nextPage = () => { | |||
| if (currentPage.value < totalPages.value) { | |||
| page.value++; | |||
| getAllOrders(); | |||
| } | |||
| }; | |||
| const prevPage = () => { | |||
| if (currentPage.value > 1) { | |||
| page.value--; | |||
| getAllOrders(); | |||
| } | |||
| }; | |||
| onMounted(() => { | |||
| getAllOrders(); | |||
| }); | |||
| return { | |||
| allOrders, | |||
| visiblePages, | |||
| nextPage, | |||
| prevPage, | |||
| handlePageInput, | |||
| currentPage, | |||
| totalPages, | |||
| page, | |||
| convertToJalali, | |||
| searchPage, | |||
| getStatusLabel | |||
| }; | |||
| }, | |||
| }; | |||
| </script> | |||
| <template> | |||
| <Layout> | |||
| <BRow> | |||
| <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 --> | |||
| </div> | |||
| <div class="table-responsive"> | |||
| <table class="table table-hover tbl-product" id="pc-dt-simple"> | |||
| <thead> | |||
| <tr> | |||
| <th >شناسه</th> | |||
| <th>تاریخ ایحاد</th> | |||
| <th>کد رهگیری</th> | |||
| <th>وضعیت</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| <tr v-for="order in allOrders" :key="order?.id"> | |||
| <td >{{ order?.id }}</td> | |||
| <td>{{ convertToJalali(order?.created_at) }}</td> | |||
| <td>{{order?.tracking_code}}</td> | |||
| <td >{{ order?.status }}</td> | |||
| </tr> | |||
| </tbody> | |||
| </table> | |||
| </div> | |||
| </BCardBody> | |||
| </BCard> | |||
| </BCol> | |||
| </BRow> | |||
| <BRow> | |||
| <BCol sm="12"> | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| class="page-item" | |||
| :class="{ active: currentPage === n }" | |||
| > | |||
| <a | |||
| class="page-link" | |||
| href="javascript:void(0)" | |||
| @click="page = n" | |||
| > | |||
| {{ n }} | |||
| </a> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| </li> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| > | |||
| <span class="page-link" @click="nextPage">بعدی</span> | |||
| </li> | |||
| </ul> | |||
| </nav> | |||
| </div> | |||
| </BCol> | |||
| <BCol sm="4"> | |||
| <div class="ms-0 search-number"> | |||
| <input | |||
| v-model="searchPage" | |||
| type="text" | |||
| class="form-control" | |||
| placeholder="برو به صفحه" | |||
| :max="totalPages" | |||
| min="1" | |||
| @input="handlePageInput" | |||
| /> | |||
| </div> | |||
| </BCol> | |||
| </BRow> | |||
| </Layout> | |||
| </template> | |||
| <style scoped> | |||
| .product-img { | |||
| max-width: 48px; | |||
| min-width: 48px; | |||
| max-height: 45px; | |||
| min-height: 45px; | |||
| object-fit: cover; | |||
| } | |||
| .brand-img { | |||
| max-width: 43px; | |||
| min-width: 43px; | |||
| max-height: 40px; | |||
| min-height: 40px; | |||
| object-fit: cover; | |||
| } | |||
| .search-number { | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| .search-number input { | |||
| width: 150px; | |||
| padding: 0.5rem; | |||
| font-size: 1rem; | |||
| border-radius: 0.375rem; | |||
| margin-bottom: 7px; | |||
| border: 1px solid #ced4da; | |||
| transition: border-color 0.3s ease, box-shadow 0.3s ease; | |||
| } | |||
| .search-number input:focus { | |||
| border-color: #007bff; | |||
| box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.25); | |||
| } | |||
| .search-number input::placeholder { | |||
| color: #6c757d; | |||
| } | |||
| .search-number input:disabled { | |||
| background-color: #f8f9fa; | |||
| cursor: not-allowed; | |||
| } | |||
| .pagination { | |||
| display: flex; | |||
| flex-wrap: wrap; | |||
| gap: 5px; | |||
| } | |||
| .page-item { | |||
| flex: 0 0 auto; | |||
| } | |||
| .page-link { | |||
| cursor: pointer; | |||
| user-select: none; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,466 @@ | |||
| <script> | |||
| import Layout from "@/layout/custom.vue"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import { onMounted, ref, watch, computed } from "vue"; | |||
| import DatePicker from "vue3-persian-datetime-picker"; | |||
| import Select2 from "vue3-select2-component"; | |||
| import moment from "jalali-moment"; | |||
| export default { | |||
| name: "PRODUCT-LIST", | |||
| components: { | |||
| Layout, | |||
| Select2, | |||
| DatePicker, | |||
| }, | |||
| setup() { | |||
| const isLoading = ref(false); | |||
| const date = ref([]); | |||
| const brands = ref([]); | |||
| const searchPage = ref(); | |||
| const currentPage = ref(1); | |||
| const totalPages = ref(1); | |||
| const paginate = ref(20); | |||
| const page = ref(1); | |||
| const filterLoading = ref(false); | |||
| const selectedBrand = ref(); | |||
| const allProducts = ref([]); | |||
| const getAllProducts = () => { | |||
| filterLoading.value = true; | |||
| ApiServiece.get( | |||
| `admin/orders/order-items/approved?brand_id=${ | |||
| selectedBrand.value || "" | |||
| }&start_date=${date.value[0] || ""}&end_date=${ | |||
| date.value[1] || "" | |||
| }&paginate=${paginate.value || 10}&page=${page.value || 1}` | |||
| ) | |||
| .then((resp) => { | |||
| console.log(resp); | |||
| 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); | |||
| }); | |||
| }; | |||
| const convertToJalali = (date) => { | |||
| return moment(date, "YYYY-MM-DD HH:mm:ss") | |||
| .locale("fa") | |||
| .format("YYYY/MM/DD"); | |||
| }; | |||
| const formattedUsers = computed(() => { | |||
| return brands.value.map((brand) => ({ | |||
| id: brand?.id, | |||
| text: brand?.title, | |||
| })); | |||
| }); | |||
| const getFile = () => { | |||
| isLoading.value = true; | |||
| ApiServiece.post( | |||
| "admin/orders/order-items/approved/export", | |||
| {}, | |||
| { responseType: "blob" } | |||
| ) | |||
| .then((resp) => { | |||
| const excelBlob = resp.data; | |||
| const url = window.URL.createObjectURL(excelBlob); | |||
| const link = document.createElement("a"); | |||
| link.href = url; | |||
| link.setAttribute("download", "order-items-export.xlsx"); | |||
| document.body.appendChild(link); | |||
| link.click(); | |||
| window.URL.revokeObjectURL(url); | |||
| document.body.removeChild(link); | |||
| isLoading.value = false; | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| isLoading.value = false; | |||
| }); | |||
| }; | |||
| watch(selectedBrand, () => { | |||
| getAllProducts(); | |||
| page.value = 1; | |||
| }); | |||
| const visiblePages = computed(() => { | |||
| const pages = []; | |||
| if (totalPages.value <= 5) { | |||
| for (let i = 1; i <= totalPages.value; i++) { | |||
| pages.push(i); | |||
| } | |||
| } else { | |||
| let start = currentPage.value - 2; | |||
| let end = currentPage.value + 2; | |||
| if (start < 1) start = 1; | |||
| if (end > totalPages.value) end = totalPages.value; | |||
| for (let i = start; i <= end; i++) { | |||
| pages.push(i); | |||
| } | |||
| } | |||
| return pages; | |||
| }); | |||
| function handlePageInput() { | |||
| if (searchPage.value < 1) { | |||
| searchPage.value = 1; | |||
| } else if (searchPage.value > totalPages.value) { | |||
| searchPage.value = totalPages.value; | |||
| } | |||
| if (searchPage.value >= 1 && searchPage.value <= totalPages.value) { | |||
| page.value = searchPage.value; | |||
| } | |||
| } | |||
| watch(selectedBrand, () => { | |||
| getAllProducts(); | |||
| }); | |||
| watch(date, () => { | |||
| getAllProducts(); | |||
| }); | |||
| watch(page, () => { | |||
| getAllProducts(); | |||
| }); | |||
| const nextPage = () => { | |||
| if (currentPage.value < totalPages.value) { | |||
| page.value++; | |||
| getAllProducts(); | |||
| } | |||
| }; | |||
| const prevPage = () => { | |||
| if (currentPage.value > 1) { | |||
| page.value--; | |||
| getAllProducts(); | |||
| } | |||
| }; | |||
| function formatWithCommas(number) { | |||
| return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); | |||
| } | |||
| onMounted(() => { | |||
| getAllProducts(); | |||
| getAllBrands(); | |||
| }); | |||
| return { | |||
| allProducts, | |||
| visiblePages, | |||
| nextPage, | |||
| prevPage, | |||
| handlePageInput, | |||
| currentPage, | |||
| totalPages, | |||
| page, | |||
| getFile, | |||
| convertToJalali, | |||
| searchPage, | |||
| brands, | |||
| formattedUsers, | |||
| selectedBrand, | |||
| date, | |||
| isLoading, | |||
| formatWithCommas, | |||
| }; | |||
| }, | |||
| }; | |||
| </script> | |||
| <template> | |||
| <Layout> | |||
| <BRow> | |||
| <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"> | |||
| <Select2 | |||
| id="token" | |||
| v-model="selectedBrand" | |||
| :options="formattedUsers" | |||
| :settings="{ | |||
| placeholder: 'انتخاب برند', | |||
| dir: 'rtl', | |||
| width: '100%', | |||
| theme: 'classic', | |||
| }" | |||
| class="select2 custom-select2" | |||
| /> | |||
| </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> | |||
| <div class="table-responsive"> | |||
| <table class="table table-hover tbl-product" id="pc-dt-simple"> | |||
| <thead> | |||
| <tr> | |||
| <th class="text-end">#</th> | |||
| <th>جزییات محصول</th> | |||
| <th>تاریخ ایحاد</th> | |||
| <th class="text-end">قیمت عمده</th> | |||
| <th class="text-end">قیمت تک</th> | |||
| <th class="text-end">تعداد سفارش</th> | |||
| <th class="text-end">تعداد ارسال شده</th> | |||
| <th class="text-center">عنوان برند</th> | |||
| <th class="text-center">تصویر برند</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| <tr v-for="product in allProducts" :key="product?.id"> | |||
| <td class="text-end">5</td> | |||
| <td> | |||
| <BRow> | |||
| <BCol class="col-auto pe-5"> | |||
| <img | |||
| :src="product?.product?.image" | |||
| alt="user-image" | |||
| class="wid-40 rounded product-img" | |||
| /> | |||
| </BCol> | |||
| <BCol> | |||
| <h6 class="mb-1">{{ product?.product?.title }}</h6> | |||
| <p class="text-muted f-12 mb-0"> | |||
| {{ product.product.description.slice(0, 25) | |||
| }}{{ | |||
| product.product.description.length > 25 | |||
| ? "..." | |||
| : "" | |||
| }} | |||
| </p> | |||
| </BCol> | |||
| </BRow> | |||
| </td> | |||
| <td>{{ convertToJalali(product.created_at) }}</td> | |||
| <td | |||
| v-if="product?.product?.wholesale_price" | |||
| class="text-end" | |||
| > | |||
| {{ formatWithCommas(product?.product?.wholesale_price) }}تومان | |||
| </td> | |||
| <td | |||
| v-if="!product?.product?.wholesale_price" | |||
| class="text-end" | |||
| > | |||
| <i | |||
| class="ph-duotone ph-x-circle text-danger f-24" | |||
| data-bs-toggle="tooltip" | |||
| data-bs-title="danger" | |||
| ></i> | |||
| </td> | |||
| <td v-if="product?.product?.retail_price" class="text-end"> | |||
| {{ formatWithCommas(product?.product?.retail_price) }} | |||
| </td> | |||
| <td v-if="!product?.product?.retail_price" class="text-end"> | |||
| <i | |||
| class="ph-duotone ph-x-circle text-danger f-24" | |||
| data-bs-toggle="tooltip" | |||
| data-bs-title="danger" | |||
| ></i> | |||
| </td> | |||
| <td class="text-end">{{ product.count }}</td> | |||
| <td class="text-end">{{ product.send_count }}</td> | |||
| <td class="text-center"> | |||
| {{ product.product?.brand?.title }} | |||
| </td> | |||
| <td class="text-center"> | |||
| <img | |||
| :src="product.product?.brand?.image" | |||
| alt="user-image" | |||
| class="wid-40 brand-img" | |||
| /> | |||
| </td> | |||
| </tr> | |||
| </tbody> | |||
| </table> | |||
| </div> | |||
| </BCardBody> | |||
| </BCard> | |||
| </BCol> | |||
| </BRow> | |||
| <BRow> | |||
| <BCol sm="12"> | |||
| <div class="d-flex justify-content-center"> | |||
| <nav aria-label="Page navigation"> | |||
| <ul class="pagination"> | |||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||
| <span class="page-link" @click="prevPage">قبلی</span> | |||
| </li> | |||
| <li v-if="currentPage > 2" class="page-item" @click="page = 1"> | |||
| <a class="page-link" href="javascript:void(0)">1</a> | |||
| </li> | |||
| <li v-if="currentPage > 3" class="page-item" disabled> | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-for="n in visiblePages" | |||
| :key="n" | |||
| class="page-item" | |||
| :class="{ active: currentPage === n }" | |||
| > | |||
| <a | |||
| class="page-link" | |||
| href="javascript:void(0)" | |||
| @click="page = n" | |||
| > | |||
| {{ n }} | |||
| </a> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 2" | |||
| class="page-item" | |||
| disabled | |||
| > | |||
| <span class="page-link">...</span> | |||
| </li> | |||
| <li | |||
| v-if="currentPage < totalPages - 1" | |||
| class="page-item" | |||
| @click="page = totalPages" | |||
| > | |||
| <a class="page-link" href="javascript:void(0)">{{ | |||
| totalPages | |||
| }}</a> | |||
| </li> | |||
| <li | |||
| class="page-item" | |||
| :class="{ disabled: currentPage === totalPages }" | |||
| > | |||
| <span class="page-link" @click="nextPage">بعدی</span> | |||
| </li> | |||
| </ul> | |||
| </nav> | |||
| </div> | |||
| </BCol> | |||
| <BCol sm="4"> | |||
| <div class="ms-0 search-number"> | |||
| <input | |||
| v-model="searchPage" | |||
| type="text" | |||
| class="form-control" | |||
| placeholder="برو به صفحه" | |||
| :max="totalPages" | |||
| min="1" | |||
| @input="handlePageInput" | |||
| /> | |||
| </div> | |||
| </BCol> | |||
| </BRow> | |||
| </Layout> | |||
| </template> | |||
| <style scoped> | |||
| .product-img { | |||
| max-width: 48px; | |||
| min-width: 48px; | |||
| max-height: 45px; | |||
| min-height: 45px; | |||
| object-fit: cover; | |||
| } | |||
| .brand-img { | |||
| max-width: 43px; | |||
| min-width: 43px; | |||
| max-height: 40px; | |||
| min-height: 40px; | |||
| object-fit: cover; | |||
| } | |||
| .search-number { | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| .search-number input { | |||
| width: 150px; | |||
| padding: 0.5rem; | |||
| font-size: 1rem; | |||
| border-radius: 0.375rem; | |||
| margin-bottom: 7px; | |||
| border: 1px solid #ced4da; | |||
| transition: border-color 0.3s ease, box-shadow 0.3s ease; | |||
| } | |||
| .search-number input:focus { | |||
| border-color: #007bff; | |||
| box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.25); | |||
| } | |||
| .search-number input::placeholder { | |||
| color: #6c757d; | |||
| } | |||
| .search-number input:disabled { | |||
| background-color: #f8f9fa; | |||
| cursor: not-allowed; | |||
| } | |||
| .pagination { | |||
| display: flex; | |||
| flex-wrap: wrap; | |||
| gap: 5px; | |||
| } | |||
| .page-item { | |||
| flex: 0 0 auto; | |||
| } | |||
| .page-link { | |||
| cursor: pointer; | |||
| user-select: none; | |||
| } | |||
| </style> | |||
| @@ -98,9 +98,10 @@ export default { | |||
| }; | |||
| return statusLabels[status] || "نامشخص"; | |||
| }; | |||
| const deleteOrder = (id) => { | |||
| Swal.fire({ | |||
| text: `می خواهید سفارش ${id} را حذف کنید ؟`, | |||
| text: `می خواهید سفارش ${id} را حذف کنید؟`, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| @@ -128,6 +129,42 @@ export default { | |||
| }); | |||
| }; | |||
| const changeStatus = (id, status) => { | |||
| Swal.fire({ | |||
| text: `آیا می خواهید وضعیت سبد خرید را به ${getStatusLabel( | |||
| status | |||
| )} تغییر دهید؟ `, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| cancelButtonColor: "#d33", | |||
| confirmButtonText: "بله!", | |||
| cancelButtonText: "خیر", | |||
| }).then((result) => { | |||
| if (result.isConfirmed) { | |||
| const formData = new FormData(); | |||
| formData.append("status", status); | |||
| ApiServiece.put(`admin/orders/${id}`, formData) | |||
| .then(() => { | |||
| toast.success("!تغییر وضعیت سبد خرید با موفقیت انجام شد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| }) | |||
| .then(() => { | |||
| getOrders(); | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| toast.error("!مشکلی در تغییر وضعیت سبد خرید پیش آمد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| }); | |||
| } | |||
| }); | |||
| }; | |||
| function handlePageInput() { | |||
| if (searchPage.value < 1) { | |||
| searchPage.value = 1; | |||
| @@ -168,11 +205,10 @@ export default { | |||
| return { | |||
| orders, | |||
| convertToJalali, | |||
| deleteOrder, | |||
| searchQuery, | |||
| filterLoading, | |||
| changeStatus, | |||
| currentPage, | |||
| totalPages, | |||
| nextPage, | |||
| @@ -193,7 +229,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 bg-primary text-white" | |||
| class="card-header d-flex justify-content-between align-items-center p-3" | |||
| dir="rtl" | |||
| > | |||
| <div class="d-flex align-items-center"> | |||
| @@ -238,11 +274,95 @@ export default { | |||
| مشاهده | |||
| </router-link> | |||
| <button | |||
| class="btn btn-sm btn-outline-warning dropdown-toggle me-1" | |||
| type="button" | |||
| id="dropdownMenuButton" | |||
| data-bs-toggle="dropdown" | |||
| aria-expanded="false" | |||
| > | |||
| ویرایش وضعیت | |||
| </button> | |||
| <ul | |||
| class="dropdown-menu" | |||
| aria-labelledby="dropdownMenuButton" | |||
| > | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus(order?.id, 'waiting')" | |||
| ><span class="badge badge-waiting" | |||
| >در انتظار</span | |||
| ></a | |||
| > | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus(order?.id, 'paid')" | |||
| ><span class="badge badge-paid">پرداختشده</span></a | |||
| > | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus(order?.id, 'un_paid')" | |||
| ><span class="badge badge-un_paid" | |||
| >پرداختنشده</span | |||
| ></a | |||
| > | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus(order?.id, 'approved')" | |||
| ><span class="badge badge-approved" | |||
| >تأییدشده</span | |||
| ></a | |||
| > | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus(order?.id, 'processing')" | |||
| ><span class="badge badge-processing" | |||
| >در حال پردازش</span | |||
| ></a | |||
| > | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus(order?.id, 'shipping')" | |||
| ><span class="badge badge-shipping" | |||
| >در حال ارسال</span | |||
| ></a | |||
| > | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus(order?.id, 'delivered')" | |||
| ><span class="badge badge-delivered" | |||
| >تحویلشده</span | |||
| ></a | |||
| > | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus(order?.id, 'canceled')" | |||
| ><span class="badge badge-canceled" | |||
| >لغوشده</span | |||
| ></a | |||
| > | |||
| </li> | |||
| </ul> | |||
| <!-- <button | |||
| @click="deleteOrder(order?.id)" | |||
| class="btn btn-sm btn-outline-danger" | |||
| > | |||
| حذف | |||
| </button> | |||
| </button> --> | |||
| </td> | |||
| </tr> | |||
| </tbody> | |||
| @@ -454,4 +574,7 @@ export default { | |||
| .badge-canceled { | |||
| background-color: #6c757d; | |||
| } | |||
| .dropdown-item { | |||
| cursor: pointer; | |||
| } | |||
| </style> | |||
| @@ -3,9 +3,6 @@ import Layout from "@/layout/custom.vue"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import { useRoute } from "vue-router"; | |||
| import { onMounted, ref } from "vue"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import Swal from "sweetalert2"; | |||
| export default { | |||
| name: "OrderDetails", | |||
| @@ -59,40 +56,47 @@ export default { | |||
| }); | |||
| }; | |||
| const changeStatus = (status) => { | |||
| Swal.fire({ | |||
| text: `آیا می خواهید وضعیت سفارش را به ${status} تغییر دهید ؟ `, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| cancelButtonColor: "#d33", | |||
| confirmButtonText: "بله!", | |||
| cancelButtonText: "خیر", | |||
| }).then((result) => { | |||
| if (result.isConfirmed) { | |||
| const formData = new FormData(); | |||
| formData.append("status", status); | |||
| ApiServiece.put(`admin/orders/${route.params.id}` , formData) | |||
| .then(() => { | |||
| toast.success("!تغییر وضعیت با موفقیت انجام شد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| }) | |||
| .then(()=>{ | |||
| getOrder() | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| toast.error("!مشکلی در تغییر وضعیت سفارش پیش آمد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| }); | |||
| } | |||
| }); | |||
| const updateShippedCount = (item) => { | |||
| const formData = new FormData(); | |||
| formData.append("send_count", item.send_count); | |||
| ApiServiece.put(`admin/orders/order-items/${item.id}`, formData) | |||
| .then(() => { | |||
| console.log("Shipped quantity updated successfully."); | |||
| }) | |||
| .catch((error) => { | |||
| console.error("Failed to update shipped quantity:", error); | |||
| }); | |||
| }; | |||
| const updateNote = (item) => { | |||
| // Call API to update the note | |||
| console.log(item); | |||
| const formData = new FormData(); | |||
| formData.append("description", item.description); | |||
| ApiServiece.put(`admin/orders/order-items/${item.id}`, formData) | |||
| .then(() => { | |||
| console.log("Note updated successfully."); | |||
| }) | |||
| .catch((error) => { | |||
| console.error("Failed to update note:", error); | |||
| }); | |||
| }; | |||
| const updateStatus = (item) => { | |||
| const formData = new FormData(); | |||
| formData.append("status", item.status); | |||
| ApiServiece.put(`admin/orders/order-items/${item.id}`, formData) | |||
| .then(() => { | |||
| console.log("Status updated successfully."); | |||
| }) | |||
| .catch((error) => { | |||
| console.error("Failed to update status:", error); | |||
| }); | |||
| }; | |||
| function formatWithCommas(number) { | |||
| return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); | |||
| } | |||
| onMounted(() => { | |||
| getOrder(); | |||
| }); | |||
| @@ -102,7 +106,10 @@ export default { | |||
| getStatusClass, | |||
| formatDate, | |||
| getStatusLabel, | |||
| changeStatus, | |||
| updateShippedCount, | |||
| updateNote, | |||
| updateStatus, | |||
| formatWithCommas | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -117,128 +124,81 @@ export default { | |||
| class="d-flex justify-content-between align-items-center" | |||
| > | |||
| <h5 class="mb-0">جزئیات سفارش</h5> | |||
| <div class="dropdown"> | |||
| <button | |||
| class="btn btn-sm btn-outline-warning dropdown-toggle me-1" | |||
| type="button" | |||
| id="dropdownMenuButton" | |||
| data-bs-toggle="dropdown" | |||
| aria-expanded="false" | |||
| > | |||
| ویرایش وضعیت | |||
| </button> | |||
| <ul class="dropdown-menu" aria-labelledby="dropdownMenuButton"> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus('waiting')" | |||
| ><span class="badge badge-waiting">در انتظار</span></a | |||
| > | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus('paid')" | |||
| ><span class="badge badge-paid">پرداختشده</span></a | |||
| > | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus('un_paid')" | |||
| ><span class="badge badge-un_paid">پرداختنشده</span></a | |||
| > | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus('approved')" | |||
| ><span class="badge badge-approved">تأییدشده</span></a | |||
| > | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus('processing')" | |||
| ><span class="badge badge-processing" | |||
| >در حال پردازش</span | |||
| ></a | |||
| > | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus('shipping')" | |||
| ><span class="badge badge-shipping">در حال ارسال</span></a | |||
| > | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus('delivered')" | |||
| ><span class="badge badge-delivered">تحویلشده</span></a | |||
| > | |||
| </li> | |||
| <li> | |||
| <a | |||
| class="dropdown-item d-flex justify-content-center align-items-center" | |||
| @click="changeStatus('canceled')" | |||
| ><span class="badge badge-canceled">لغوشده</span></a | |||
| > | |||
| </li> | |||
| </ul> | |||
| </div> | |||
| </BCardHeader> | |||
| <BCardBody v-if="order"> | |||
| <BRow class="mb-3"> | |||
| <BCol md="6"> <strong>شناسه سفارش:</strong> {{ order.id }} </BCol> | |||
| <BCol md="6"> | |||
| <strong>وضعیت:</strong> | |||
| <span class="badge me-2" :class="getStatusClass(order.status)"> | |||
| {{ getStatusLabel(order.status) }} | |||
| </span> | |||
| </BCol> | |||
| </BRow> | |||
| <BRow class="mb-3"> | |||
| <BCol md="6"> | |||
| <strong>قیمت کل:</strong> {{ order.total_price }} تومان | |||
| </BCol> | |||
| <BCol md="6"> | |||
| <strong>هزینه ارسال:</strong> {{ order.shipping_price }} تومان | |||
| </BCol> | |||
| </BRow> | |||
| <BRow class="mb-3"> | |||
| <BCol md="6"> | |||
| <strong>تاریخ ثبت سفارش:</strong> | |||
| {{ formatDate(order.created_at) }} | |||
| </BCol> | |||
| <BCol md="6"> | |||
| <strong>تاریخ بروزرسانی:</strong> | |||
| {{ formatDate(order.updated_at) }} | |||
| </BCol> | |||
| </BRow> | |||
| <!-- Order Details and Items Here --> | |||
| <!-- Order Items --> | |||
| <h6 class="mt-4 mb-3">آیتم های سفارش</h6> | |||
| <div v-if="order.order_items.length > 0"> | |||
| <BTable | |||
| hover | |||
| class="table-header" | |||
| bordered | |||
| :items="order.order_items" | |||
| :fields="[ | |||
| { key: 'id', label: 'شناسه آیتم' }, | |||
| { key: 'count', label: 'تعداد' }, | |||
| { key: 'title', label: 'عنوان محصول' }, | |||
| { key: 'count', label: 'تعداد در خواستی ' }, | |||
| { key: 'price', label: 'قیمت' }, | |||
| { key: 'created_at', label: 'تاریخ ثبت' }, | |||
| { key: 'edit_count', label: 'تعداد فرستاده شده' }, | |||
| { key: 'description', label: 'یادداشت' }, | |||
| { key: 'status', label: 'ویرایش وضعیت' }, | |||
| ]" | |||
| > | |||
| <!-- Price formatting --> | |||
| <template #cell(price)="data"> | |||
| {{ data.item.price }} تومان | |||
| {{ formatWithCommas(data.item.price) }} تومان | |||
| </template> | |||
| <template #cell(title)="data"> | |||
| {{ data.item?.product?.title }} | |||
| </template> | |||
| <!-- Created Date formatting --> | |||
| <template #cell(created_at)="data"> | |||
| {{ formatDate(data.item.created_at) }} | |||
| </template> | |||
| <!-- Requested Quantity (Non-editable) --> | |||
| <template #cell(count)="data"> | |||
| {{ data.item.count }} | |||
| </template> | |||
| <!-- Editable shipped quantity with API call --> | |||
| <template #cell(edit_count)="data"> | |||
| <input | |||
| v-model="data.item.send_count" | |||
| type="number" | |||
| class="form-control" | |||
| :min="1" | |||
| :max="data.item.count" | |||
| placeholder="تعداد فرستاده شده" | |||
| @input="updateShippedCount(data.item)" | |||
| /> | |||
| </template> | |||
| <!-- Editable note with API call on focus out --> | |||
| <template #cell(description)="data"> | |||
| <textarea | |||
| v-model="data.item.description" | |||
| class="form-control" | |||
| placeholder="یادداشت" | |||
| @focusout="updateNote(data.item)" | |||
| ></textarea> | |||
| </template> | |||
| <!-- Editable status cell --> | |||
| <template #cell(status)="data"> | |||
| <select | |||
| v-model="data.item.status" | |||
| class="form-control selector" | |||
| @change="updateStatus(data.item)" | |||
| placeholder="وضعیت" | |||
| > | |||
| <option value="done">کامل شده</option> | |||
| <option value="processing">در انتظار</option> | |||
| </select> | |||
| </template> | |||
| </BTable> | |||
| </div> | |||
| <div v-else class="text-center text-muted my-3"> | |||
| @@ -293,7 +253,14 @@ export default { | |||
| h5 { | |||
| font-weight: bold; | |||
| } | |||
| .dropdown-item{ | |||
| cursor: pointer; | |||
| .dropdown-item { | |||
| cursor: pointer; | |||
| } | |||
| .table-header { | |||
| text-align: center; | |||
| } | |||
| .selector { | |||
| padding: 4px; | |||
| height: 35px; | |||
| } | |||
| </style> | |||
| @@ -8,7 +8,6 @@ | |||
| </BCardHeader> | |||
| <BCardBody> | |||
| <BRow class="g-3"> | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">عنوان </label> | |||
| @@ -26,15 +25,14 @@ | |||
| </small> | |||
| </BCol> | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">اسلاگ</label> | |||
| <label class="form-label">کلمه کلیدی</label> | |||
| <input | |||
| type="text" | |||
| v-model="slug" | |||
| class="form-control" | |||
| placeholder="اسلاگ محصول" | |||
| placeholder="کلمه کلیدی محصول" | |||
| :class="{ 'is-invalid': errors.slug }" | |||
| @input="clearError('slug')" | |||
| /> | |||
| @@ -44,7 +42,6 @@ | |||
| </small> | |||
| </BCol> | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">خلاصه</label> | |||
| @@ -149,6 +146,23 @@ | |||
| </small> | |||
| </BCol> | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">تعداد در کارتن</label> | |||
| <input | |||
| type="number" | |||
| v-model="countInCarton" | |||
| class="form-control" | |||
| placeholder="تعداد" | |||
| :class="{ 'is-invalid': errors.countInCarton }" | |||
| @input="clearError('countInCarton')" | |||
| /> | |||
| </div> | |||
| <small v-if="errors.countInCarton" class="text-danger"> | |||
| {{ errors.countInCarton }} | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="productType == 1 || productType == 3" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">قیمت (تک) </label> | |||
| @@ -247,7 +261,7 @@ | |||
| </label> | |||
| <DatePicker | |||
| format="YYYY/MM/DD HH:mm:ss" | |||
| :format="'jYYYY/jMM/jDD HH:mm:ss'" | |||
| type="datetime" | |||
| v-model="expire" | |||
| @input="handleInput" | |||
| @@ -266,10 +280,8 @@ | |||
| v-model="selectedCat" | |||
| class="form-control" | |||
| @change="clearError('selectedCat')" | |||
| placeholder="انتخاب دسته محصول" | |||
| > | |||
| <option value="" disabled selected> | |||
| انتخاب دسته محصول | |||
| </option> | |||
| <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | |||
| {{ cat.title }} | |||
| </option> | |||
| @@ -288,10 +300,8 @@ | |||
| v-model="selectedBrand" | |||
| class="form-control" | |||
| @change="clearError('selectedBrand')" | |||
| placeholder="انتخاب برند محصول" | |||
| > | |||
| <option value="" disabled selected> | |||
| انتخاب برند محصول | |||
| </option> | |||
| <option | |||
| v-for="brand in brands" | |||
| :key="brand.id" | |||
| @@ -311,7 +321,22 @@ | |||
| <h5 class="mb-0">اضافه کردن ویژگی ها</h5> | |||
| </div> | |||
| <BRow class="g-3 mt-2"> | |||
| <template v-if="attrebutes.length === 0"> | |||
| <BCol> | |||
| <div class="alert alert-info text-center"> | |||
| هیچ ویژگی وجود ندارد برای اضافه کردن ویژگی کلیک کنید | |||
| <BButton | |||
| variant="success" | |||
| size="lg" | |||
| class="mt-1 px-4 py-2 shadow-lg rounded-pill" | |||
| data-bs-toggle="modal" | |||
| data-bs-target="#addAttribute" | |||
| > | |||
| <i class="fas fa-plus me-2"></i> کلیک کنید | |||
| </BButton> | |||
| </div> | |||
| </BCol> | |||
| </template> | |||
| <BCol | |||
| v-for="attrebute in attrebutes" | |||
| :key="attrebute.id" | |||
| @@ -323,7 +348,6 @@ | |||
| <label | |||
| class="form-label d-flex align-items-center mb-3" | |||
| > | |||
| <BCol md="2" class="d-flex justify-content-center"> | |||
| <BFormCheckbox | |||
| :id="'checkbox-id-' + attrebute.id" | |||
| @@ -332,7 +356,6 @@ | |||
| /> | |||
| </BCol> | |||
| <BCol | |||
| md="5" | |||
| class="d-flex align-items-center" | |||
| @@ -341,7 +364,6 @@ | |||
| {{ attrebute.title }} | |||
| </BCol> | |||
| <BCol md="5"> | |||
| <input | |||
| type="number" | |||
| @@ -370,7 +392,82 @@ | |||
| </BCard> | |||
| <BCard> | |||
| <div class="card-header"> | |||
| <h5 class="mb-0">اضافه کردن مشخصه ها</h5> | |||
| </div> | |||
| <BRow class="g-3 mt-2"> | |||
| <template v-if="relatedAttrebutes.length === 0"> | |||
| <BCol> | |||
| <div class="alert alert-info text-center"> | |||
| دسته ای انتخاب نکرده اید یا دسته انتخابی شما مشخصه ای در | |||
| خود ندارد برای اضافه کردن مشخصه کلیک کنید | |||
| <BButton | |||
| variant="success" | |||
| size="lg" | |||
| class="mt-1 px-4 py-2 shadow-lg rounded-pill" | |||
| data-bs-toggle="modal" | |||
| data-bs-target="#addIdentity" | |||
| > | |||
| <i class="fas fa-plus me-2"></i> کلیک کنید | |||
| </BButton> | |||
| </div> | |||
| </BCol> | |||
| </template> | |||
| <template v-else> | |||
| <BCol | |||
| v-for="identity in relatedAttrebutes" | |||
| :key="identity.id" | |||
| md="6" | |||
| lg="4" | |||
| > | |||
| <div class="card shadow-sm"> | |||
| <div class="card-body"> | |||
| <label | |||
| class="form-label d-flex align-items-center mb-3" | |||
| > | |||
| <BCol md="2" class="d-flex justify-content-center"> | |||
| <BFormCheckbox | |||
| :id="'checkbox-id-' + identity.id" | |||
| class="mr-2 test" | |||
| v-model="identity.isChecked" | |||
| /> | |||
| </BCol> | |||
| <BCol md="5" class="d-flex align-items-center"> | |||
| {{ identity.title }} | |||
| </BCol> | |||
| <BCol md="5"> | |||
| <input | |||
| type="text" | |||
| class="form-control" | |||
| placeholder="مقدار" | |||
| :disabled="!identity.isChecked" | |||
| v-model="identity.value" | |||
| @input="clearError(`identity${identity.id}`)" | |||
| /> | |||
| </BCol> | |||
| </label> | |||
| <small | |||
| v-if="errors[`identityVal_${identity.id}`]" | |||
| class="text-danger" | |||
| > | |||
| {{ errors[`identityVal_${identity.id}`] }} | |||
| </small> | |||
| </div> | |||
| </div> | |||
| </BCol> | |||
| <small v-if="errors.selectedIdentities" class="text-danger"> | |||
| {{ errors.selectedIdentities }} | |||
| </small> | |||
| </template> | |||
| </BRow> | |||
| </BCard> | |||
| <BCard> | |||
| <div | |||
| class="card-header text-center p-4" | |||
| style="background-color: #f7f7f7" | |||
| @@ -386,12 +483,10 @@ | |||
| md="6" | |||
| > | |||
| <div class="form-group position-relative"> | |||
| <label class="form-label mb-2 fw-bold text-secondary" | |||
| >تصویر محصول</label | |||
| > | |||
| <div class="custom-file"> | |||
| <input | |||
| type="file" | |||
| @@ -402,7 +497,6 @@ | |||
| /> | |||
| </div> | |||
| <div v-if="image.preview" class="mt-3 position-relative"> | |||
| <img | |||
| :src="image.preview" | |||
| @@ -410,7 +504,6 @@ | |||
| class="img-fluid rounded-3 shadow-lg border Image-Preview" | |||
| /> | |||
| <button | |||
| type="button" | |||
| @click="removeImage()" | |||
| @@ -426,7 +519,6 @@ | |||
| </small> | |||
| </BRow> | |||
| <div class="text-center mt-4"> | |||
| <button | |||
| @click="addImage" | |||
| @@ -459,6 +551,11 @@ | |||
| </div> | |||
| </div> | |||
| </BCardFooter> | |||
| <addIdentity :cats="cats" /> | |||
| <addAttribute | |||
| :attributeValues="attributeValues" | |||
| @attribute-updated="handleAttributeUpdated()" | |||
| /> | |||
| </BCard> | |||
| </BCol> | |||
| </BRow> | |||
| @@ -466,22 +563,29 @@ | |||
| </template> | |||
| <script> | |||
| import addAttribute from "@/components/modals/attribute/addAttribute.vue"; | |||
| import moment from "moment"; | |||
| import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import { ref, onMounted } from "vue"; | |||
| import { ref, onMounted, watch } 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: { | |||
| Layout, | |||
| DatePicker, | |||
| addIdentity, | |||
| addAttribute, | |||
| }, | |||
| setup() { | |||
| const attributeValues = ref(); | |||
| const relatedAttrebutes = ref([]); | |||
| const countInCarton = ref(); | |||
| const selectedAttributes = ref(); | |||
| const selectedIdentities = ref(); | |||
| const expire = ref(); | |||
| const chosenPrice = ref(); | |||
| const spescialPrice = ref(); | |||
| @@ -491,7 +595,7 @@ export default { | |||
| const productAttributes = ref([]); | |||
| const images = ref([{ file: null, preview: null }]); | |||
| const brands = ref(); | |||
| const attrebutes = ref(); | |||
| const attrebutes = ref([]); | |||
| const selectedBrand = ref(); | |||
| const selectedCat = ref(); | |||
| const date = ref(); | |||
| @@ -521,13 +625,38 @@ export default { | |||
| }); | |||
| }; | |||
| const handleInput = () => { | |||
| if (expire.value) { | |||
| expire.value = moment(expire.value).format("YYYY-MM-DD HH:mm:ss"); | |||
| } | |||
| clearError("expire"); | |||
| const getAttributeValues = () => { | |||
| ApiServiece.get(`admin/attributes`).then((resp) => { | |||
| console.log(resp); | |||
| attributeValues.value = resp.data.data; | |||
| }); | |||
| }; | |||
| const handleAttributeUpdated = () => { | |||
| getAttrebuteValues(); | |||
| }; | |||
| watch(selectedCat, () => { | |||
| ApiServiece.get(`admin/attributes?category_id=${selectedCat.value}`) | |||
| .then((resp) => { | |||
| relatedAttrebutes.value = resp.data.data; | |||
| console.log(relatedAttrebutes.value); | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| }); | |||
| }); | |||
| 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"); | |||
| } else { | |||
| expire.value = null; | |||
| clearError("expire"); | |||
| } | |||
| }; | |||
| const getBrands = () => { | |||
| ApiServiece.get(`admin/brands`) | |||
| .then((resp) => { | |||
| @@ -539,7 +668,7 @@ export default { | |||
| }; | |||
| const getAttrebuteValues = () => { | |||
| ApiServiece.get(`admin/attribute-values`) | |||
| ApiServiece.get(`admin/attribute-values?attribute_id=1`) | |||
| .then((resp) => { | |||
| attrebutes.value = resp.data.data; | |||
| console.log(attrebutes.value); | |||
| @@ -604,7 +733,7 @@ export default { | |||
| errors.value = {}; | |||
| if (!title.value) errors.value.title = "وارد کردن عنوان محصول الزامی است"; | |||
| if (!slug.value) | |||
| errors.value.slug = "وارد کردن اسلاگ محصول ضروری می باشد"; | |||
| errors.value.slug = "وارد کردن کلمه کلیدی محصول ضروری می باشد"; | |||
| if (!summary.value) | |||
| errors.value.summary = "وارد کردن خلاصه محصول ضروری می باشد"; | |||
| if (!selectedCat.value) | |||
| @@ -633,6 +762,10 @@ export default { | |||
| if (!selectedBrand.value) | |||
| errors.value.selectedBrand = "انتخاب برند برای محصول ضروری می باشد"; | |||
| if (!countInCarton.value) | |||
| errors.value.countInCarton = | |||
| "انتخاب تعداد محصول در هر کارتن ضروری می باشد"; | |||
| if (images.value.length <= 0) | |||
| errors.value.images = "انتخاب عکس برای محصول ضروری می باشد"; | |||
| @@ -650,6 +783,20 @@ export default { | |||
| }); | |||
| } | |||
| const missingIdentityVal = relatedAttrebutes.value.filter( | |||
| (identity) => | |||
| identity.isChecked && | |||
| (identity.value == null || identity.value === "") | |||
| ); | |||
| if (missingIdentityVal.length > 0) { | |||
| missingIdentityVal.forEach((identity) => { | |||
| errors.value[ | |||
| `identityVal_${identity.id}` | |||
| ] = `وارد کردن مقدار مشخصه الزامی می باشد`; | |||
| }); | |||
| } | |||
| return Object.keys(errors.value).length === 0; | |||
| }; | |||
| @@ -661,10 +808,10 @@ export default { | |||
| getCats(); | |||
| getBrands(); | |||
| getAttrebuteValues(); | |||
| getAttributeValues(); | |||
| }); | |||
| let id = ""; | |||
| const submitForm = () => { | |||
| console.log(expire.value); | |||
| if (!validateForm()) return; | |||
| loading.value = true; | |||
| @@ -713,6 +860,7 @@ export default { | |||
| formData.append("brand_id", selectedBrand.value); | |||
| formData.append("category_id", selectedCat.value); | |||
| formData.append("count_in_carton", countInCarton.value); | |||
| formData.append("image", image.value); | |||
| ApiServiece.post(`admin/products`, formData, { | |||
| @@ -740,8 +888,28 @@ export default { | |||
| const jsonString = JSON.stringify(finalPayload, null, 2); | |||
| console.log(jsonString); | |||
| ApiServiece.post(`admin/products/${id}/attributes`, jsonString).then( | |||
| (resp) => { | |||
| 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); | |||
| @@ -752,25 +920,25 @@ export default { | |||
| "content-type": "multipart", | |||
| Authorization: `Bearer ${localStorage.getItem("token")}`, | |||
| }, | |||
| }) | |||
| }); | |||
| }); | |||
| } | |||
| ); | |||
| }); | |||
| }) | |||
| .then(() => { | |||
| loading.value = false; | |||
| toast.success("!محصول با موفقیت اضافه شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .catch((error) => { | |||
| console.error(error); | |||
| toast.error("!مشکلی در ایجاد محصوا بوجود آمد", { | |||
| toast.error(`${error?.response?.data?.message}`, { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .finally(() => { | |||
| loading.value = false; | |||
| toast.success("!محصول با موفقیت اضافه شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }); | |||
| }; | |||
| @@ -811,6 +979,11 @@ export default { | |||
| chosenPrice, | |||
| expire, | |||
| handleInput, | |||
| countInCarton, | |||
| relatedAttrebutes, | |||
| selectedIdentities, | |||
| attributeValues, | |||
| handleAttributeUpdated, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -29,12 +29,12 @@ | |||
| <!-- Second Input Field (Slug) --> | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">اسلاگ</label> | |||
| <label class="form-label">کلمه کلیدی</label> | |||
| <input | |||
| type="text" | |||
| v-model="slug" | |||
| class="form-control" | |||
| placeholder="اسلاگ محصول" | |||
| placeholder="کلمه کلیدی محصول" | |||
| :class="{ 'is-invalid': errors.slug }" | |||
| @input="clearError('slug')" | |||
| /> | |||
| @@ -95,7 +95,6 @@ | |||
| alt="Image Preview" | |||
| class="img-fluid rounded shadow-sm Image-Preview" | |||
| /> | |||
| </div> | |||
| <small v-if="errors.image" class="text-danger"> | |||
| @@ -112,10 +111,8 @@ | |||
| v-model="productType" | |||
| class="form-control" | |||
| @select="clearError('productType')" | |||
| placeholder="انتخاب حالت محصول" | |||
| > | |||
| <option value="" disabled selected> | |||
| انتخاب حالت محصول | |||
| </option> | |||
| <option value="1">تک</option> | |||
| <option value="2">عمده</option> | |||
| <option value="3">هردو</option> | |||
| @@ -143,6 +140,23 @@ | |||
| </small> | |||
| </BCol> | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">تعداد در کارتن</label> | |||
| <input | |||
| type="number" | |||
| v-model="countInCarton" | |||
| class="form-control" | |||
| placeholder="تعداد" | |||
| :class="{ 'is-invalid': errors.countInCarton }" | |||
| @input="clearError('countInCarton')" | |||
| /> | |||
| </div> | |||
| <small v-if="errors.countInCarton" class="text-danger"> | |||
| {{ errors.countInCarton }} | |||
| </small> | |||
| </BCol> | |||
| <BCol v-if="productType == 1 || productType == 3" md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">قیمت (تک) </label> | |||
| @@ -168,10 +182,8 @@ | |||
| v-model="isChosen" | |||
| class="form-control" | |||
| @select="clearError('isChosen')" | |||
| placeholder="انتخاب حالت منتخب" | |||
| > | |||
| <option value="" disabled selected> | |||
| انتخاب حالت منتخب | |||
| </option> | |||
| <option value="1">هست</option> | |||
| <option value="0">نیست</option> | |||
| </select> | |||
| @@ -206,8 +218,8 @@ | |||
| v-model="spescial" | |||
| class="form-control" | |||
| @select="clearError('spescial')" | |||
| placeholder="انتخاب حالت ویژه" | |||
| > | |||
| <option value="" disabled selected>انتخاب حالت ویژه</option> | |||
| <option value="1">هست</option> | |||
| <option value="0">نیست</option> | |||
| </select> | |||
| @@ -241,10 +253,9 @@ | |||
| </label> | |||
| <DatePicker | |||
| format="YYYY/MM/DD HH:mm:ss" | |||
| :format="'jYYYY/jMM/jDD HH:mm:ss'" | |||
| type="datetime" | |||
| v-model="expire" | |||
| @input="handleInput" | |||
| ></DatePicker> | |||
| </div> | |||
| <small v-if="errors.expire" class="text-danger"> | |||
| @@ -260,10 +271,8 @@ | |||
| v-model="selectedCat" | |||
| class="form-control" | |||
| @input="clearError('selectedCat')" | |||
| placeholder="انتخاب دسته محصول" | |||
| > | |||
| <option value="" disabled selected> | |||
| انتخاب دسته محصول | |||
| </option> | |||
| <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | |||
| {{ cat.title }} | |||
| </option> | |||
| @@ -282,10 +291,8 @@ | |||
| v-model="selectedBrand" | |||
| class="form-control" | |||
| @select="clearError('selectedBrand')" | |||
| placeholder="انتخاب برند محصول" | |||
| > | |||
| <option value="" disabled selected> | |||
| انتخاب برند محصول | |||
| </option> | |||
| <option | |||
| v-for="brand in brands" | |||
| :key="brand.id" | |||
| @@ -304,6 +311,13 @@ | |||
| <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> | |||
| <BRow class="g-3 mt-2"> | |||
| <!-- Loop through attributes --> | |||
| <BCol | |||
| @@ -398,9 +412,149 @@ | |||
| <BCard> | |||
| <div class="card-header"> | |||
| <h5 class="mb-0">اضافه کردن ویژگی ها</h5> | |||
| <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 | |||
| v-for="identity in localIdentities" | |||
| :key="identity.id" | |||
| md="6" | |||
| lg="4" | |||
| > | |||
| <div class="card shadow-sm"> | |||
| <div class="card-body"> | |||
| <!-- Card Header with Delete Icon --> | |||
| <div | |||
| class="d-flex justify-content-between align-items-center" | |||
| > | |||
| <!-- Optional Title or Heading for the card --> | |||
| <div class="card-title"> | |||
| <!-- You can add a title or leave it empty if you don't need one --> | |||
| </div> | |||
| <!-- Delete Icon --> | |||
| <button | |||
| class="btn btn-link text-danger p-0" | |||
| @click="deleteIdentity(identity.id)" | |||
| title="حذف" | |||
| > | |||
| <i class="fas fa-trash-alt"></i> | |||
| </button> | |||
| </div> | |||
| <!-- Checkbox Column --> | |||
| <label | |||
| class="form-label d-flex align-items-center mb-3" | |||
| > | |||
| <BCol md="2" class="d-flex justify-content-center"> | |||
| <BFormCheckbox | |||
| :id="'checkbox-id-' + identity.id" | |||
| class="mr-2 test" | |||
| v-model="identity.isChecked" | |||
| /> | |||
| </BCol> | |||
| <!-- Title Column with Dynamic Color --> | |||
| <BCol md="5" class="d-flex align-items-center"> | |||
| {{ identity.title }} | |||
| </BCol> | |||
| <!-- Number Input Column --> | |||
| <BCol md="5"> | |||
| <input | |||
| type="text" | |||
| class="form-control" | |||
| placeholder="تعداد" | |||
| :disabled="!identity.isChecked" | |||
| v-model="identity.value" | |||
| /> | |||
| </BCol> | |||
| </label> | |||
| <!-- Error Message --> | |||
| <!-- Edit Button --> | |||
| <div class="mt-3 text-center"> | |||
| <button | |||
| class="btn btn-primary" | |||
| @click=" | |||
| editIdentity( | |||
| identity.id, | |||
| identity.value, | |||
| identity.attribute_value_id | |||
| ) | |||
| " | |||
| > | |||
| ویرایش | |||
| </button> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </BCol> | |||
| <small v-if="errors.selectedAttributes" class="text-danger"> | |||
| {{ errors.selectedAttributes }} | |||
| </small> | |||
| </BRow> | |||
| </BCard> | |||
| <BCard> | |||
| <div class="card-header"> | |||
| <h5 class="mb-0">اضافه کردن ویژگی جدید</h5> | |||
| </div> | |||
| <BRow class="g-3 mt-2"> | |||
| <template v-if="attrebutes.length === 0 && !repeatedIdentity"> | |||
| <BCol> | |||
| <div class="alert alert-info text-center"> | |||
| برای شما ویژگی ثبت نشده است. برای ساختن یک ویژگی | |||
| <BButton | |||
| variant="success" | |||
| size="lg" | |||
| class="mt-2 px-4 py-2 shadow-lg rounded-pill" | |||
| data-bs-toggle="modal" | |||
| data-bs-target="#addIdentity" | |||
| > | |||
| <i class="fas fa-plus me-2"></i> کلیک کنید | |||
| </BButton> | |||
| </div> | |||
| </BCol> | |||
| </template> | |||
| <template v-if="attrebutes.length === 0 && repeatedAttrebute"> | |||
| <BCol> | |||
| <div | |||
| class="alert alert-info text-center p-4 shadow-lg rounded-lg" | |||
| > | |||
| <h5 class="mb-3"> | |||
| هیچ ویژگی جدیدی برای شما ثبت نشده است | |||
| </h5> | |||
| <p class="mb-3"> | |||
| شما میتوانید ویژگی های قبلی را ویرایش کنید. | |||
| <br /> | |||
| برای اضافه کردن ویژگی جدید ، لطفاً دکمه زیر را کلیک | |||
| کنید . | |||
| </p> | |||
| <BButton | |||
| variant="success" | |||
| size="lg" | |||
| class="mt-3 px-4 py-2 shadow-lg rounded-pill" | |||
| data-bs-toggle="modal" | |||
| data-bs-target="#addAttribute" | |||
| > | |||
| <i class="fas fa-plus me-2"></i> اضافه کردن ویژگی جدید | |||
| </BButton> | |||
| </div> | |||
| </BCol> | |||
| </template> | |||
| <!-- Loop through attributes --> | |||
| <BCol | |||
| v-for="attrebute in attrebutes" | |||
| @@ -459,6 +613,105 @@ | |||
| </BRow> | |||
| </BCard> | |||
| <BCard> | |||
| <div class="card-header"> | |||
| <h5 class="mb-0">اضافه کردن مشخصه جدید</h5> | |||
| </div> | |||
| <template v-if="identities.length === 0 && !repeatedIdentity"> | |||
| <BCol> | |||
| <div class="alert alert-info text-center"> | |||
| برای شما مشخصهای ثبت نشده است. برای ساختن یک مشخصه | |||
| <BButton | |||
| variant="success" | |||
| size="lg" | |||
| class="mt-2 px-4 py-2 shadow-lg rounded-pill" | |||
| data-bs-toggle="modal" | |||
| data-bs-target="#addIdentity" | |||
| > | |||
| <i class="fas fa-plus me-2"></i> کلیک کنید | |||
| </BButton> | |||
| </div> | |||
| </BCol> | |||
| </template> | |||
| <template v-if="identities.length === 0 && repeatedIdentity"> | |||
| <BCol> | |||
| <div | |||
| class="alert alert-info text-center p-4 shadow-lg rounded-lg" | |||
| > | |||
| <h5 class="mb-3"> | |||
| هیچ مشخصه جدیدی برای این دستهبندی ثبت نشده است | |||
| </h5> | |||
| <p class="mb-3"> | |||
| شما میتوانید مشخصههای قبلی را ویرایش کنید. | |||
| <br /> | |||
| برای اضافه کردن مشخصه جدید به این دسته، لطفاً دکمه زیر | |||
| را کلیک کنید . | |||
| </p> | |||
| <BButton | |||
| variant="success" | |||
| size="lg" | |||
| class="mt-3 px-4 py-2 shadow-lg rounded-pill" | |||
| data-bs-toggle="modal" | |||
| data-bs-target="#addIdentity" | |||
| > | |||
| <i class="fas fa-plus me-2"></i> اضافه کردن مشخصه جدید | |||
| </BButton> | |||
| </div> | |||
| </BCol> | |||
| </template> | |||
| <BRow class="g-3 mt-2"> | |||
| <!-- Loop through attributes --> | |||
| <BCol | |||
| v-for="identity in identities" | |||
| :key="identity.id" | |||
| md="6" | |||
| lg="4" | |||
| > | |||
| <div class="card shadow-sm"> | |||
| <div class="card-body"> | |||
| <label | |||
| class="form-label d-flex align-items-center mb-3" | |||
| > | |||
| <!-- Checkbox Column --> | |||
| <BCol md="2" class="d-flex justify-content-center"> | |||
| <BFormCheckbox | |||
| :id="'checkbox-id-' + identity.id" | |||
| class="mr-2 test" | |||
| v-model="identity.isChecked" | |||
| /> | |||
| </BCol> | |||
| <!-- Title Column with Dynamic Color --> | |||
| <BCol md="5" class="d-flex align-items-center"> | |||
| {{ identity.title }} | |||
| </BCol> | |||
| <!-- Number Input Column --> | |||
| <BCol md="5"> | |||
| <input | |||
| type="text" | |||
| class="form-control" | |||
| placeholder="مقدار" | |||
| :disabled="!identity.isChecked" | |||
| v-model="identity.value" | |||
| @input="clearError(`identityVal_${identity.id}`)" | |||
| /> | |||
| </BCol> | |||
| </label> | |||
| <small | |||
| v-if="errors[`identityVal_${identity.id}`]" | |||
| class="text-danger" | |||
| > | |||
| {{ errors[`identityVal_${identity.id}`] }} | |||
| </small> | |||
| </div> | |||
| </div> | |||
| </BCol> | |||
| </BRow> | |||
| </BCard> | |||
| <BCard> | |||
| <!-- Card Header --> | |||
| <div | |||
| @@ -548,6 +801,11 @@ | |||
| </button> | |||
| </div> | |||
| </div> | |||
| <addIdentity | |||
| :cats="cats" | |||
| @attribute-updated="handleAttributeUpdated()" | |||
| /> | |||
| <addAttribute @attribute-updated="handleAttributeUpdated()" /> | |||
| </BCardFooter> | |||
| </BCard> | |||
| </BCol> | |||
| @@ -562,8 +820,10 @@ import { toast } from "vue3-toastify"; | |||
| import "vue3-toastify/dist/index.css"; | |||
| import moment from "moment"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| import { ref, onMounted } from "vue"; | |||
| import { ref, onMounted, watch } from "vue"; | |||
| import Layout from "@/layout/custom.vue"; | |||
| import addIdentity from "@/components/modals/identity/addIdentity.vue"; | |||
| import addAttribute from "@/components/modals/attribute/addAttribute.vue"; | |||
| import DatePicker from "vue3-persian-datetime-picker"; | |||
| export default { | |||
| @@ -571,15 +831,10 @@ export default { | |||
| components: { | |||
| Layout, | |||
| DatePicker, | |||
| addIdentity, | |||
| addAttribute, | |||
| }, | |||
| setup() { | |||
| const selectedAttrebutes = ref([ | |||
| { | |||
| inventory: null, | |||
| type: null, | |||
| attribute_value_id: null, | |||
| }, | |||
| ]); | |||
| const locals = ref([ | |||
| { | |||
| id: null, | |||
| @@ -589,13 +844,18 @@ export default { | |||
| isChecked: null, | |||
| }, | |||
| ]); | |||
| const localIdentities = ref([]); | |||
| const countInCarton = ref(); | |||
| const files = ref([]); | |||
| const productValueId = ref(); | |||
| const localsIds = ref(); | |||
| const identities = ref([]); | |||
| const localIdentitiesIds = ref([]); | |||
| const selectedAttributes = ref(); | |||
| const selectedidentities = ref([]); | |||
| const product = ref(); | |||
| const route = useRoute(); | |||
| const imagesTosend = ref() | |||
| const imagesTosend = ref(); | |||
| const expire = ref(); | |||
| const chosenPrice = ref(); | |||
| const spescialPrice = ref(); | |||
| @@ -606,7 +866,7 @@ export default { | |||
| const images = ref([{ file: null, preview: null }]); | |||
| const localImages = ref([{ preview: null }]); | |||
| const brands = ref(); | |||
| const attrebutes = ref(); | |||
| const attrebutes = ref([]); | |||
| const selectedBrand = ref(); | |||
| const selectedCat = ref(); | |||
| const date = ref(); | |||
| @@ -616,7 +876,8 @@ export default { | |||
| const loading = ref(false); | |||
| const image = ref(); | |||
| const imagePreview = ref(); | |||
| const repeatedIdentity = ref(false); | |||
| const repeatedAttrebute = ref(false); | |||
| const errors = ref({}); | |||
| const title = ref(""); | |||
| const slug = ref(""); | |||
| @@ -637,13 +898,59 @@ export default { | |||
| }); | |||
| }; | |||
| const handleInput = () => { | |||
| if (expire.value) { | |||
| expire.value = moment(expire.value).format("YYYY-MM-DD HH:mm:ss"); | |||
| } | |||
| clearError("expire"); | |||
| watch(selectedCat, () => { | |||
| ApiServiece.get(`admin/attributes?category_id=${selectedCat.value}`) | |||
| .then((resp) => { | |||
| identities.value = resp.data.data; | |||
| console.log(identities.value); | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| }); | |||
| }); | |||
| const getIdentities = () => { | |||
| ApiServiece.get(`admin/attributes?category_id=${selectedCat.value}`) | |||
| .then((resp) => { | |||
| identities.value = resp.data.data; | |||
| console.log(identities.value); | |||
| }) | |||
| .then(() => { | |||
| localIdentitiesIds.value = localIdentities.value.map( | |||
| (identity) => identity.title | |||
| ); | |||
| let hasRepeatedIdentity = false; | |||
| identities.value = identities.value.filter((identity) => { | |||
| if (!localIdentitiesIds.value.includes(identity.title)) { | |||
| return true; | |||
| } else { | |||
| hasRepeatedIdentity = true; | |||
| return false; | |||
| } | |||
| }); | |||
| repeatedIdentity.value = hasRepeatedIdentity; | |||
| console.log(repeatedIdentity.value); | |||
| }); | |||
| }; | |||
| 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) => { | |||
| @@ -655,12 +962,11 @@ export default { | |||
| }; | |||
| const getAttrebuteValues = () => { | |||
| ApiServiece.get(`admin/attribute-values`) | |||
| ApiServiece.get(`admin/attribute-values?attribute_id=1`) | |||
| .then((resp) => { | |||
| console.log(resp); | |||
| attrebutes.value = resp.data.data; | |||
| console.log("Attributes before filtering:", attrebutes.value); | |||
| console.log("Filtered attributes:", attrebutes.value); | |||
| }) | |||
| .then(() => { | |||
| getProduct(); | |||
| @@ -675,7 +981,7 @@ export default { | |||
| const deleteAttribute = (id) => { | |||
| Swal.fire({ | |||
| title: `آیا می خواهید این ویژگی را حذف کنید ؟`, | |||
| text: `آیا می خواهید این ویژگی را حذف کنید؟`, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| @@ -705,9 +1011,43 @@ export default { | |||
| }); | |||
| }; | |||
| const deleteIdentity = (id) => { | |||
| Swal.fire({ | |||
| text: `آیا می خواهید این مشخصه را حذف کنید؟`, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| cancelButtonColor: "#d33", | |||
| confirmButtonText: "بله!", | |||
| cancelButtonText: "خیر", | |||
| }).then((result) => { | |||
| if (result.isConfirmed) { | |||
| ApiServiece.delete( | |||
| `admin/products/${route.params.id}/attributes/${id}` | |||
| ) | |||
| .then(() => { | |||
| toast.success("!مشخصه با موفقیت حذف شد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| localIdentities.value = localIdentities.value.filter( | |||
| (identity) => identity.id !== id | |||
| ); | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| toast.error("!مشکلی در حذف کردن مشخصه پیش آمد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| }); | |||
| } | |||
| }); | |||
| }; | |||
| const deletImage = (name) => { | |||
| Swal.fire({ | |||
| title: `آیا می خواهید این عکس را حذف کنید ؟`, | |||
| text: `آیا می خواهید این عکس را حذف کنید؟`, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| @@ -811,7 +1151,7 @@ export default { | |||
| errors.value = {}; | |||
| if (!title.value) errors.value.title = "وارد کردن عنوان محصول الزامی است"; | |||
| if (!slug.value) | |||
| errors.value.slug = "وارد کردن اسلاگ محصول ضروری می باشد"; | |||
| errors.value.slug = "وارد کردن کلمه کلیدی محصول ضروری می باشد"; | |||
| if (!summary.value) | |||
| errors.value.summary = "وارد کردن خلاصه محصول ضروری می باشد"; | |||
| if (!selectedCat.value) | |||
| @@ -840,6 +1180,10 @@ export default { | |||
| if (!selectedBrand.value) | |||
| errors.value.selectedBrand = "انتخاب برند برای محصول ضروری می باشد"; | |||
| if (!countInCarton.value) | |||
| errors.value.countInCarton = | |||
| "انتخاب تعداد محصول در هر کارتن ضروری می باشد"; | |||
| if (images.value.length <= 0) | |||
| errors.value.images = "انتخاب عکس برای محصول ضروری می باشد"; | |||
| @@ -857,6 +1201,20 @@ export default { | |||
| }); | |||
| } | |||
| const missingIdentityVal = identities.value.filter( | |||
| (identity) => | |||
| identity.isChecked && | |||
| (identity.value == null || identity.value === "") | |||
| ); | |||
| if (missingIdentityVal.length > 0) { | |||
| missingIdentityVal.forEach((identity) => { | |||
| errors.value[ | |||
| `identityVal_${identity.id}` | |||
| ] = `وارد کردن مقدار مشخصه الزامی می باشد`; | |||
| }); | |||
| } | |||
| return Object.keys(errors.value).length === 0; | |||
| }; | |||
| @@ -865,57 +1223,88 @@ export default { | |||
| }; | |||
| const getProduct = () => { | |||
| ApiServiece.get(`admin/products/${route.params.id}`).then((resp) => { | |||
| productValueId.value = resp.data.data; | |||
| console.log(resp.data.data.product_attributes); | |||
| product.value = resp.data.data; | |||
| title.value = product.value?.title; | |||
| slug.value = product.value?.slug; | |||
| summary.value = product.value?.summary; | |||
| description.value = product.value?.description; | |||
| imagePreview.value = product.value?.image; | |||
| productType.value = product.value?.type; | |||
| wholesalePrice.value = product.value?.wholesale_price; | |||
| retailePrice.value = product.value?.retail_price; | |||
| isChosen.value = product.value?.is_chosen; | |||
| 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; | |||
| selectedBrand.value = product.value?.brand_id; | |||
| selectedCat.value = product.value?.category_id; | |||
| // Update images | |||
| images.value = product.value.images.map((imageUrl) => ({ | |||
| preview: imageUrl, | |||
| })); | |||
| localImages.value = product.value.images.map((imageUrl) => ({ | |||
| preview: imageUrl, | |||
| })); | |||
| locals.value = product.value.product_attributes.map( | |||
| (productAttribute) => { | |||
| return { | |||
| id: productAttribute.id, | |||
| title: productAttribute.attribute_value.title, | |||
| code: productAttribute.attribute_value.code, | |||
| inventory: productAttribute.inventory, | |||
| isChecked: productAttribute.inventory > 0, | |||
| value: productAttribute.inventory, | |||
| }; | |||
| } | |||
| ); | |||
| ApiServiece.get(`admin/products/${route.params.id}`) | |||
| .then((resp) => { | |||
| console.log(resp); | |||
| productValueId.value = resp.data.data; | |||
| console.log(resp.data.data.product_attributes); | |||
| product.value = resp.data.data; | |||
| title.value = product.value?.title; | |||
| slug.value = product.value?.slug; | |||
| summary.value = product.value?.summary; | |||
| description.value = product.value?.description; | |||
| imagePreview.value = product.value?.image; | |||
| productType.value = product.value?.type; | |||
| wholesalePrice.value = product.value?.wholesale_price; | |||
| retailePrice.value = product.value?.retail_price; | |||
| isChosen.value = product.value?.is_chosen; | |||
| 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; | |||
| selectedBrand.value = product.value?.brand_id; | |||
| selectedCat.value = product.value?.category_id; | |||
| countInCarton.value = product.value?.count_in_carton; | |||
| // Update images | |||
| images.value = product.value.images.map((imageUrl) => ({ | |||
| preview: imageUrl, | |||
| })); | |||
| localImages.value = product.value.images.map((imageUrl) => ({ | |||
| preview: imageUrl, | |||
| })); | |||
| locals.value = product.value.product_attributes.map( | |||
| (productAttribute) => { | |||
| return { | |||
| id: productAttribute.id, | |||
| title: productAttribute.attribute_value.title, | |||
| code: productAttribute.attribute_value.code, | |||
| inventory: productAttribute.inventory, | |||
| isChecked: productAttribute.inventory > 0, | |||
| value: productAttribute.inventory, | |||
| }; | |||
| } | |||
| ); | |||
| localsIds.value = locals.value.map((attribute) => attribute.code); | |||
| let hasRepeatedAttribute = false; | |||
| attrebutes.value = attrebutes.value.filter((attrebute) => { | |||
| if (!localsIds.value.includes(attrebute.code)) { | |||
| return true; | |||
| } else { | |||
| hasRepeatedAttribute = true; | |||
| return false; | |||
| } | |||
| }); | |||
| localsIds.value = locals.value.map((attribute) => attribute.code); | |||
| console.log(localsIds.value); | |||
| repeatedAttrebute.value = hasRepeatedAttribute; | |||
| console.log(attrebutes.value); | |||
| attrebutes.value = attrebutes.value.filter( | |||
| (attribute) => !localsIds.value.includes(attribute.code) | |||
| ); | |||
| }); | |||
| 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, | |||
| }; | |||
| } | |||
| ); | |||
| }) | |||
| .then(() => { | |||
| getIdentities(); | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| }); | |||
| }; | |||
| const editAttribute = (id, inventory) => { | |||
| @@ -951,6 +1340,41 @@ export default { | |||
| }); | |||
| }; | |||
| const editIdentity = (id, title, valueID) => { | |||
| Swal.fire({ | |||
| text: "آیا برای ویرایش مشخصه اطمینان دارید؟", | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonText: "بله، ویرایش کن", | |||
| cancelButtonText: "لغو", | |||
| reverseButtons: true, | |||
| }).then((result) => { | |||
| if (result.isConfirmed) { | |||
| const formData = new FormData(); | |||
| formData.append("attribute_id", id); | |||
| formData.append("title", title); | |||
| ApiServiece.put(`admin/attribute-values/:${valueID}`, formData) | |||
| .then(() => { | |||
| toast.success("!مشخصه با موفقیت ویرایش شد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| }) | |||
| .catch(() => { | |||
| toast.error("!مشکلی در ویرایش مشخصه ایجاد شد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| }); | |||
| } | |||
| }); | |||
| }; | |||
| const handleAttributeUpdated = () => { | |||
| getProduct(); | |||
| }; | |||
| onMounted(() => { | |||
| getCats(); | |||
| getBrands(); | |||
| @@ -959,6 +1383,7 @@ export default { | |||
| let id = ""; | |||
| const submitForm = () => { | |||
| console.log(errors.value); | |||
| if (!validateForm()) return; | |||
| loading.value = true; | |||
| @@ -969,6 +1394,7 @@ 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) { | |||
| formData.append("wholesale_price", wholesalePrice.value); | |||
| } | |||
| @@ -1021,6 +1447,7 @@ export default { | |||
| }, | |||
| }) | |||
| .then((resp) => { | |||
| loading.value = false; | |||
| id = resp.data.data.id; | |||
| console.log(id); | |||
| @@ -1045,19 +1472,38 @@ export default { | |||
| ); | |||
| } | |||
| selectedidentities.value = identities.value | |||
| .filter((identity) => identity.isChecked) | |||
| .map((identity) => ({ | |||
| attribute_id: identity.id, | |||
| attribute_value_title: identity.value, | |||
| })); | |||
| if (selectedidentities.value.length > 0) { | |||
| const finalPayload = { | |||
| productSolidAttributes: selectedidentities.value, | |||
| }; | |||
| const jsonString = JSON.stringify(finalPayload, null, 2); | |||
| ApiServiece.post( | |||
| `admin/products/${route.params.id}/solid-attributes`, | |||
| jsonString | |||
| ); | |||
| } | |||
| if (files.value.length > 0) { | |||
| console.log(files.value); | |||
| imagesTosend.value = images.value.filter((image) => { | |||
| imagesTosend.value = images.value.filter((image) => { | |||
| return !localImages.value.some( | |||
| (localImage) => localImage.preview === image.preview | |||
| ); | |||
| }); | |||
| imagesTosend.value.map((image) => { | |||
| // Collect all promises | |||
| const uploadPromises = imagesTosend.value.map((image) => { | |||
| const formData = new FormData(); | |||
| formData.append("image", image.file); | |||
| ApiServiece.post( | |||
| return ApiServiece.post( | |||
| `admin/products/${route.params.id}/images`, | |||
| formData, | |||
| { | |||
| @@ -1066,26 +1512,42 @@ export default { | |||
| Authorization: `Bearer ${localStorage.getItem("token")}`, | |||
| }, | |||
| } | |||
| ).then((resp) => { | |||
| console.log(resp); | |||
| ); | |||
| }); | |||
| // Wait for all uploads to finish | |||
| Promise.all(uploadPromises) | |||
| .then((responses) => { | |||
| console.log("All images uploaded:", responses); | |||
| loading.value = false; | |||
| toast.success("!محصول با موفقیت ویرایش شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .catch((error) => { | |||
| console.error("Error uploading images:", error); | |||
| loading.value = false; | |||
| toast.error("خطایی در بارگذاری تصاویر رخ داد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }); | |||
| } else { | |||
| toast.success("!محصول با موفقیت ویرایش شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| } | |||
| }) | |||
| .catch((error) => { | |||
| loading.value = false; | |||
| console.error(error); | |||
| toast.error("!مشکلی در ویرایش محصول ایحاد شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }) | |||
| .finally(() => { | |||
| loading.value = false; | |||
| toast.success("!محصول با موفقیت ویرایش شد", { | |||
| position: "top-right", | |||
| autoClose: 1000, | |||
| }); | |||
| }); | |||
| }; | |||
| @@ -1117,7 +1579,6 @@ export default { | |||
| images, | |||
| addImage, | |||
| handleImageUpload, | |||
| selectedAttrebutes, | |||
| description, | |||
| onCheckboxChange, | |||
| retailePrice, | |||
| @@ -1125,12 +1586,21 @@ export default { | |||
| spescialPrice, | |||
| chosenPrice, | |||
| expire, | |||
| handleInput, | |||
| selectedAttributes, | |||
| selectedidentities, | |||
| locals, | |||
| editAttribute, | |||
| deleteAttribute, | |||
| deletImage, | |||
| countInCarton, | |||
| localIdentities, | |||
| deleteIdentity, | |||
| identities, | |||
| handleAttributeUpdated, | |||
| editIdentity, | |||
| repeatedIdentity, | |||
| repeatedAttrebute, | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -18,6 +18,9 @@ export default { | |||
| const totalPages = ref(1); | |||
| const paginate = ref(20); | |||
| const page = ref(1); | |||
| function formatWithCommas(number) { | |||
| return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); | |||
| } | |||
| const filterLoading = ref(false); | |||
| const searchQuery = ref(""); | |||
| @@ -73,7 +76,7 @@ export default { | |||
| const deleteProduct = (id, title) => { | |||
| Swal.fire({ | |||
| title: `می خواهید محصول ${title} را حذف کنید ؟`, | |||
| text: `می خواهید محصول ${title} را حذف کنید؟`, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| @@ -105,7 +108,7 @@ export default { | |||
| const restoreProduct = (id, title) => { | |||
| Swal.fire({ | |||
| title: `می خواهید محصول ${title} را بازیابی کنید ؟`, | |||
| text: `می خواهید محصول ${title} را بازیابی کنید؟`, | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| confirmButtonColor: "#3085d6", | |||
| @@ -126,7 +129,7 @@ export default { | |||
| }) | |||
| .catch((err) => { | |||
| console.log(err); | |||
| toast.error("!مشکلی در بایابی محصول پیش آمد", { | |||
| toast.error("!مشکلی در بازیابی محصول پیش آمد", { | |||
| position: "top-right", | |||
| autoClose: 3000, | |||
| }); | |||
| @@ -187,6 +190,7 @@ export default { | |||
| searchPage, | |||
| visiblePages, | |||
| restoreProduct, | |||
| formatWithCommas | |||
| }; | |||
| }, | |||
| }; | |||
| @@ -197,7 +201,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 bg-primary text-white" | |||
| class="card-header d-flex justify-content-between align-items-center p-3" | |||
| dir="rtl" | |||
| > | |||
| <div class="d-flex align-items-center"> | |||
| @@ -253,36 +257,31 @@ export default { | |||
| <td>{{ product.sold_count }}</td> | |||
| <td> | |||
| <span v-if="product.is_chosen == 0"> | |||
| نیست | |||
| <i | |||
| class="fas fa-times-circle status-icon unavailable" | |||
| ></i> | |||
| </span> | |||
| <span v-if="product.is_chosen == 1"> | |||
| انتخاب شده | |||
| <i | |||
| class="fas fa-check-circle status-icon available" | |||
| ></i> | |||
| </span> | |||
| </td> | |||
| <td v-if="product?.chosen_price"> | |||
| {{ product?.chosen_price }} | |||
| {{ formatWithCommas(product?.chosen_price) }} | |||
| </td> | |||
| <td v-if="!product.chosen_price"> | |||
| ندارد | |||
| <i | |||
| class="fas fa-times-circle status-icon unavailable" | |||
| ></i> | |||
| </td> | |||
| <td> | |||
| <span v-if="product.is_special == 0"> | |||
| نیست | |||
| <i | |||
| class="fas fa-times-circle status-icon unavailable" | |||
| ></i> | |||
| </span> | |||
| <span v-if="product.is_special == 1"> | |||
| انتخاب شده | |||
| <i | |||
| class="fas fa-check-circle status-icon available" | |||
| ></i> | |||
| @@ -290,10 +289,10 @@ export default { | |||
| </td> | |||
| <td v-if="product?.wholesale_price"> | |||
| {{ product.wholesale_price }} | |||
| {{ formatWithCommas(product.wholesale_price) }} | |||
| </td> | |||
| <td v-if="product?.retail_price"> | |||
| {{ product?.retail_price }} | |||
| {{ formatWithCommas(product?.retail_price) }} | |||
| </td> | |||
| <td v-if="!product.deleted_at"> | |||
| @@ -498,7 +497,6 @@ export default { | |||
| user-select: none; | |||
| } | |||
| .table { | |||
| background-color: #f9f9f9; | |||
| border-radius: 8px; | |||
| overflow: hidden; | |||
| font-size: 0.9rem; | |||
| @@ -513,19 +511,10 @@ export default { | |||
| vertical-align: middle; | |||
| } | |||
| .table-light { | |||
| background-color: #f8f9fa; | |||
| } | |||
| .table th { | |||
| font-weight: bold; | |||
| } | |||
| .table td { | |||
| border: 1px solid #e0e0e0; | |||
| color: #333; | |||
| } | |||
| .table-hover tbody tr:hover { | |||
| background-color: #e9ecef; | |||
| cursor: pointer; | |||
| @@ -577,19 +566,14 @@ table tbody tr:hover { | |||
| } | |||
| td, | |||
| th { | |||
| color: #5f6368; | |||
| } | |||
| table[dir="rtl"] td, | |||
| table[dir="rtl"] th { | |||
| text-align: center; | |||
| } | |||
| td:last-child { | |||
| display: flex; | |||
| justify-content: center; | |||
| gap: 10px; | |||
| align-items: center; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,146 @@ | |||
| <script> | |||
| import accountInfo from "@/components/modals/profile/accountInfo.vue"; | |||
| import addAddress from "@/components/modals/profile/addAddress.vue"; | |||
| import moment from "jalali-moment"; | |||
| import addressList from "@/components/modals/profile/addressList.vue"; | |||
| import Layout from "@/layout/custom.vue"; | |||
| import { computed, onMounted, ref } from "vue"; | |||
| import { useStore } from "vuex"; | |||
| import ApiServiece from "@/services/ApiService"; | |||
| export default { | |||
| name: "ACCOUNT-PROFILE", | |||
| components: { | |||
| Layout, | |||
| addAddress, | |||
| accountInfo, | |||
| addressList, | |||
| }, | |||
| setup() { | |||
| const addresses = ref([]); | |||
| const store = useStore(); | |||
| const user = computed(() => store.getters["user/getUser"]); | |||
| const convertToJalali = (date) => { | |||
| return moment(date, "YYYY-MM-DD HH:mm:ss") | |||
| .locale("fa") | |||
| .format("YYYY/MM/DD"); | |||
| }; | |||
| const getAddress = () => { | |||
| ApiServiece.get(`wholesale/my-addresses`).then((resp) => { | |||
| addresses.value = resp.data.data; | |||
| console.log(addresses.value); | |||
| }); | |||
| }; | |||
| onMounted(() => { | |||
| getAddress(); | |||
| }); | |||
| return { | |||
| user, | |||
| convertToJalali, | |||
| addresses, | |||
| }; | |||
| }, | |||
| }; | |||
| </script> | |||
| <template> | |||
| <Layout> | |||
| <BRow> | |||
| <BCol class="col-sm-12"> | |||
| <BCard v-if="addresses.length == 0 " no-body class="alert alert-warning p-0"> | |||
| <BCardBody> | |||
| <div class="d-flex align-items-center"> | |||
| <div class="flex-grow-1 me-3"> | |||
| <h4 class="alert-heading">!هشدار</h4> | |||
| <p class="mb-2"> | |||
| شما هنوز هیچ آدرسی را ثبت نکرده اید برای اضافه کردن آدرس لینک | |||
| زیر را کلیک کنید | |||
| </p> | |||
| <a href="#" class="alert-link"><u>اضافه کردن آدرس</u></a> | |||
| </div> | |||
| <div class="flex-shrink-0"> | |||
| <img | |||
| src="@/assets/images/application/img-accout-password-alert.png" | |||
| alt="img" | |||
| class="img-fluid wid-80" | |||
| /> | |||
| </div> | |||
| </div> | |||
| </BCardBody> | |||
| </BCard> | |||
| <BRow> | |||
| <BCol class="col-lg-5 col-xxl-3"> | |||
| <BCard no-body class="overflow-hidden"> | |||
| <BCardBody class="position-relative"> | |||
| <div class="text-center mt-3"> | |||
| <div class="chat-avtar d-inline-flex mx-auto"> | |||
| <img | |||
| class="rounded-circle img-fluid wid-90 img-thumbnail" | |||
| src="@/assets/images/user/avatar-1.jpg" | |||
| alt="User image" | |||
| /> | |||
| </div> | |||
| <h5 class="mb-2">{{ user.name }}</h5> | |||
| </div> | |||
| </BCardBody> | |||
| <div | |||
| class="nav flex-column nav-pills list-group list-group-flush account-pills mb-0" | |||
| id="user-set-tab" | |||
| role="tablist" | |||
| aria-orientation="vertical" | |||
| > | |||
| <a | |||
| class="nav-link list-group-item list-group-item-action active" | |||
| id="user-set-profile-tab" | |||
| data-bs-toggle="pill" | |||
| href="#user-set-profile" | |||
| role="tab" | |||
| aria-controls="user-set-profile" | |||
| aria-selected="true" | |||
| > | |||
| <span class="f-w-500" | |||
| ><i class="ph-duotone ph-user-circle m-r-10"></i>اطلاعات | |||
| حساب | |||
| </span> | |||
| </a> | |||
| <a | |||
| class="nav-link list-group-item list-group-item-action" | |||
| id="user-set-information-tab" | |||
| data-bs-toggle="pill" | |||
| href="#user-set-information" | |||
| role="tab" | |||
| aria-controls="user-set-information" | |||
| aria-selected="false" | |||
| > | |||
| <span class="f-w-500" | |||
| ><i class="ph-duotone ph-clipboard-text m-r-10"></i> اضافه | |||
| کردن آدرس</span | |||
| > | |||
| </a> | |||
| <a | |||
| class="nav-link list-group-item list-group-item-action" | |||
| id="user-list-address-tab" | |||
| data-bs-toggle="pill" | |||
| href="#addressList" | |||
| role="tab" | |||
| aria-controls="addressList" | |||
| aria-selected="false" | |||
| > | |||
| <span class="f-w-500" | |||
| ><i class="ph-duotone ph-map-pin m-r-10"></i> مشاهده آدرس ها | |||
| </span> | |||
| </a> | |||
| </div> | |||
| </BCard> | |||
| </BCol> | |||
| <BCol class="col-lg-7 col-xxl-9"> | |||
| <div class="tab-content" id="user-set-tabContent"> | |||
| <addAddress /> | |||
| <accountInfo :user="user" /> | |||
| <addressList :addresses="addresses" /> | |||
| </div> | |||
| </BCol> | |||
| </BRow> | |||
| </BCol> | |||
| </BRow> | |||
| </Layout> | |||
| </template> | |||
| @@ -29,12 +29,12 @@ | |||
| <!-- Second Input Field (Slug) --> | |||
| <BCol md="6"> | |||
| <div class="form-group"> | |||
| <label class="form-label">اسلاگ</label> | |||
| <label class="form-label">ککلمه کلیدی</label> | |||
| <input | |||
| type="text" | |||
| v-model="slug" | |||
| class="form-control" | |||
| placeholder="اسلاگ محصول" | |||
| placeholder="کلمه کلیدی محصول" | |||
| :class="{ 'is-invalid': errors.slug }" | |||
| @input="clearError('slug')" | |||
| /> | |||
| @@ -586,7 +586,7 @@ export default { | |||
| errors.value = {}; | |||
| if (!title.value) errors.value.title = "وارد کردن عنوان محصول الزامی است"; | |||
| if (!slug.value) | |||
| errors.value.slug = "وارد کردن اسلاگ محصول ضروری می باشد"; | |||
| errors.value.slug = "وارد کردن کلمه کلیدی محصول ضروری می باشد"; | |||
| if (!summary.value) | |||
| errors.value.summary = "وارد کردن خلاصه محصول ضروری می باشد"; | |||
| if (!blogCat.value) | |||
| @@ -114,7 +114,7 @@ export default { | |||
| const blockUser = (id) => { | |||
| Swal.fire({ | |||
| title: "آیا مطمئن هستید؟", | |||
| text: "آیا میخواهید این کاربر را مسدود کنید؟", | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| @@ -147,7 +147,7 @@ export default { | |||
| const unBlockUser = (id) => { | |||
| Swal.fire({ | |||
| title: "آیا مطمئن هستید؟", | |||
| text: "آیا میخواهید این کاربر را فعال نمایید؟", | |||
| icon: "warning", | |||
| showCancelButton: true, | |||
| @@ -242,15 +242,16 @@ export default { | |||
| <li><a class="dropdown-item" href="#">بلاک</a></li> | |||
| </ul> | |||
| </div> --> | |||
| </div> | |||
| <button | |||
| <button | |||
| data-bs-toggle="modal" | |||
| data-bs-target="#addUser" | |||
| class="btn btn-add-user" | |||
| class="btn btn-add-user me-3" | |||
| > | |||
| <i class="ti ti-plus"></i> افزودن کاربر | |||
| افزودن کاربر | |||
| </button> | |||
| </div> | |||
| </div> | |||
| <div class="table-responsive"> | |||
| @@ -262,7 +263,7 @@ export default { | |||
| <th>موبایل</th> | |||
| <th>نقش</th> | |||
| <th>تاریخ ایجاد</th> | |||
| <th>فعالیت</th> | |||
| <th>وضعیت</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||