| @@ -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", | "form-wizard-vue3": "^1.1.0", | ||||
| "fslightbox": "^3.4.1", | "fslightbox": "^3.4.1", | ||||
| "is": "^3.3.0", | "is": "^3.3.0", | ||||
| "jalaali-js": "^1.2.7", | |||||
| "jalali-moment": "^3.3.11", | "jalali-moment": "^3.3.11", | ||||
| "jquery": "^3.7.1", | "jquery": "^3.7.1", | ||||
| "moment": "^2.30.1", | "moment": "^2.30.1", | ||||
| @@ -64,6 +65,7 @@ | |||||
| "vue3-datepicker": "^0.4.0", | "vue3-datepicker": "^0.4.0", | ||||
| "vue3-google-map": "^0.18.0", | "vue3-google-map": "^0.18.0", | ||||
| "vue3-persian-datetime-picker": "^1.2.2", | "vue3-persian-datetime-picker": "^1.2.2", | ||||
| "vue3-select2-component": "^0.1.7", | |||||
| "vue3-toastify": "^0.2.5", | "vue3-toastify": "^0.2.5", | ||||
| "vuex": "^4.1.0", | "vuex": "^4.1.0", | ||||
| "yarn": "^1.22.21" | "yarn": "^1.22.21" | ||||
| @@ -11982,6 +11984,12 @@ | |||||
| "dev": true, | "dev": true, | ||||
| "license": "MIT" | "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": { | "node_modules/selfsigned": { | ||||
| "version": "2.4.1", | "version": "2.4.1", | ||||
| "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", | "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", | ||||
| @@ -13689,6 +13697,16 @@ | |||||
| "moment-jalaali": "^0.9.4" | "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": { | "node_modules/vue3-toastify": { | ||||
| "version": "0.2.5", | "version": "0.2.5", | ||||
| "resolved": "https://registry.npmjs.org/vue3-toastify/-/vue3-toastify-0.2.5.tgz", | "resolved": "https://registry.npmjs.org/vue3-toastify/-/vue3-toastify-0.2.5.tgz", | ||||
| @@ -39,6 +39,7 @@ | |||||
| "form-wizard-vue3": "^1.1.0", | "form-wizard-vue3": "^1.1.0", | ||||
| "fslightbox": "^3.4.1", | "fslightbox": "^3.4.1", | ||||
| "is": "^3.3.0", | "is": "^3.3.0", | ||||
| "jalaali-js": "^1.2.7", | |||||
| "jalali-moment": "^3.3.11", | "jalali-moment": "^3.3.11", | ||||
| "jquery": "^3.7.1", | "jquery": "^3.7.1", | ||||
| "moment": "^2.30.1", | "moment": "^2.30.1", | ||||
| @@ -64,6 +65,7 @@ | |||||
| "vue3-datepicker": "^0.4.0", | "vue3-datepicker": "^0.4.0", | ||||
| "vue3-google-map": "^0.18.0", | "vue3-google-map": "^0.18.0", | ||||
| "vue3-persian-datetime-picker": "^1.2.2", | "vue3-persian-datetime-picker": "^1.2.2", | ||||
| "vue3-select2-component": "^0.1.7", | |||||
| "vue3-toastify": "^0.2.5", | "vue3-toastify": "^0.2.5", | ||||
| "vuex": "^4.1.0", | "vuex": "^4.1.0", | ||||
| "yarn": "^1.22.21" | "yarn": "^1.22.21" | ||||
| @@ -30,7 +30,6 @@ export default { | |||||
| const logoutUser = async () => { | const logoutUser = async () => { | ||||
| try { | try { | ||||
| const result = await Swal.fire({ | const result = await Swal.fire({ | ||||
| title: "آیا مطمئن هستید؟", | |||||
| text: "شما از سیستم خارج خواهید شد.", | text: "شما از سیستم خارج خواهید شد.", | ||||
| icon: "warning", | icon: "warning", | ||||
| showCancelButton: true, | showCancelButton: true, | ||||
| @@ -51,6 +50,10 @@ export default { | |||||
| } | } | ||||
| }; | }; | ||||
| const gotoAccount = () => { | |||||
| router.push({ name: "profile" }); | |||||
| }; | |||||
| onMounted(() => { | onMounted(() => { | ||||
| updateLogo(); | updateLogo(); | ||||
| @@ -68,7 +71,7 @@ export default { | |||||
| }); | }); | ||||
| }); | }); | ||||
| return { currentLogo, user, logoutUser }; | |||||
| return { currentLogo, user, logoutUser, gotoAccount }; | |||||
| }, | }, | ||||
| components: { | components: { | ||||
| ChevronDownIcon, | ChevronDownIcon, | ||||
| @@ -134,7 +137,6 @@ export default { | |||||
| let collapses = document.querySelectorAll(".navbar-content .collapse"); | let collapses = document.querySelectorAll(".navbar-content .collapse"); | ||||
| collapses.forEach((collapse) => { | collapses.forEach((collapse) => { | ||||
| // Hide sibling collapses on `show.bs.collapse` | |||||
| collapse.addEventListener("show.bs.collapse", (e) => { | collapse.addEventListener("show.bs.collapse", (e) => { | ||||
| e.stopPropagation(); | e.stopPropagation(); | ||||
| let closestCollapse = collapse.parentElement.closest(".collapse"); | let closestCollapse = collapse.parentElement.closest(".collapse"); | ||||
| @@ -246,6 +248,17 @@ export default { | |||||
| <span class="pc-mtext">کاربران</span></router-link | <span class="pc-mtext">کاربران</span></router-link | ||||
| > | > | ||||
| </li> | </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' }"> | <li class="pc-item" :class="{ active: this.$route.path === '/brands' }"> | ||||
| <router-link to="/brands" class="pc-link"> | <router-link to="/brands" class="pc-link"> | ||||
| <span class="pc-micon"> | <span class="pc-micon"> | ||||
| @@ -266,6 +279,17 @@ export default { | |||||
| <span class="pc-mtext">ویژگی ها</span></router-link | <span class="pc-mtext">ویژگی ها</span></router-link | ||||
| > | > | ||||
| </li> | </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' }"> | <li class="pc-item" :class="{ active: this.$route.path === '/blogs' }"> | ||||
| <router-link to="/blogs" class="pc-link"> | <router-link to="/blogs" class="pc-link"> | ||||
| <span class="pc-micon"> | <span class="pc-micon"> | ||||
| @@ -296,13 +320,44 @@ export default { | |||||
| <span class="pc-mtext">تخفیف ها</span></router-link | <span class="pc-mtext">تخفیف ها</span></router-link | ||||
| > | > | ||||
| </li> | </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"> | <span class="pc-micon"> | ||||
| <i class="ph-duotone ph-shopping-cart"></i> | <i class="ph-duotone ph-shopping-cart"></i> | ||||
| </span> | </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> | ||||
| <li | <li | ||||
| class="pc-item" | class="pc-item" | ||||
| @@ -315,10 +370,7 @@ export default { | |||||
| <span class="pc-mtext">نظرات</span></router-link | <span class="pc-mtext">نظرات</span></router-link | ||||
| > | > | ||||
| </li> | </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"> | <router-link to="/faqs" class="pc-link"> | ||||
| <span class="pc-micon"> | <span class="pc-micon"> | ||||
| <i class="ph-duotone ph-file-text"></i> | <i class="ph-duotone ph-file-text"></i> | ||||
| @@ -365,6 +417,15 @@ export default { | |||||
| </div> | </div> | ||||
| </li> | </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 --> | <!-- other --> | ||||
| </ul> | </ul> | ||||
| </simplebar> | </simplebar> | ||||
| @@ -392,7 +453,7 @@ export default { | |||||
| </span> | </span> | ||||
| </template> | </template> | ||||
| <BRow xl="6"> | <BRow xl="6"> | ||||
| <BCol xl="6"> | |||||
| <BCol @click="gotoAccount()" xl="6"> | |||||
| <BDropdownItem class="pc-user-links p-0"> | <BDropdownItem class="pc-user-links p-0"> | ||||
| <i class="ph-duotone ph-user"></i> | <i class="ph-duotone ph-user"></i> | ||||
| <br /> | <br /> | ||||
| @@ -128,7 +128,6 @@ | |||||
| <script> | <script> | ||||
| import { ref } from "vue"; | import { ref } from "vue"; | ||||
| import Swal from "sweetalert2"; | |||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| @@ -226,12 +225,10 @@ export default { | |||||
| }) | }) | ||||
| .catch((error) => { | .catch((error) => { | ||||
| console.error(error); | console.error(error); | ||||
| Swal.fire({ | |||||
| icon: "error", | |||||
| title: "خطا", | |||||
| text: `افزودن برند با مشکل مواجه شد: ${ | |||||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||||
| }`, | |||||
| toast.error("!مشکلی در ایجاد برند پیش آمد", { | |||||
| position: "top-right", | |||||
| autoClose: 1000 | |||||
| }); | }); | ||||
| }) | }) | ||||
| .finally(() => { | .finally(() => { | ||||
| @@ -60,13 +60,7 @@ | |||||
| alt="Image Preview" | alt="Image Preview" | ||||
| class="img-fluid rounded shadow-sm 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> | </div> | ||||
| <small v-if="errors.localImage" class="text-danger"> | <small v-if="errors.localImage" class="text-danger"> | ||||
| @@ -129,7 +123,7 @@ | |||||
| <script> | <script> | ||||
| import { ref, toRef, watch } from "vue"; | import { ref, toRef, watch } from "vue"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| import Swal from "sweetalert2"; | |||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| @@ -206,15 +200,7 @@ export default { | |||||
| (newVal) => (localId.value = newVal) | (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 = () => { | const validateForm = () => { | ||||
| errors.value = {}; | errors.value = {}; | ||||
| @@ -265,12 +251,9 @@ export default { | |||||
| }) | }) | ||||
| .catch((error) => { | .catch((error) => { | ||||
| console.error(error); | console.error(error); | ||||
| Swal.fire({ | |||||
| icon: "error", | |||||
| title: "خطا", | |||||
| text: `ویرایش برند با مشکل مواجه شد: ${ | |||||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||||
| }`, | |||||
| toast.error("!ویرایش برند با مشکل مواجه شد", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | }); | ||||
| }) | }) | ||||
| .finally(() => { | .finally(() => { | ||||
| @@ -288,7 +271,7 @@ export default { | |||||
| localImage, | localImage, | ||||
| handleImageChange, | handleImageChange, | ||||
| imagePreview, | imagePreview, | ||||
| removeImage, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -63,8 +63,9 @@ | |||||
| class="form-select" | class="form-select" | ||||
| v-model="role" | v-model="role" | ||||
| @change="clearError('role')" | @change="clearError('role')" | ||||
| placeholder="نوع کاربر" | |||||
| > | > | ||||
| <option disabled value="">نوع کاربر</option> | |||||
| <option value="admin">مدیر</option> | <option value="admin">مدیر</option> | ||||
| <option value="client">مشتری</option> | <option value="client">مشتری</option> | ||||
| </select> | </select> | ||||
| @@ -140,7 +141,7 @@ | |||||
| <script> | <script> | ||||
| import { ref } from "vue"; | import { ref } from "vue"; | ||||
| import Swal from "sweetalert2"; | |||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| @@ -204,13 +205,10 @@ export default { | |||||
| }) | }) | ||||
| .catch((error) => { | .catch((error) => { | ||||
| console.error(error); | console.error(error); | ||||
| Swal.fire({ | |||||
| icon: "error", | |||||
| title: "خطا", | |||||
| text: `افزودن کاربر با مشکل مواجه شد: ${ | |||||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||||
| }`, | |||||
| confirmButtonText: "باشه", | |||||
| toast.error("!افزودن کاربر با مشکل مواجه شد", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | }); | ||||
| }) | }) | ||||
| .finally(() => { | .finally(() => { | ||||
| @@ -100,7 +100,6 @@ | |||||
| <script> | <script> | ||||
| import { ref, toRef, watch } from "vue"; | import { ref, toRef, watch } from "vue"; | ||||
| import Swal from "sweetalert2"; | |||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| @@ -145,7 +144,8 @@ export default { | |||||
| console.log(localAttributeValues.value); | console.log(localAttributeValues.value); | ||||
| formData.append("title", colorName.value); | formData.append("title", colorName.value); | ||||
| formData.append("code", colorCode.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) | ApiServiece.post(`admin/attribute-values`, formData) | ||||
| .then((resp) => { | .then((resp) => { | ||||
| @@ -163,12 +163,9 @@ export default { | |||||
| }) | }) | ||||
| .catch((error) => { | .catch((error) => { | ||||
| console.error(error); | console.error(error); | ||||
| Swal.fire({ | |||||
| icon: "error", | |||||
| title: "خطا", | |||||
| text: `افزودن ویژگی با مشکل مواجه شد: ${ | |||||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||||
| }`, | |||||
| toast.success("!مشکلی در ایجاد ویژگی پیش آمد", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | }); | ||||
| }) | }) | ||||
| .finally(() => { | .finally(() => { | ||||
| @@ -98,7 +98,7 @@ | |||||
| <script> | <script> | ||||
| import { ref, toRef, watch } from "vue"; | import { ref, toRef, watch } from "vue"; | ||||
| import Swal from "sweetalert2"; | |||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| @@ -190,12 +190,9 @@ export default { | |||||
| }) | }) | ||||
| .catch((error) => { | .catch((error) => { | ||||
| console.error(error); | console.error(error); | ||||
| Swal.fire({ | |||||
| icon: "error", | |||||
| title: "خطا", | |||||
| text: `ویرایش ویژگی با مشکل مواجه شد: ${ | |||||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||||
| }`, | |||||
| toast.success("!مشکلی در ویرایش ویژگی پیش آمد", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | }); | ||||
| }) | }) | ||||
| .finally(() => { | .finally(() => { | ||||
| @@ -107,25 +107,19 @@ | |||||
| <script> | <script> | ||||
| import { iconData } from "../../../views/live-preview/icon/data"; | 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 { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| export default { | export default { | ||||
| props: { | |||||
| }, | |||||
| props: {}, | |||||
| setup(props, { emit }) { | setup(props, { emit }) { | ||||
| const title = ref(); | const title = ref(); | ||||
| const selectedIcon = ref(); | const selectedIcon = ref(); | ||||
| const errors = ref({}); | const errors = ref({}); | ||||
| const loading = ref(false); | const loading = ref(false); | ||||
| const clearError = (field) => { | const clearError = (field) => { | ||||
| errors.value[field] = ""; | errors.value[field] = ""; | ||||
| }; | }; | ||||
| @@ -164,12 +158,9 @@ export default { | |||||
| }) | }) | ||||
| .catch((error) => { | .catch((error) => { | ||||
| console.error(error); | console.error(error); | ||||
| Swal.fire({ | |||||
| icon: "error", | |||||
| title: "خطا", | |||||
| text: `!افزودن دسته با مشکل مواجه شد: ${ | |||||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||||
| }`, | |||||
| toast.error("!مشکلی در اضافه کردن دسته پیش آمد", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | }); | ||||
| }) | }) | ||||
| .finally(() => { | .finally(() => { | ||||
| @@ -59,14 +59,14 @@ | |||||
| </b-dropdown-item> | </b-dropdown-item> | ||||
| </b-dropdown> | </b-dropdown> | ||||
| <div v-if="selectedIcon" class="mt-2"> | |||||
| <div v-if="localIcon" class="mt-2"> | |||||
| <label class="form-label">آیکن انتخاب شده:</label> | <label class="form-label">آیکن انتخاب شده:</label> | ||||
| <div class="selected-icon-container"> | <div class="selected-icon-container"> | ||||
| <i :class="`ph-duotone ${selectedIcon}`"></i> | |||||
| <i :class="`ph-duotone ${localIcon}`"></i> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div v-if="!selectedIcon" class="mt-2"> | |||||
| <div v-if="!localIcon" class="mt-2"> | |||||
| <label class="form-label">آیکن انتخاب شده:</label> | <label class="form-label">آیکن انتخاب شده:</label> | ||||
| <div class="selected-icon-container"> | <div class="selected-icon-container"> | ||||
| <i :class="`ph-duotone ${localIcon}`"></i> | <i :class="`ph-duotone ${localIcon}`"></i> | ||||
| @@ -113,7 +113,6 @@ | |||||
| <script> | <script> | ||||
| import { iconData } from "../../../views/live-preview/icon/data"; | import { iconData } from "../../../views/live-preview/icon/data"; | ||||
| import { ref, toRef, watch } from "vue"; | import { ref, toRef, watch } from "vue"; | ||||
| import Swal from "sweetalert2"; | |||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| @@ -136,7 +135,6 @@ export default { | |||||
| setup(props, { emit }) { | setup(props, { emit }) { | ||||
| const localTitle = toRef(props.title); | const localTitle = toRef(props.title); | ||||
| const localIcon = ref(props.icon); | const localIcon = ref(props.icon); | ||||
| const selectedIcon = ref(); | |||||
| const localId = toRef(props.id); | const localId = toRef(props.id); | ||||
| const errors = ref({}); | const errors = ref({}); | ||||
| const loading = ref(false); | const loading = ref(false); | ||||
| @@ -164,22 +162,21 @@ export default { | |||||
| errors.value = {}; | errors.value = {}; | ||||
| if (!localTitle.value) | if (!localTitle.value) | ||||
| errors.value.localTitle = "وارد کردن عنوان ضروری می باشد"; | errors.value.localTitle = "وارد کردن عنوان ضروری می باشد"; | ||||
| if (!selectedIcon.value) errors.value.icon = "انتخاب آیکن ضروری است"; | |||||
| if (!localIcon.value) errors.value.icon = "انتخاب آیکن ضروری است"; | |||||
| return Object.keys(errors.value).length === 0; | return Object.keys(errors.value).length === 0; | ||||
| }; | }; | ||||
| const setSelectedIcon = (icon) => { | const setSelectedIcon = (icon) => { | ||||
| selectedIcon.value = icon; | |||||
| localIcon.value = icon; | |||||
| }; | }; | ||||
| const editCat = () => { | const editCat = () => { | ||||
| console.log(selectedIcon.value); | |||||
| if (!validateForm()) return; | if (!validateForm()) return; | ||||
| loading.value = true; | loading.value = true; | ||||
| const formData = new FormData(); | const formData = new FormData(); | ||||
| formData.append("title", localTitle.value); | formData.append("title", localTitle.value); | ||||
| formData.append("icon", selectedIcon.value); | |||||
| formData.append("icon", localIcon.value); | |||||
| ApiServiece.put(`admin/blog-categories/${localId.value}`, formData) | ApiServiece.put(`admin/blog-categories/${localId.value}`, formData) | ||||
| .then(() => { | .then(() => { | ||||
| toast.success("!دسته با موفقیت ویرایش شد", { | toast.success("!دسته با موفقیت ویرایش شد", { | ||||
| @@ -195,12 +192,9 @@ export default { | |||||
| }) | }) | ||||
| .catch((error) => { | .catch((error) => { | ||||
| console.error(error); | console.error(error); | ||||
| Swal.fire({ | |||||
| icon: "error", | |||||
| title: "خطا", | |||||
| text: `!ویرایش دسته با مشکل مواجه شد: ${ | |||||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||||
| }`, | |||||
| toast.error("!ویرایش دسته با مشکل مواجه شد", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | }); | ||||
| }) | }) | ||||
| .finally(() => { | .finally(() => { | ||||
| @@ -214,7 +208,6 @@ export default { | |||||
| clearError, | clearError, | ||||
| editCat, | editCat, | ||||
| localTitle, | localTitle, | ||||
| selectedIcon, | |||||
| iconData, | iconData, | ||||
| setSelectedIcon, | setSelectedIcon, | ||||
| localIcon, | localIcon, | ||||
| @@ -98,11 +98,11 @@ | |||||
| <BRow class="g-3"> | <BRow class="g-3"> | ||||
| <!-- Brand Description --> | <!-- Brand Description --> | ||||
| <BCol lg="12"> | |||||
| <BCol lg="6"> | |||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label class="form-label">انتخاب پدر</label> | <label class="form-label">انتخاب پدر</label> | ||||
| <select v-model="selectedPaernt" class="form-control"> | |||||
| <option value="" disabled selected>انتخاب کنید</option> | |||||
| <select v-model="selectedPaernt" class="form-control" placeholder="انتخاب کنید"> | |||||
| <option | <option | ||||
| v-for="parent in localParents" | v-for="parent in localParents" | ||||
| :key="parent.id" | :key="parent.id" | ||||
| @@ -113,6 +113,40 @@ | |||||
| </select> | </select> | ||||
| </div> | </div> | ||||
| </BCol> | </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> | </BRow> | ||||
| <!-- Submit Buttons --> | <!-- Submit Buttons --> | ||||
| @@ -146,8 +180,8 @@ | |||||
| </template> | </template> | ||||
| <script> | <script> | ||||
| import { iconData } from "../../../views/live-preview/icon/data"; | |||||
| import { ref, toRef, watch } from "vue"; | import { ref, toRef, watch } from "vue"; | ||||
| import Swal from "sweetalert2"; | |||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| @@ -160,6 +194,7 @@ export default { | |||||
| }, | }, | ||||
| }, | }, | ||||
| setup(props, { emit }) { | setup(props, { emit }) { | ||||
| const selectedIcon = ref(); | |||||
| const selectedPaernt = ref(); | const selectedPaernt = ref(); | ||||
| const localParents = toRef(props.parents); | const localParents = toRef(props.parents); | ||||
| const image = ref(null); | const image = ref(null); | ||||
| @@ -213,12 +248,17 @@ export default { | |||||
| } | } | ||||
| }; | }; | ||||
| const setSelectedIcon = (icon) => { | |||||
| selectedIcon.value = icon; | |||||
| }; | |||||
| const validateForm = () => { | const validateForm = () => { | ||||
| errors.value = {}; | errors.value = {}; | ||||
| if (!description.value) | if (!description.value) | ||||
| errors.value.description = "وارد کردن توضیحات ضروری می باشد"; | errors.value.description = "وارد کردن توضیحات ضروری می باشد"; | ||||
| if (!title.value) errors.value.title = "وارد کردن عنوان ضروری می باشد"; | if (!title.value) errors.value.title = "وارد کردن عنوان ضروری می باشد"; | ||||
| if (!image.value) errors.value.image = "یک عکس انتخاب نمایید"; | if (!image.value) errors.value.image = "یک عکس انتخاب نمایید"; | ||||
| if (!selectedIcon.value) errors.value.icon = "انتخاب آیکن ضروری است"; | |||||
| return Object.keys(errors.value).length === 0; | return Object.keys(errors.value).length === 0; | ||||
| }; | }; | ||||
| @@ -234,6 +274,7 @@ export default { | |||||
| formData.append("title", title.value); | formData.append("title", title.value); | ||||
| formData.append("description", description.value); | formData.append("description", description.value); | ||||
| formData.append("image", image.value); | formData.append("image", image.value); | ||||
| formData.append("icon", selectedIcon.value); | |||||
| if (selectedPaernt.value) { | if (selectedPaernt.value) { | ||||
| formData.append("parent_id", selectedPaernt.value); | formData.append("parent_id", selectedPaernt.value); | ||||
| } | } | ||||
| @@ -258,12 +299,9 @@ export default { | |||||
| }) | }) | ||||
| .catch((error) => { | .catch((error) => { | ||||
| console.error(error); | console.error(error); | ||||
| Swal.fire({ | |||||
| icon: "error", | |||||
| title: "خطا", | |||||
| text: `!افزودن دسته با مشکل مواجه شد: ${ | |||||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||||
| }`, | |||||
| toast.error("!افزودن دسته با مشکل مواجه شد", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | }); | ||||
| }) | }) | ||||
| .finally(() => { | .finally(() => { | ||||
| @@ -284,6 +322,9 @@ export default { | |||||
| description, | description, | ||||
| localParents, | localParents, | ||||
| selectedPaernt, | selectedPaernt, | ||||
| setSelectedIcon, | |||||
| iconData, | |||||
| selectedIcon, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -367,4 +408,31 @@ export default { | |||||
| .delete-btn:focus { | .delete-btn:focus { | ||||
| outline: none; | 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> | </style> | ||||
| @@ -10,7 +10,7 @@ | |||||
| <div class="modal-dialog modal-sm" role="document"> | <div class="modal-dialog modal-sm" role="document"> | ||||
| <div class="modal-content"> | <div class="modal-content"> | ||||
| <div class="modal-header"> | <div class="modal-header"> | ||||
| <h5 class="modal-title" id="exampleModalLabel">ویرایش برند</h5> | |||||
| <h5 class="modal-title" id="exampleModalLabel">ویرایش دسته</h5> | |||||
| <button | <button | ||||
| type="button" | type="button" | ||||
| @@ -60,13 +60,6 @@ | |||||
| alt="Image Preview" | alt="Image Preview" | ||||
| class="img-fluid rounded shadow-sm 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> | </div> | ||||
| <small v-if="errors.localImage" class="text-danger"> | <small v-if="errors.localImage" class="text-danger"> | ||||
| @@ -98,11 +91,14 @@ | |||||
| <BRow class="g-3"> | <BRow class="g-3"> | ||||
| <!-- Brand Description --> | <!-- Brand Description --> | ||||
| <BCol lg="12"> | |||||
| <BCol lg="6"> | |||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label class="form-label">انتخاب پدر</label> | <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 | <option | ||||
| v-for="parent in allLocalParents" | v-for="parent in allLocalParents" | ||||
| :key="parent.id" | :key="parent.id" | ||||
| @@ -113,6 +109,46 @@ | |||||
| </select> | </select> | ||||
| </div> | </div> | ||||
| </BCol> | </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> | </BRow> | ||||
| <!-- Submit Buttons --> | <!-- Submit Buttons --> | ||||
| @@ -148,9 +184,10 @@ | |||||
| <script> | <script> | ||||
| import { ref, toRef, watch } from "vue"; | import { ref, toRef, watch } from "vue"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| import Swal from "sweetalert2"; | |||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| import { iconData } from "../../../views/live-preview/icon/data"; | |||||
| export default { | export default { | ||||
| props: { | props: { | ||||
| @@ -178,9 +215,14 @@ export default { | |||||
| type: Array, | type: Array, | ||||
| Required: true, | Required: true, | ||||
| }, | }, | ||||
| icon: { | |||||
| type: String, | |||||
| Required: true, | |||||
| }, | |||||
| }, | }, | ||||
| setup(props, { emit }) { | setup(props, { emit }) { | ||||
| const localIcon = toRef(props.icon); | |||||
| const imagePreview = ref(null); | const imagePreview = ref(null); | ||||
| const allLocalParents = toRef(props.allParents); | const allLocalParents = toRef(props.allParents); | ||||
| const localParent = toRef(props.parent); | const localParent = toRef(props.parent); | ||||
| @@ -190,6 +232,7 @@ export default { | |||||
| const image = ref(null); | const image = ref(null); | ||||
| const localId = toRef(props.id); | const localId = toRef(props.id); | ||||
| const errors = ref({}); | const errors = ref({}); | ||||
| const loading = ref(false); | const loading = ref(false); | ||||
| const handleImageChange = (event) => { | const handleImageChange = (event) => { | ||||
| @@ -214,6 +257,10 @@ export default { | |||||
| } | } | ||||
| }; | }; | ||||
| const setSelectedIcon = (icon) => { | |||||
| localIcon.value = icon; | |||||
| }; | |||||
| watch( | watch( | ||||
| () => props.title, | () => props.title, | ||||
| (newVal) => (localTitle.value = newVal) | (newVal) => (localTitle.value = newVal) | ||||
| @@ -245,15 +292,10 @@ export default { | |||||
| (newVal) => (allLocalParents.value = newVal) | (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 = () => { | const validateForm = () => { | ||||
| errors.value = {}; | errors.value = {}; | ||||
| @@ -264,6 +306,7 @@ export default { | |||||
| if (!localImage.value && !imagePreview.value) { | if (!localImage.value && !imagePreview.value) { | ||||
| errors.value.localImage = "یک عکس انتخاب نمایید"; | errors.value.localImage = "یک عکس انتخاب نمایید"; | ||||
| } | } | ||||
| if (!localIcon.value) errors.value.icon = "انتخاب آیکن ضروری است"; | |||||
| return Object.keys(errors.value).length === 0; | return Object.keys(errors.value).length === 0; | ||||
| }; | }; | ||||
| @@ -272,12 +315,14 @@ export default { | |||||
| }; | }; | ||||
| const editCat = () => { | const editCat = () => { | ||||
| console.log(localId.value); | |||||
| if (!validateForm()) return; | if (!validateForm()) return; | ||||
| loading.value = true; | loading.value = true; | ||||
| const formData = new FormData(); | const formData = new FormData(); | ||||
| formData.append("title", localTitle.value); | formData.append("title", localTitle.value); | ||||
| formData.append("description", localDesc.value); | formData.append("description", localDesc.value); | ||||
| formData.append("icon", localIcon.value); | |||||
| if (image.value) { | if (image.value) { | ||||
| formData.append("image", image.value); | formData.append("image", image.value); | ||||
| @@ -307,12 +352,9 @@ export default { | |||||
| }) | }) | ||||
| .catch((error) => { | .catch((error) => { | ||||
| console.error(error); | console.error(error); | ||||
| Swal.fire({ | |||||
| icon: "error", | |||||
| title: "خطا", | |||||
| text: `ویرایش دسته با مشکل مواجه شد: ${ | |||||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||||
| }`, | |||||
| toast.error("!ویراش دسته با مشکل مواجه شد", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | }); | ||||
| }) | }) | ||||
| .finally(() => { | .finally(() => { | ||||
| @@ -330,9 +372,12 @@ export default { | |||||
| localImage, | localImage, | ||||
| handleImageChange, | handleImageChange, | ||||
| imagePreview, | imagePreview, | ||||
| removeImage, | |||||
| iconData, | |||||
| localParent, | localParent, | ||||
| allLocalParents, | allLocalParents, | ||||
| setSelectedIcon, | |||||
| localIcon, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -516,4 +561,33 @@ export default { | |||||
| .delete-btn:focus { | .delete-btn:focus { | ||||
| outline: none; | 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> | </style> | ||||
| @@ -1,12 +1,13 @@ | |||||
| <template> | <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-dialog modal-lg" role="document"> | ||||
| <div class="modal-content"> | <div class="modal-content"> | ||||
| <div class="modal-header"> | <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" | class="form-select" | ||||
| v-model="localRole" | v-model="localRole" | ||||
| @change="clearError('role')" | @change="clearError('role')" | ||||
| placeholder="نوع کاربر" | |||||
| > | > | ||||
| <option disabled value="">نوع کاربر</option> | |||||
| <option value="admin">مدیر</option> | <option value="admin">مدیر</option> | ||||
| <option value="client">مشتری</option> | <option value="client">مشتری</option> | ||||
| </select> | </select> | ||||
| @@ -121,7 +122,6 @@ | |||||
| <script> | <script> | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| import { ref, toRef, watch } from "vue"; | import { ref, toRef, watch } from "vue"; | ||||
| import Swal from "sweetalert2"; | |||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| @@ -217,13 +217,10 @@ export default { | |||||
| }) | }) | ||||
| .catch((error) => { | .catch((error) => { | ||||
| console.error(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(() => { | .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> | <script> | ||||
| import rightBar from "./right-bar.vue"; | |||||
| export default { | export default { | ||||
| name: "NAVBAR", | name: "NAVBAR", | ||||
| components: {}, | components: {}, | ||||
| @@ -7,10 +8,15 @@ export default { | |||||
| isFullscreen: false, | isFullscreen: false, | ||||
| isSidebarHidden: false, | isSidebarHidden: false, | ||||
| currentMode: "light", | currentMode: "light", | ||||
| rightBar, | |||||
| show: false, | show: false, | ||||
| }; | }; | ||||
| }, | }, | ||||
| mounted() { | mounted() { | ||||
| const savedMode = localStorage.getItem("themeMode"); | |||||
| if (savedMode) { | |||||
| this.changeMode(savedMode); // Apply the saved theme mode | |||||
| } | |||||
| // Add event listener for keydown events | // Add event listener for keydown events | ||||
| document.addEventListener("keydown", this.handleKeyDown); | document.addEventListener("keydown", this.handleKeyDown); | ||||
| }, | }, | ||||
| @@ -43,6 +49,7 @@ export default { | |||||
| }, | }, | ||||
| changeMode(mode) { | changeMode(mode) { | ||||
| this.currentMode = mode; | this.currentMode = mode; | ||||
| localStorage.setItem("themeMode", mode); | |||||
| if (mode === "dark") { | if (mode === "dark") { | ||||
| document.body.setAttribute("data-pc-theme", "dark"); | document.body.setAttribute("data-pc-theme", "dark"); | ||||
| document.body.setAttribute("data-topbar", "dark"); | document.body.setAttribute("data-topbar", "dark"); | ||||
| @@ -90,8 +97,45 @@ export default { | |||||
| <i class="ti ti-menu-2"></i> | <i class="ti ti-menu-2"></i> | ||||
| </a> | </a> | ||||
| </li> | </li> | ||||
| </ul> | </ul> | ||||
| </div> | </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> | </div> | ||||
| </template> | </template> | ||||
| @@ -1,3 +1,4 @@ | |||||
| // پنل ادمین | |||||
| export default [ | export default [ | ||||
| { | { | ||||
| path: "/", | path: "/", | ||||
| @@ -165,6 +166,16 @@ export default [ | |||||
| }, | }, | ||||
| component: () => import("../views/live-preview/pages/orders/orders.vue"), | 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", | path: "/singleOrder/:id", | ||||
| name: "singleOrder", | name: "singleOrder", | ||||
| @@ -175,6 +186,16 @@ export default [ | |||||
| component: () => | component: () => | ||||
| import("../views/live-preview/pages/orders/singleOrder.vue"), | 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", | path: "/comments", | ||||
| name: "comments", | name: "comments", | ||||
| @@ -203,6 +224,64 @@ export default [ | |||||
| }, | }, | ||||
| component: () => import("../views/live-preview/pages/faqs/editFaqs.vue"), | 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: "/", | path: "/", | ||||
| name: "live-preview", | name: "live-preview", | ||||
| @@ -39,7 +39,7 @@ export default { | |||||
| const getAttributes = () => { | const getAttributes = () => { | ||||
| filterLoading.value = true; | filterLoading.value = true; | ||||
| ApiServiece.get( | ApiServiece.get( | ||||
| `admin/attribute-values?title=${encodeURIComponent( | |||||
| `admin/attribute-values?attribute_id=1&title=${encodeURIComponent( | |||||
| searchQuery.value || "" | searchQuery.value || "" | ||||
| )}&code=${encodeURIComponent(searchQuery.value || "")} | )}&code=${encodeURIComponent(searchQuery.value || "")} | ||||
| &paginate=${paginate.value || 10}&page=${page.value || 1} | &paginate=${paginate.value || 10}&page=${page.value || 1} | ||||
| @@ -156,6 +156,7 @@ export default { | |||||
| const getAttributeValues = () => { | const getAttributeValues = () => { | ||||
| ApiServiece.get(`admin/attributes`).then((resp) => { | ApiServiece.get(`admin/attributes`).then((resp) => { | ||||
| console.log(resp) | |||||
| attributeValues.value = resp.data.data; | attributeValues.value = resp.data.data; | ||||
| }); | }); | ||||
| }; | }; | ||||
| @@ -195,7 +196,7 @@ export default { | |||||
| <div class="col-md-12"> | <div class="col-md-12"> | ||||
| <div class="card shadow-sm border-0 rounded"> | <div class="card shadow-sm border-0 rounded"> | ||||
| <div | <div | ||||
| class="card-header d-flex justify-content-between align-items-center p-3 bg-primary text-white" | |||||
| class="card-header d-flex justify-content-between align-items-center p-3 " | |||||
| dir="rtl" | dir="rtl" | ||||
| > | > | ||||
| <div class="d-flex align-items-center"> | <div class="d-flex align-items-center"> | ||||
| @@ -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) => { | const editModalData = (id, title, icon) => { | ||||
| catId.value = id; | catId.value = id; | ||||
| catTitle.value = title; | catTitle.value = title; | ||||
| catIcon.value = icon; | catIcon.value = icon; | ||||
| @@ -183,7 +184,7 @@ export default { | |||||
| <div class="col-md-12"> | <div class="col-md-12"> | ||||
| <div class="card shadow-sm border-0 rounded"> | <div class="card shadow-sm border-0 rounded"> | ||||
| <div | <div | ||||
| class="card-header d-flex justify-content-between align-items-center p-3 bg-primary text-white" | |||||
| class="card-header d-flex justify-content-between align-items-center p-3" | |||||
| dir="rtl" | dir="rtl" | ||||
| > | > | ||||
| <div class="d-flex align-items-center"> | <div class="d-flex align-items-center"> | ||||
| @@ -27,12 +27,12 @@ | |||||
| <BCol md="6"> | <BCol md="6"> | ||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label class="form-label">اسلاگ</label> | |||||
| <label class="form-label">کلمه کلیدی</label> | |||||
| <input | <input | ||||
| type="text" | type="text" | ||||
| v-model="slug" | v-model="slug" | ||||
| class="form-control" | class="form-control" | ||||
| placeholder="اسلاگ بلاگ" | |||||
| placeholder="کلمه کلیدی بلاگ" | |||||
| :class="{ 'is-invalid': errors.slug }" | :class="{ 'is-invalid': errors.slug }" | ||||
| @input="clearError('slug')" | @input="clearError('slug')" | ||||
| /> | /> | ||||
| @@ -98,9 +98,10 @@ | |||||
| :class="{ 'is-invalid': errors.blogCat }" | :class="{ 'is-invalid': errors.blogCat }" | ||||
| v-model="blogCat" | v-model="blogCat" | ||||
| class="form-control" | 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"> | <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | ||||
| {{ cat.title }} | {{ cat.title }} | ||||
| </option> | </option> | ||||
| @@ -165,7 +166,7 @@ | |||||
| </template> | </template> | ||||
| <script> | <script> | ||||
| import Swal from "sweetalert2"; | |||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| @@ -232,7 +233,7 @@ export default { | |||||
| const validateForm = () => { | const validateForm = () => { | ||||
| errors.value = {}; | errors.value = {}; | ||||
| if (!title.value) errors.value.title = "وارد کردن عنوان بلاگ الزامی است"; | if (!title.value) errors.value.title = "وارد کردن عنوان بلاگ الزامی است"; | ||||
| if (!slug.value) errors.value.slug = "وارد کردن اسلاگ بلاگ ضروری می باشد"; | |||||
| if (!slug.value) errors.value.slug = "وارد کردن کلمه کلیدی بلاگ ضروری می باشد"; | |||||
| if (!summary.value) | if (!summary.value) | ||||
| errors.value.summary = "وارد کردن خلاصه بلاگ ضروری می باشد"; | errors.value.summary = "وارد کردن خلاصه بلاگ ضروری می باشد"; | ||||
| if (!blogCat.value) | if (!blogCat.value) | ||||
| @@ -313,12 +314,9 @@ export default { | |||||
| .catch((error) => { | .catch((error) => { | ||||
| console.error(error); | console.error(error); | ||||
| Swal.fire({ | |||||
| icon: "error", | |||||
| title: "خطا", | |||||
| text: `!افزودن بلاگ با مشکل مواجه شد: ${ | |||||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||||
| }`, | |||||
| toast.error("!مشکلی در اضافه کردن بلاگ پیش آمد", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | }); | ||||
| }) | }) | ||||
| .finally(() => { | .finally(() => { | ||||
| @@ -73,7 +73,7 @@ export default { | |||||
| const deleteBlog = (id, title) => { | const deleteBlog = (id, title) => { | ||||
| Swal.fire({ | Swal.fire({ | ||||
| text: `می خواهید بلاگ ${title} را حذف کنید ؟`, | |||||
| text: `می خواهید بلاگ ${title} را حذف کنید؟`, | |||||
| icon: "warning", | icon: "warning", | ||||
| showCancelButton: true, | showCancelButton: true, | ||||
| confirmButtonColor: "#3085d6", | confirmButtonColor: "#3085d6", | ||||
| @@ -162,7 +162,7 @@ export default { | |||||
| <div class="col-md-12"> | <div class="col-md-12"> | ||||
| <div class="card shadow-sm border-0 rounded"> | <div class="card shadow-sm border-0 rounded"> | ||||
| <div | <div | ||||
| class="card-header d-flex justify-content-between align-items-center p-3 bg-primary text-white" | |||||
| class="card-header d-flex justify-content-between align-items-center p-3 " | |||||
| dir="rtl" | dir="rtl" | ||||
| > | > | ||||
| <div class="d-flex align-items-center"> | <div class="d-flex align-items-center"> | ||||
| @@ -188,7 +188,7 @@ export default { | |||||
| <tr> | <tr> | ||||
| <th>عکس</th> | <th>عکس</th> | ||||
| <th>عنوان</th> | <th>عنوان</th> | ||||
| <td>اسلاگ</td> | |||||
| <td>کلمه کلیدی</td> | |||||
| <th>تاریخ ایجاد</th> | <th>تاریخ ایجاد</th> | ||||
| <th>عملیات</th> | <th>عملیات</th> | ||||
| </tr> | </tr> | ||||
| @@ -28,12 +28,12 @@ | |||||
| <!-- Second Input Field (Slug) --> | <!-- Second Input Field (Slug) --> | ||||
| <BCol md="6"> | <BCol md="6"> | ||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label class="form-label">اسلاگ</label> | |||||
| <label class="form-label">کلمه کلیدی</label> | |||||
| <input | <input | ||||
| type="text" | type="text" | ||||
| v-model="slug" | v-model="slug" | ||||
| class="form-control" | class="form-control" | ||||
| placeholder="اسلاگ بلاگ" | |||||
| placeholder="کلمه کلیدی بلاگ" | |||||
| :class="{ 'is-invalid': errors.slug }" | :class="{ 'is-invalid': errors.slug }" | ||||
| @input="clearError('slug')" | @input="clearError('slug')" | ||||
| /> | /> | ||||
| @@ -94,8 +94,9 @@ | |||||
| v-model="blogCat" | v-model="blogCat" | ||||
| class="form-control" | class="form-control" | ||||
| @input="clearError('blogCat')" | @input="clearError('blogCat')" | ||||
| placeholder="انتخاب دسته بلاگ" | |||||
| > | > | ||||
| <option value="" disabled selected>انتخاب دسته بلاگ</option> | |||||
| <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | ||||
| {{ cat.title }} | {{ cat.title }} | ||||
| </option> | </option> | ||||
| @@ -161,7 +162,7 @@ | |||||
| </template> | </template> | ||||
| <script> | <script> | ||||
| import Swal from "sweetalert2"; | |||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| @@ -220,7 +221,7 @@ export default { | |||||
| const validateForm = () => { | const validateForm = () => { | ||||
| errors.value = {}; | errors.value = {}; | ||||
| if (!title.value) errors.value.title = "وارد کردن عنوان بلاگ الزامی است"; | if (!title.value) errors.value.title = "وارد کردن عنوان بلاگ الزامی است"; | ||||
| if (!slug.value) errors.value.slug = "وارد کردن اسلاگ بلاگ ضروری می باشد"; | |||||
| if (!slug.value) errors.value.slug = "وارد کردن کلمه کلیدی بلاگ ضروری می باشد"; | |||||
| if (!summary.value) | if (!summary.value) | ||||
| errors.value.summary = "وارد کردن خلاصه بلاگ ضروری می باشد"; | errors.value.summary = "وارد کردن خلاصه بلاگ ضروری می باشد"; | ||||
| if (!blogCat.value) | if (!blogCat.value) | ||||
| @@ -229,7 +230,7 @@ export default { | |||||
| errors.value.author = "وارد کردن نویسنده بلاگ ضروری می باشد"; | errors.value.author = "وارد کردن نویسنده بلاگ ضروری می باشد"; | ||||
| if (!editorContent.value) | if (!editorContent.value) | ||||
| errors.value.editorContent = "وارد کردن محتوای بلاگ ضروری می باشد"; | errors.value.editorContent = "وارد کردن محتوای بلاگ ضروری می باشد"; | ||||
| if (!image.value) errors.value.image = "وارد کردن عکس بلاگ ضروری می باشد"; | |||||
| if (!imagePreview.value) errors.value.image = "وارد کردن عکس بلاگ ضروری می باشد"; | |||||
| return Object.keys(errors.value).length === 0; | return Object.keys(errors.value).length === 0; | ||||
| }; | }; | ||||
| @@ -313,12 +314,9 @@ export default { | |||||
| .catch((error) => { | .catch((error) => { | ||||
| console.error(error); | console.error(error); | ||||
| Swal.fire({ | |||||
| icon: "error", | |||||
| title: "خطا", | |||||
| text: `!ویرایش بلاگ با مشکل مواجه شد: ${ | |||||
| error.response?.data?.message || "خطای غیرمنتظره رخ داد." | |||||
| }`, | |||||
| toast.error("!مشکلی در ویرایش باگ پیش آمد", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | }); | ||||
| }) | }) | ||||
| .finally(() => { | .finally(() => { | ||||
| @@ -92,7 +92,7 @@ export default { | |||||
| }; | }; | ||||
| const deleteBrand = (id, title) => { | const deleteBrand = (id, title) => { | ||||
| Swal.fire({ | Swal.fire({ | ||||
| title: `می خواهید برند ${title} را حذف کنید ؟`, | |||||
| text: `می خواهید برند ${title} را حذف کنید؟`, | |||||
| icon: "warning", | icon: "warning", | ||||
| showCancelButton: true, | showCancelButton: true, | ||||
| confirmButtonColor: "#3085d6", | confirmButtonColor: "#3085d6", | ||||
| @@ -190,7 +190,7 @@ export default { | |||||
| <div class="col-md-12"> | <div class="col-md-12"> | ||||
| <div class="card shadow-sm border-0 rounded"> | <div class="card shadow-sm border-0 rounded"> | ||||
| <div | <div | ||||
| class="card-header d-flex justify-content-between align-items-center text-white" | |||||
| class="card-header d-flex justify-content-between align-items-center" | |||||
| dir="rtl" | dir="rtl" | ||||
| > | > | ||||
| <div class="d-flex align-items-center"> | <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 Layout from "@/layout/custom.vue"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| import moment from "jalali-moment"; | import moment from "jalali-moment"; | ||||
| import { onMounted, ref, watch , computed } from "vue"; | |||||
| import { onMounted, ref, watch, computed } from "vue"; | |||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| import Swal from "sweetalert2"; | import Swal from "sweetalert2"; | ||||
| @@ -18,6 +18,7 @@ export default { | |||||
| editCat, | editCat, | ||||
| }, | }, | ||||
| setup() { | setup() { | ||||
| const catIcon = ref(); | |||||
| const searchPage = ref(); | const searchPage = ref(); | ||||
| const currentPage = ref(1); | const currentPage = ref(1); | ||||
| const totalPages = ref(1); | const totalPages = ref(1); | ||||
| @@ -41,9 +42,11 @@ export default { | |||||
| }); | }); | ||||
| const getCats = () => { | const getCats = () => { | ||||
| filterLoading.value = true; | filterLoading.value = true; | ||||
| ApiServiece.get(`admin/categories?title=${searchQuery.value}&paginate=${ | |||||
| ApiServiece.get( | |||||
| `admin/categories?title=${searchQuery.value}&paginate=${ | |||||
| paginate.value || 10 | paginate.value || 10 | ||||
| }&page=${page.value || 1}`) | |||||
| }&page=${page.value || 1}` | |||||
| ) | |||||
| .then((resp) => { | .then((resp) => { | ||||
| filterLoading.value = false; | filterLoading.value = false; | ||||
| cats.value = resp.data.data.data; | cats.value = resp.data.data.data; | ||||
| @@ -107,7 +110,7 @@ export default { | |||||
| }; | }; | ||||
| const deleteCat = (id, title) => { | const deleteCat = (id, title) => { | ||||
| Swal.fire({ | Swal.fire({ | ||||
| title: `می خواهید دسته ${title} را حذف کنید ؟`, | |||||
| text: `می خواهید دسته ${title} را حذف کنید؟`, | |||||
| icon: "warning", | icon: "warning", | ||||
| showCancelButton: true, | showCancelButton: true, | ||||
| confirmButtonColor: "#3085d6", | confirmButtonColor: "#3085d6", | ||||
| @@ -122,7 +125,7 @@ export default { | |||||
| position: "top-right", | position: "top-right", | ||||
| autoClose: 3000, | autoClose: 3000, | ||||
| }); | }); | ||||
| cats.value = cats.value.filter((cat) => cat.id !== id); | |||||
| getCats(); | |||||
| }) | }) | ||||
| .catch((err) => { | .catch((err) => { | ||||
| console.log(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; | catId.value = id; | ||||
| catTitle.value = title; | catTitle.value = title; | ||||
| catDescription.value = desc; | catDescription.value = desc; | ||||
| catParent.value = parent; | catParent.value = parent; | ||||
| catImage.value = img; | catImage.value = img; | ||||
| catIcon.value = icon; | |||||
| }; | }; | ||||
| const descriptionModal = (desc) => { | const descriptionModal = (desc) => { | ||||
| @@ -151,6 +155,38 @@ export default { | |||||
| getCats(); | 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(() => { | onMounted(() => { | ||||
| getCats(); | getCats(); | ||||
| }); | }); | ||||
| @@ -176,6 +212,8 @@ export default { | |||||
| handlePageInput, | handlePageInput, | ||||
| searchPage, | searchPage, | ||||
| visiblePages, | visiblePages, | ||||
| catIcon, | |||||
| restoreCat, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -186,7 +224,7 @@ export default { | |||||
| <div class="col-md-12"> | <div class="col-md-12"> | ||||
| <div class="card shadow-sm border-0 rounded"> | <div class="card shadow-sm border-0 rounded"> | ||||
| <div | <div | ||||
| class="card-header d-flex justify-content-between align-items-center p-3 bg-primary text-white" | |||||
| class="card-header d-flex justify-content-between align-items-center p-3" | |||||
| dir="rtl" | dir="rtl" | ||||
| > | > | ||||
| <div class="d-flex align-items-center"> | <div class="d-flex align-items-center"> | ||||
| @@ -256,7 +294,8 @@ export default { | |||||
| cat?.title, | cat?.title, | ||||
| cat.description, | cat.description, | ||||
| cat?.parent?.id, | cat?.parent?.id, | ||||
| cat?.image | |||||
| cat?.image, | |||||
| cat?.icon | |||||
| ) | ) | ||||
| " | " | ||||
| data-bs-toggle="modal" | data-bs-toggle="modal" | ||||
| @@ -266,11 +305,19 @@ export default { | |||||
| ویرایش | ویرایش | ||||
| </button> | </button> | ||||
| <button | <button | ||||
| v-if="!cat.deleted_at" | |||||
| @click="deleteCat(cat.id, cat.title)" | @click="deleteCat(cat.id, cat.title)" | ||||
| class="btn btn-sm btn-outline-danger" | class="btn btn-sm btn-outline-danger" | ||||
| > | > | ||||
| حذف | حذف | ||||
| </button> | </button> | ||||
| <button | |||||
| v-else | |||||
| @click="restoreCat(cat?.id, cat?.title)" | |||||
| class="btn btn-sm btn-outline-success" | |||||
| > | |||||
| بازیابی | |||||
| </button> | |||||
| </td> | </td> | ||||
| </tr> | </tr> | ||||
| </tbody> | </tbody> | ||||
| @@ -291,82 +338,82 @@ export default { | |||||
| :parent="catParent" | :parent="catParent" | ||||
| :image="catImage" | :image="catImage" | ||||
| :allParents="cats" | :allParents="cats" | ||||
| :icon="catIcon" | |||||
| @cat-updated="handleCatUpdated()" | @cat-updated="handleCatUpdated()" | ||||
| /> | /> | ||||
| <showDescription :desc="catDescription" /> | <showDescription :desc="catDescription" /> | ||||
| </BRow> | </BRow> | ||||
| <BRow> | <BRow> | ||||
| <BCol sm="12"> | |||||
| <div class="d-flex justify-content-center"> | |||||
| <nav aria-label="Page navigation"> | |||||
| <ul class="pagination"> | |||||
| <li class="page-item" :class="{ disabled: currentPage === 1 }"> | |||||
| <span class="page-link" @click="prevPage">قبلی</span> | |||||
| </li> | |||||
| <BCol sm="12"> | |||||
| <div class="d-flex justify-content-center"> | |||||
| <nav aria-label="Page navigation"> | |||||
| <ul class="pagination"> | |||||
| <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> | </BRow> | ||||
| </Layout> | </Layout> | ||||
| </template> | </template> | ||||
| @@ -80,7 +80,7 @@ export default { | |||||
| const deleteDiscount = (id, title) => { | const deleteDiscount = (id, title) => { | ||||
| Swal.fire({ | Swal.fire({ | ||||
| text: `می خواهید تخفیف ${title} را حذف کنید ؟`, | |||||
| text: `می خواهید تخفیف ${title} را حذف کنید؟`, | |||||
| icon: "warning", | icon: "warning", | ||||
| showCancelButton: true, | showCancelButton: true, | ||||
| confirmButtonColor: "#3085d6", | confirmButtonColor: "#3085d6", | ||||
| @@ -125,11 +125,11 @@ export default { | |||||
| const changeCommentStatus = (id, op) => { | const changeCommentStatus = (id, op) => { | ||||
| let text, successMessage, errorMessage; | let text, successMessage, errorMessage; | ||||
| if (op === "confirmed") { | if (op === "confirmed") { | ||||
| text = `آیای می خواهید این نظر را قبول کنید؟`; | |||||
| text = ` می خواهید این نظر را قبول کنید؟`; | |||||
| successMessage = "!نظر با موفقیت قبول شد"; | successMessage = "!نظر با موفقیت قبول شد"; | ||||
| errorMessage = "!مشکلی در تغییر وضعیت نظر ایجاد شد"; | errorMessage = "!مشکلی در تغییر وضعیت نظر ایجاد شد"; | ||||
| } else if (op === "rejected") { | } else if (op === "rejected") { | ||||
| text = `آیای می خواهید این نظر را رد کنید؟`; | |||||
| text = `می خواهید این نظر را رد کنید؟`; | |||||
| successMessage = "!نظر با موفقیت رد شد"; | successMessage = "!نظر با موفقیت رد شد"; | ||||
| errorMessage = "!مشکلی در تغییر وضعیت نظر ایجاد شد"; | errorMessage = "!مشکلی در تغییر وضعیت نظر ایجاد شد"; | ||||
| } | } | ||||
| @@ -197,7 +197,6 @@ export default { | |||||
| deleteDiscount, | deleteDiscount, | ||||
| searchQuery, | searchQuery, | ||||
| filterLoading, | filterLoading, | ||||
| currentPage, | currentPage, | ||||
| totalPages, | totalPages, | ||||
| nextPage, | nextPage, | ||||
| @@ -218,10 +217,10 @@ export default { | |||||
| <div class="col-md-12"> | <div class="col-md-12"> | ||||
| <div class="card shadow-sm border-0 rounded"> | <div class="card shadow-sm border-0 rounded"> | ||||
| <div | <div | ||||
| class="card-header d-flex justify-content-between align-items-center p-3 bg-primary text-white" | |||||
| class="card-header d-flex justify-content-between align-items-center p-3 " | |||||
| dir="rtl" | dir="rtl" | ||||
| > | > | ||||
| <div class="d-flex align-items-center"> | |||||
| <!-- <div class="d-flex align-items-center"> | |||||
| <input | <input | ||||
| v-model="searchQuery" | v-model="searchQuery" | ||||
| type="text" | type="text" | ||||
| @@ -229,13 +228,8 @@ export default { | |||||
| class="form-control form-control-sm d-inline-block me-2" | class="form-control form-control-sm d-inline-block me-2" | ||||
| style="width: 250px; border-radius: 15px" | style="width: 250px; border-radius: 15px" | ||||
| /> | /> | ||||
| <router-link | |||||
| to="/addDiscount" | |||||
| class="btn btn-light text-primary btn-sm px-3" | |||||
| > | |||||
| افزودن تخفیف | |||||
| </router-link> | |||||
| </div> | |||||
| </div> --> | |||||
| </div> | </div> | ||||
| <div v-if="!filterLoading" class="card-body table-border-style p-0"> | <div v-if="!filterLoading" class="card-body table-border-style p-0"> | ||||
| <div class="table-responsive"> | <div class="table-responsive"> | ||||
| @@ -33,10 +33,8 @@ | |||||
| class="form-control" | class="form-control" | ||||
| :class="{ 'is-invalid': errors.discountType }" | :class="{ 'is-invalid': errors.discountType }" | ||||
| @change="clearError('discountType')" | @change="clearError('discountType')" | ||||
| placeholder="انتخاب نوع تخفیف" | |||||
| > | > | ||||
| <option value="" disabled> | |||||
| لطفاً نوع تخفیف را انتخاب کنید | |||||
| </option> | |||||
| <option value="percentage">درصدی</option> | <option value="percentage">درصدی</option> | ||||
| <option value="const">مبلغ ثابت</option> | <option value="const">مبلغ ثابت</option> | ||||
| </select> | </select> | ||||
| @@ -105,10 +103,8 @@ | |||||
| class="form-control" | class="form-control" | ||||
| :class="{ 'is-invalid': errors.whichPart }" | :class="{ 'is-invalid': errors.whichPart }" | ||||
| @select="clearError('whichPart')" | @select="clearError('whichPart')" | ||||
| placeholder="انتخاب محل اعمال تخفبف" | |||||
| > | > | ||||
| <option value="" disabled> | |||||
| لطفاً اعمال تخفیف را انتخاب کنید | |||||
| </option> | |||||
| <option value="cat">دسته</option> | <option value="cat">دسته</option> | ||||
| <option value="product">محصول</option> | <option value="product">محصول</option> | ||||
| <option value="both">هردو</option> | <option value="both">هردو</option> | ||||
| @@ -127,8 +123,8 @@ | |||||
| v-model="selectedCat" | v-model="selectedCat" | ||||
| class="form-control" | class="form-control" | ||||
| @select="clearError('selectedCat')" | @select="clearError('selectedCat')" | ||||
| placeholder="انتخاب دسته" | |||||
| > | > | ||||
| <option value="" disabled selected>انتخاب دسته</option> | |||||
| <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | ||||
| {{ cat.title }} | {{ cat.title }} | ||||
| </option> | </option> | ||||
| @@ -141,26 +137,19 @@ | |||||
| <BCol | <BCol | ||||
| v-if="whichPart === 'product' || whichPart === 'both'" | 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"> | <small v-if="errors.selectedProduct" class="text-danger"> | ||||
| {{ errors.selectedProduct }} | {{ errors.selectedProduct }} | ||||
| </small> | </small> | ||||
| @@ -171,7 +160,7 @@ | |||||
| <label class="form-label"> تاریخ اعمال تخفیف </label> | <label class="form-label"> تاریخ اعمال تخفیف </label> | ||||
| <DatePicker | <DatePicker | ||||
| format="YYYY/MM/DD HH:mm:ss" | |||||
| :format="'jYYYY/jMM/jDD HH:mm:ss'" | |||||
| type="datetime" | type="datetime" | ||||
| v-model="startDate" | v-model="startDate" | ||||
| @input="handleStartDateInput" | @input="handleStartDateInput" | ||||
| @@ -187,7 +176,7 @@ | |||||
| <label class="form-label"> تاریخ انقضای تخفیف </label> | <label class="form-label"> تاریخ انقضای تخفیف </label> | ||||
| <DatePicker | <DatePicker | ||||
| format="YYYY/MM/DD HH:mm:ss" | |||||
| :format="'jYYYY/jMM/jDD HH:mm:ss'" | |||||
| type="datetime" | type="datetime" | ||||
| v-model="expire" | v-model="expire" | ||||
| @input="handleExpireDateInput" | @input="handleExpireDateInput" | ||||
| @@ -209,7 +198,7 @@ | |||||
| :disabled="loading" | :disabled="loading" | ||||
| > | > | ||||
| <span v-if="loading"> | <span v-if="loading"> | ||||
| <i class="fa fa-spinner fa-spin"></i> بارگذاری... | |||||
| <i class="fa fa-spinner fa-spin"></i> ایجاد... | |||||
| </span> | </span> | ||||
| <span v-else>ایجاد</span> | <span v-else>ایجاد</span> | ||||
| </button> | </button> | ||||
| @@ -223,11 +212,12 @@ | |||||
| </template> | </template> | ||||
| <script> | <script> | ||||
| import Select2 from "vue3-select2-component"; | |||||
| import moment from "moment"; | import moment from "moment"; | ||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| import { ref, onMounted } from "vue"; | |||||
| import { ref, onMounted, computed } from "vue"; | |||||
| import Layout from "@/layout/custom.vue"; | import Layout from "@/layout/custom.vue"; | ||||
| import DatePicker from "vue3-persian-datetime-picker"; | import DatePicker from "vue3-persian-datetime-picker"; | ||||
| @@ -236,6 +226,7 @@ export default { | |||||
| components: { | components: { | ||||
| Layout, | Layout, | ||||
| DatePicker, | DatePicker, | ||||
| Select2, | |||||
| }, | }, | ||||
| setup() { | setup() { | ||||
| const title = ref(); | const title = ref(); | ||||
| @@ -275,18 +266,32 @@ export default { | |||||
| const handleStartDateInput = () => { | const handleStartDateInput = () => { | ||||
| if (startDate.value) { | 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 = () => { | const handleExpireDateInput = () => { | ||||
| if (expire.value) { | 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 = () => { | const validateForm = () => { | ||||
| errors.value = {}; | errors.value = {}; | ||||
| if (!title.value) errors.value.title = "وارد کردن عنوان تخفیف الزامی است"; | if (!title.value) errors.value.title = "وارد کردن عنوان تخفیف الزامی است"; | ||||
| @@ -348,6 +353,7 @@ export default { | |||||
| ApiServiece.post(`/admin/discounts`, formData) | ApiServiece.post(`/admin/discounts`, formData) | ||||
| .then((resp) => { | .then((resp) => { | ||||
| loading.value = false; | |||||
| toast.success("!تخفیف با موفقیت اضافه شد", { | toast.success("!تخفیف با موفقیت اضافه شد", { | ||||
| position: "top-right", | position: "top-right", | ||||
| autoClose: 1000, | autoClose: 1000, | ||||
| @@ -355,6 +361,7 @@ export default { | |||||
| console.log(resp); | console.log(resp); | ||||
| }) | }) | ||||
| .catch((error) => { | .catch((error) => { | ||||
| loading.value = false; | |||||
| console.log(error.response.message); | console.log(error.response.message); | ||||
| toast.error(`${error.response.data.message}`, { | toast.error(`${error.response.data.message}`, { | ||||
| position: "top-right", | position: "top-right", | ||||
| @@ -381,6 +388,8 @@ export default { | |||||
| handleExpireDateInput, | handleExpireDateInput, | ||||
| whichPart, | whichPart, | ||||
| clearError, | clearError, | ||||
| loading, | |||||
| formattedProducts, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -19,7 +19,7 @@ export default { | |||||
| const searchPage = ref(); | const searchPage = ref(); | ||||
| const currentPage = ref(1); | const currentPage = ref(1); | ||||
| const totalPages = ref(1); | const totalPages = ref(1); | ||||
| const paginate = ref(5); | |||||
| const paginate = ref(20); | |||||
| const page = ref(1); | const page = ref(1); | ||||
| const filterLoading = ref(false); | const filterLoading = ref(false); | ||||
| @@ -76,7 +76,7 @@ export default { | |||||
| const deleteDiscount = (id, title) => { | const deleteDiscount = (id, title) => { | ||||
| Swal.fire({ | Swal.fire({ | ||||
| text: `می خواهید تخفیف ${title} را حذف کنید ؟`, | |||||
| text: `می خواهید تخفیف ${title} را حذف کنید؟`, | |||||
| icon: "warning", | icon: "warning", | ||||
| showCancelButton: true, | showCancelButton: true, | ||||
| confirmButtonColor: "#3085d6", | confirmButtonColor: "#3085d6", | ||||
| @@ -167,7 +167,7 @@ export default { | |||||
| <div class="col-md-12"> | <div class="col-md-12"> | ||||
| <div class="card shadow-sm border-0 rounded"> | <div class="card shadow-sm border-0 rounded"> | ||||
| <div | <div | ||||
| class="card-header d-flex justify-content-between align-items-center text-white" | |||||
| class="card-header d-flex justify-content-between align-items-center" | |||||
| dir="rtl" | dir="rtl" | ||||
| > | > | ||||
| <div class="d-flex align-items-center"> | <div class="d-flex align-items-center"> | ||||
| @@ -180,7 +180,7 @@ export default { | |||||
| /> | /> | ||||
| <router-link | <router-link | ||||
| to="/addDiscount" | to="/addDiscount" | ||||
| class="btn btn-light text-primary btn-sm px-3" | |||||
| class="btn btn-light text-primary btn-sm px-3 " | |||||
| > | > | ||||
| افزودن تخفیف | افزودن تخفیف | ||||
| </router-link> | </router-link> | ||||
| @@ -203,7 +203,8 @@ export default { | |||||
| <tbody> | <tbody> | ||||
| <tr v-for="discount in discounts" :key="discount.id"> | <tr v-for="discount in discounts" :key="discount.id"> | ||||
| <td>{{ discount?.title }}</td> | <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.amount }}</td> | ||||
| <td>{{ discount.min_order }}</td> | <td>{{ discount.min_order }}</td> | ||||
| <td>{{ convertToJalali(discount?.starts_at) }}</td> | <td>{{ convertToJalali(discount?.starts_at) }}</td> | ||||
| @@ -4,7 +4,7 @@ | |||||
| <BCol sm="12"> | <BCol sm="12"> | ||||
| <BCard no-body> | <BCard no-body> | ||||
| <BCardHeader> | <BCardHeader> | ||||
| <h5>ایجاد تخفیف</h5> | |||||
| <h5>ویرایش تخفیف</h5> | |||||
| </BCardHeader> | </BCardHeader> | ||||
| <BCardBody> | <BCardBody> | ||||
| <BRow class="g-3"> | <BRow class="g-3"> | ||||
| @@ -34,10 +34,8 @@ | |||||
| class="form-control" | class="form-control" | ||||
| :class="{ 'is-invalid': errors.discountType }" | :class="{ 'is-invalid': errors.discountType }" | ||||
| @change="clearError('discountType')" | @change="clearError('discountType')" | ||||
| placeholder="انتخاب نوع اعمال تخفیف" | |||||
| > | > | ||||
| <option value="" disabled> | |||||
| لطفاً نوع تخفیف را انتخاب کنید | |||||
| </option> | |||||
| <option value="percentage">درصدی</option> | <option value="percentage">درصدی</option> | ||||
| <option value="const">مبلغ ثابت</option> | <option value="const">مبلغ ثابت</option> | ||||
| </select> | </select> | ||||
| @@ -106,10 +104,8 @@ | |||||
| class="form-control" | class="form-control" | ||||
| :class="{ 'is-invalid': errors.whichPart }" | :class="{ 'is-invalid': errors.whichPart }" | ||||
| @select="clearError('whichPart')" | @select="clearError('whichPart')" | ||||
| placeholder="انتخاب محل اعمل تخفیف" | |||||
| > | > | ||||
| <option value="" disabled> | |||||
| لطفاً اعمال تخفیف را انتخاب کنید | |||||
| </option> | |||||
| <option value="cat">دسته</option> | <option value="cat">دسته</option> | ||||
| <option value="product">محصول</option> | <option value="product">محصول</option> | ||||
| <option value="both">هردو</option> | <option value="both">هردو</option> | ||||
| @@ -120,10 +116,7 @@ | |||||
| </small> | </small> | ||||
| </BCol> | </BCol> | ||||
| <BCol | |||||
| v-if="whichPart === 'cat' || whichPart === 'both'" | |||||
| md="6" | |||||
| > | |||||
| <BCol v-if="whichPart === 'cat' || whichPart === 'both'" md="6"> | |||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label class="form-label">دسته</label> | <label class="form-label">دسته</label> | ||||
| <select | <select | ||||
| @@ -131,8 +124,8 @@ | |||||
| v-model="selectedCat" | v-model="selectedCat" | ||||
| class="form-control" | class="form-control" | ||||
| @select="clearError('selectedCat')" | @select="clearError('selectedCat')" | ||||
| placeholder="انتخاب دسته" | |||||
| > | > | ||||
| <option value="" disabled selected>انتخاب دسته</option> | |||||
| <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | ||||
| {{ cat.title }} | {{ cat.title }} | ||||
| </option> | </option> | ||||
| @@ -154,8 +147,8 @@ | |||||
| v-model="selectedProduct" | v-model="selectedProduct" | ||||
| class="form-control" | class="form-control" | ||||
| @select="clearError('selectedProduct')" | @select="clearError('selectedProduct')" | ||||
| placeholder="انتخاب محصول" | |||||
| > | > | ||||
| <option value="" disabled selected>انتخاب محصول</option> | |||||
| <option | <option | ||||
| v-for="product in products" | v-for="product in products" | ||||
| :key="product.id" | :key="product.id" | ||||
| @@ -175,7 +168,7 @@ | |||||
| <label class="form-label"> تاریخ اعمال تخفیف </label> | <label class="form-label"> تاریخ اعمال تخفیف </label> | ||||
| <DatePicker | <DatePicker | ||||
| format="YYYY/MM/DD HH:mm:ss" | |||||
| :format="'jYYYY/jMM/jDD HH:mm:ss'" | |||||
| type="datetime" | type="datetime" | ||||
| v-model="startDate" | v-model="startDate" | ||||
| @input="handleStartDateInput" | @input="handleStartDateInput" | ||||
| @@ -191,7 +184,7 @@ | |||||
| <label class="form-label"> تاریخ انقضای تخفیف </label> | <label class="form-label"> تاریخ انقضای تخفیف </label> | ||||
| <DatePicker | <DatePicker | ||||
| format="YYYY/MM/DD HH:mm:ss" | |||||
| :format="'jYYYY/jMM/jDD HH:mm:ss'" | |||||
| type="datetime" | type="datetime" | ||||
| v-model="expire" | v-model="expire" | ||||
| @input="handleExpireDateInput" | @input="handleExpireDateInput" | ||||
| @@ -213,9 +206,9 @@ | |||||
| :disabled="loading" | :disabled="loading" | ||||
| > | > | ||||
| <span v-if="loading"> | <span v-if="loading"> | ||||
| <i class="fa fa-spinner fa-spin"></i> بارگذاری... | |||||
| <i class="fa fa-spinner fa-spin"></i> ویرایش... | |||||
| </span> | </span> | ||||
| <span v-else>ایجاد</span> | |||||
| <span v-else>ویرایش</span> | |||||
| </button> | </button> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -290,13 +283,13 @@ export default { | |||||
| minOrder.value = discount.value.min_order; | minOrder.value = discount.value.min_order; | ||||
| selectedCat.value = discount.value.category_id; | selectedCat.value = discount.value.category_id; | ||||
| if (discount.value.category_id) { | if (discount.value.category_id) { | ||||
| whichPart.value === "cat"; | |||||
| whichPart.value = "cat"; | |||||
| } | } | ||||
| console.log(discount.value.product_id); | console.log(discount.value.product_id); | ||||
| selectedProduct.value = discount.value.product_id; | selectedProduct.value = discount.value.product_id; | ||||
| if (discount.value.product_id) { | if (discount.value.product_id) { | ||||
| whichPart.value === "product"; | |||||
| whichPart.value = "product"; | |||||
| } | } | ||||
| startDate.value = discount.value.starts_at; | startDate.value = discount.value.starts_at; | ||||
| expire.value = discount.value.expires_at; | expire.value = discount.value.expires_at; | ||||
| @@ -309,16 +302,23 @@ export default { | |||||
| const handleStartDateInput = () => { | const handleStartDateInput = () => { | ||||
| if (startDate.value) { | 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 = () => { | const handleExpireDateInput = () => { | ||||
| if (expire.value) { | 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 = () => { | const validateForm = () => { | ||||
| @@ -330,12 +330,12 @@ export default { | |||||
| errors.value.amount = "وارد کردن مقدار تخفیف الزامی می باشد"; | errors.value.amount = "وارد کردن مقدار تخفیف الزامی می باشد"; | ||||
| if (!minOrder.value) | if (!minOrder.value) | ||||
| errors.value.minOrder = "وارد کردن حداقل میزان تخفیف الزامی می باشد"; | errors.value.minOrder = "وارد کردن حداقل میزان تخفیف الزامی می باشد"; | ||||
| if ( | |||||
| if ( | |||||
| !selectedCat.value && | !selectedCat.value && | ||||
| (whichPart.value === "cat" || whichPart.value === "both") | (whichPart.value === "cat" || whichPart.value === "both") | ||||
| ) | ) | ||||
| errors.value.selectedCat = "انتخاب دسته برای تخفیف الزامی می باشد"; | errors.value.selectedCat = "انتخاب دسته برای تخفیف الزامی می باشد"; | ||||
| if ( | |||||
| if ( | |||||
| !selectedProduct.value && | !selectedProduct.value && | ||||
| (whichPart.value === "product" || whichPart.value === "both") | (whichPart.value === "product" || whichPart.value === "both") | ||||
| ) | ) | ||||
| @@ -369,11 +369,11 @@ export default { | |||||
| formData.append("type", discountType.value); | formData.append("type", discountType.value); | ||||
| formData.append("amount", amount.value); | formData.append("amount", amount.value); | ||||
| formData.append("min_order", minOrder.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); | 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); | formData.append("product_id", selectedProduct.value); | ||||
| } | } | ||||
| @@ -383,6 +383,7 @@ export default { | |||||
| ApiServiece.post(`/admin/discounts`, formData) | ApiServiece.post(`/admin/discounts`, formData) | ||||
| .then((resp) => { | .then((resp) => { | ||||
| loading.value = false; | |||||
| toast.success("!تخفیف با موفقیت اضافه شد", { | toast.success("!تخفیف با موفقیت اضافه شد", { | ||||
| position: "top-right", | position: "top-right", | ||||
| autoClose: 1000, | autoClose: 1000, | ||||
| @@ -390,6 +391,7 @@ export default { | |||||
| console.log(resp); | console.log(resp); | ||||
| }) | }) | ||||
| .catch((error) => { | .catch((error) => { | ||||
| loading.value = false; | |||||
| console.log(error.response.message); | console.log(error.response.message); | ||||
| toast.error(`${error.response.data.message}`, { | toast.error(`${error.response.data.message}`, { | ||||
| position: "top-right", | position: "top-right", | ||||
| @@ -416,6 +418,7 @@ export default { | |||||
| handleExpireDateInput, | handleExpireDateInput, | ||||
| whichPart, | whichPart, | ||||
| clearError, | clearError, | ||||
| loading, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -27,7 +27,7 @@ export default { | |||||
| faqs.value = resp.data.data; | faqs.value = resp.data.data; | ||||
| text.value = faqs.value.text; | text.value = faqs.value.text; | ||||
| status.value = faqs.value.status; | status.value = faqs.value.status; | ||||
| answerText.value = faqs.value?.children[0]?.text; | |||||
| answerText.value = faqs.value?.answer?.text; | |||||
| console.log(resp.data.data); | console.log(resp.data.data); | ||||
| if(faqs.value?.children.length > 0){ | if(faqs.value?.children.length > 0){ | ||||
| isAnswerExist.value = true | isAnswerExist.value = true | ||||
| @@ -79,7 +79,7 @@ export default { | |||||
| const deleteDiscount = (id, title) => { | const deleteDiscount = (id, title) => { | ||||
| Swal.fire({ | Swal.fire({ | ||||
| text: `می خواهید تخفیف ${title} را حذف کنید ؟`, | |||||
| text: `می خواهید تخفیف ${title} را حذف کنید؟`, | |||||
| icon: "warning", | icon: "warning", | ||||
| showCancelButton: true, | showCancelButton: true, | ||||
| confirmButtonColor: "#3085d6", | confirmButtonColor: "#3085d6", | ||||
| @@ -170,10 +170,10 @@ export default { | |||||
| <div class="col-md-12"> | <div class="col-md-12"> | ||||
| <div class="card shadow-sm border-0 rounded"> | <div class="card shadow-sm border-0 rounded"> | ||||
| <div | <div | ||||
| class="card-header d-flex justify-content-between align-items-center p-3 bg-primary text-white" | |||||
| class="card-header d-flex justify-content-between align-items-center p-3 " | |||||
| dir="rtl" | dir="rtl" | ||||
| > | > | ||||
| <div class="d-flex align-items-center"> | |||||
| <!-- <div class="d-flex align-items-center"> | |||||
| <input | <input | ||||
| v-model="searchQuery" | v-model="searchQuery" | ||||
| type="text" | type="text" | ||||
| @@ -181,7 +181,7 @@ export default { | |||||
| class="form-control form-control-sm d-inline-block me-2" | class="form-control form-control-sm d-inline-block me-2" | ||||
| style="width: 250px; border-radius: 15px" | style="width: 250px; border-radius: 15px" | ||||
| /> | /> | ||||
| </div> | |||||
| </div> --> | |||||
| </div> | </div> | ||||
| <div v-if="!filterLoading" class="card-body table-border-style p-0"> | <div v-if="!filterLoading" class="card-body table-border-style p-0"> | ||||
| <div class="table-responsive"> | <div class="table-responsive"> | ||||
| @@ -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] || "نامشخص"; | return statusLabels[status] || "نامشخص"; | ||||
| }; | }; | ||||
| const deleteOrder = (id) => { | const deleteOrder = (id) => { | ||||
| Swal.fire({ | Swal.fire({ | ||||
| text: `می خواهید سفارش ${id} را حذف کنید ؟`, | |||||
| text: `می خواهید سفارش ${id} را حذف کنید؟`, | |||||
| icon: "warning", | icon: "warning", | ||||
| showCancelButton: true, | showCancelButton: true, | ||||
| confirmButtonColor: "#3085d6", | 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() { | function handlePageInput() { | ||||
| if (searchPage.value < 1) { | if (searchPage.value < 1) { | ||||
| searchPage.value = 1; | searchPage.value = 1; | ||||
| @@ -168,11 +205,10 @@ export default { | |||||
| return { | return { | ||||
| orders, | orders, | ||||
| convertToJalali, | convertToJalali, | ||||
| deleteOrder, | deleteOrder, | ||||
| searchQuery, | searchQuery, | ||||
| filterLoading, | filterLoading, | ||||
| changeStatus, | |||||
| currentPage, | currentPage, | ||||
| totalPages, | totalPages, | ||||
| nextPage, | nextPage, | ||||
| @@ -193,7 +229,7 @@ export default { | |||||
| <div class="col-md-12"> | <div class="col-md-12"> | ||||
| <div class="card shadow-sm border-0 rounded"> | <div class="card shadow-sm border-0 rounded"> | ||||
| <div | <div | ||||
| class="card-header d-flex justify-content-between align-items-center p-3 bg-primary text-white" | |||||
| class="card-header d-flex justify-content-between align-items-center p-3" | |||||
| dir="rtl" | dir="rtl" | ||||
| > | > | ||||
| <div class="d-flex align-items-center"> | <div class="d-flex align-items-center"> | ||||
| @@ -238,11 +274,95 @@ export default { | |||||
| مشاهده | مشاهده | ||||
| </router-link> | </router-link> | ||||
| <button | <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)" | @click="deleteOrder(order?.id)" | ||||
| class="btn btn-sm btn-outline-danger" | class="btn btn-sm btn-outline-danger" | ||||
| > | > | ||||
| حذف | حذف | ||||
| </button> | |||||
| </button> --> | |||||
| </td> | </td> | ||||
| </tr> | </tr> | ||||
| </tbody> | </tbody> | ||||
| @@ -454,4 +574,7 @@ export default { | |||||
| .badge-canceled { | .badge-canceled { | ||||
| background-color: #6c757d; | background-color: #6c757d; | ||||
| } | } | ||||
| .dropdown-item { | |||||
| cursor: pointer; | |||||
| } | |||||
| </style> | </style> | ||||
| @@ -3,9 +3,6 @@ import Layout from "@/layout/custom.vue"; | |||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| import { useRoute } from "vue-router"; | import { useRoute } from "vue-router"; | ||||
| import { onMounted, ref } from "vue"; | import { onMounted, ref } from "vue"; | ||||
| import { toast } from "vue3-toastify"; | |||||
| import "vue3-toastify/dist/index.css"; | |||||
| import Swal from "sweetalert2"; | |||||
| export default { | export default { | ||||
| name: "OrderDetails", | 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(() => { | onMounted(() => { | ||||
| getOrder(); | getOrder(); | ||||
| }); | }); | ||||
| @@ -102,7 +106,10 @@ export default { | |||||
| getStatusClass, | getStatusClass, | ||||
| formatDate, | formatDate, | ||||
| getStatusLabel, | getStatusLabel, | ||||
| changeStatus, | |||||
| updateShippedCount, | |||||
| updateNote, | |||||
| updateStatus, | |||||
| formatWithCommas | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -117,128 +124,81 @@ export default { | |||||
| class="d-flex justify-content-between align-items-center" | class="d-flex justify-content-between align-items-center" | ||||
| > | > | ||||
| <h5 class="mb-0">جزئیات سفارش</h5> | <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> | </BCardHeader> | ||||
| <BCardBody v-if="order"> | <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> | <h6 class="mt-4 mb-3">آیتم های سفارش</h6> | ||||
| <div v-if="order.order_items.length > 0"> | <div v-if="order.order_items.length > 0"> | ||||
| <BTable | <BTable | ||||
| hover | hover | ||||
| class="table-header" | |||||
| bordered | bordered | ||||
| :items="order.order_items" | :items="order.order_items" | ||||
| :fields="[ | :fields="[ | ||||
| { key: 'id', label: 'شناسه آیتم' }, | |||||
| { key: 'count', label: 'تعداد' }, | |||||
| { key: 'title', label: 'عنوان محصول' }, | |||||
| { key: 'count', label: 'تعداد در خواستی ' }, | |||||
| { key: 'price', label: 'قیمت' }, | { key: 'price', label: 'قیمت' }, | ||||
| { key: 'created_at', label: 'تاریخ ثبت' }, | { key: 'created_at', label: 'تاریخ ثبت' }, | ||||
| { key: 'edit_count', label: 'تعداد فرستاده شده' }, | |||||
| { key: 'description', label: 'یادداشت' }, | |||||
| { key: 'status', label: 'ویرایش وضعیت' }, | |||||
| ]" | ]" | ||||
| > | > | ||||
| <!-- Price formatting --> | |||||
| <template #cell(price)="data"> | <template #cell(price)="data"> | ||||
| {{ data.item.price }} تومان | |||||
| {{ formatWithCommas(data.item.price) }} تومان | |||||
| </template> | </template> | ||||
| <template #cell(title)="data"> | |||||
| {{ data.item?.product?.title }} | |||||
| </template> | |||||
| <!-- Created Date formatting --> | |||||
| <template #cell(created_at)="data"> | <template #cell(created_at)="data"> | ||||
| {{ formatDate(data.item.created_at) }} | {{ formatDate(data.item.created_at) }} | ||||
| </template> | </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> | </BTable> | ||||
| </div> | </div> | ||||
| <div v-else class="text-center text-muted my-3"> | <div v-else class="text-center text-muted my-3"> | ||||
| @@ -293,7 +253,14 @@ export default { | |||||
| h5 { | h5 { | ||||
| font-weight: bold; | font-weight: bold; | ||||
| } | } | ||||
| .dropdown-item{ | |||||
| cursor: pointer; | |||||
| .dropdown-item { | |||||
| cursor: pointer; | |||||
| } | |||||
| .table-header { | |||||
| text-align: center; | |||||
| } | |||||
| .selector { | |||||
| padding: 4px; | |||||
| height: 35px; | |||||
| } | } | ||||
| </style> | </style> | ||||
| @@ -8,7 +8,6 @@ | |||||
| </BCardHeader> | </BCardHeader> | ||||
| <BCardBody> | <BCardBody> | ||||
| <BRow class="g-3"> | <BRow class="g-3"> | ||||
| <BCol md="6"> | <BCol md="6"> | ||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label class="form-label">عنوان </label> | <label class="form-label">عنوان </label> | ||||
| @@ -26,15 +25,14 @@ | |||||
| </small> | </small> | ||||
| </BCol> | </BCol> | ||||
| <BCol md="6"> | <BCol md="6"> | ||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label class="form-label">اسلاگ</label> | |||||
| <label class="form-label">کلمه کلیدی</label> | |||||
| <input | <input | ||||
| type="text" | type="text" | ||||
| v-model="slug" | v-model="slug" | ||||
| class="form-control" | class="form-control" | ||||
| placeholder="اسلاگ محصول" | |||||
| placeholder="کلمه کلیدی محصول" | |||||
| :class="{ 'is-invalid': errors.slug }" | :class="{ 'is-invalid': errors.slug }" | ||||
| @input="clearError('slug')" | @input="clearError('slug')" | ||||
| /> | /> | ||||
| @@ -44,7 +42,6 @@ | |||||
| </small> | </small> | ||||
| </BCol> | </BCol> | ||||
| <BCol md="6"> | <BCol md="6"> | ||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label class="form-label">خلاصه</label> | <label class="form-label">خلاصه</label> | ||||
| @@ -149,6 +146,23 @@ | |||||
| </small> | </small> | ||||
| </BCol> | </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"> | <BCol v-if="productType == 1 || productType == 3" md="6"> | ||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label class="form-label">قیمت (تک) </label> | <label class="form-label">قیمت (تک) </label> | ||||
| @@ -247,7 +261,7 @@ | |||||
| </label> | </label> | ||||
| <DatePicker | <DatePicker | ||||
| format="YYYY/MM/DD HH:mm:ss" | |||||
| :format="'jYYYY/jMM/jDD HH:mm:ss'" | |||||
| type="datetime" | type="datetime" | ||||
| v-model="expire" | v-model="expire" | ||||
| @input="handleInput" | @input="handleInput" | ||||
| @@ -266,10 +280,8 @@ | |||||
| v-model="selectedCat" | v-model="selectedCat" | ||||
| class="form-control" | class="form-control" | ||||
| @change="clearError('selectedCat')" | @change="clearError('selectedCat')" | ||||
| placeholder="انتخاب دسته محصول" | |||||
| > | > | ||||
| <option value="" disabled selected> | |||||
| انتخاب دسته محصول | |||||
| </option> | |||||
| <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | ||||
| {{ cat.title }} | {{ cat.title }} | ||||
| </option> | </option> | ||||
| @@ -288,10 +300,8 @@ | |||||
| v-model="selectedBrand" | v-model="selectedBrand" | ||||
| class="form-control" | class="form-control" | ||||
| @change="clearError('selectedBrand')" | @change="clearError('selectedBrand')" | ||||
| placeholder="انتخاب برند محصول" | |||||
| > | > | ||||
| <option value="" disabled selected> | |||||
| انتخاب برند محصول | |||||
| </option> | |||||
| <option | <option | ||||
| v-for="brand in brands" | v-for="brand in brands" | ||||
| :key="brand.id" | :key="brand.id" | ||||
| @@ -311,7 +321,22 @@ | |||||
| <h5 class="mb-0">اضافه کردن ویژگی ها</h5> | <h5 class="mb-0">اضافه کردن ویژگی ها</h5> | ||||
| </div> | </div> | ||||
| <BRow class="g-3 mt-2"> | <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 | <BCol | ||||
| v-for="attrebute in attrebutes" | v-for="attrebute in attrebutes" | ||||
| :key="attrebute.id" | :key="attrebute.id" | ||||
| @@ -323,7 +348,6 @@ | |||||
| <label | <label | ||||
| class="form-label d-flex align-items-center mb-3" | class="form-label d-flex align-items-center mb-3" | ||||
| > | > | ||||
| <BCol md="2" class="d-flex justify-content-center"> | <BCol md="2" class="d-flex justify-content-center"> | ||||
| <BFormCheckbox | <BFormCheckbox | ||||
| :id="'checkbox-id-' + attrebute.id" | :id="'checkbox-id-' + attrebute.id" | ||||
| @@ -332,7 +356,6 @@ | |||||
| /> | /> | ||||
| </BCol> | </BCol> | ||||
| <BCol | <BCol | ||||
| md="5" | md="5" | ||||
| class="d-flex align-items-center" | class="d-flex align-items-center" | ||||
| @@ -341,7 +364,6 @@ | |||||
| {{ attrebute.title }} | {{ attrebute.title }} | ||||
| </BCol> | </BCol> | ||||
| <BCol md="5"> | <BCol md="5"> | ||||
| <input | <input | ||||
| type="number" | type="number" | ||||
| @@ -370,7 +392,82 @@ | |||||
| </BCard> | </BCard> | ||||
| <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 | <div | ||||
| class="card-header text-center p-4" | class="card-header text-center p-4" | ||||
| style="background-color: #f7f7f7" | style="background-color: #f7f7f7" | ||||
| @@ -386,12 +483,10 @@ | |||||
| md="6" | md="6" | ||||
| > | > | ||||
| <div class="form-group position-relative"> | <div class="form-group position-relative"> | ||||
| <label class="form-label mb-2 fw-bold text-secondary" | <label class="form-label mb-2 fw-bold text-secondary" | ||||
| >تصویر محصول</label | >تصویر محصول</label | ||||
| > | > | ||||
| <div class="custom-file"> | <div class="custom-file"> | ||||
| <input | <input | ||||
| type="file" | type="file" | ||||
| @@ -402,7 +497,6 @@ | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| <div v-if="image.preview" class="mt-3 position-relative"> | <div v-if="image.preview" class="mt-3 position-relative"> | ||||
| <img | <img | ||||
| :src="image.preview" | :src="image.preview" | ||||
| @@ -410,7 +504,6 @@ | |||||
| class="img-fluid rounded-3 shadow-lg border Image-Preview" | class="img-fluid rounded-3 shadow-lg border Image-Preview" | ||||
| /> | /> | ||||
| <button | <button | ||||
| type="button" | type="button" | ||||
| @click="removeImage()" | @click="removeImage()" | ||||
| @@ -426,7 +519,6 @@ | |||||
| </small> | </small> | ||||
| </BRow> | </BRow> | ||||
| <div class="text-center mt-4"> | <div class="text-center mt-4"> | ||||
| <button | <button | ||||
| @click="addImage" | @click="addImage" | ||||
| @@ -459,6 +551,11 @@ | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </BCardFooter> | </BCardFooter> | ||||
| <addIdentity :cats="cats" /> | |||||
| <addAttribute | |||||
| :attributeValues="attributeValues" | |||||
| @attribute-updated="handleAttributeUpdated()" | |||||
| /> | |||||
| </BCard> | </BCard> | ||||
| </BCol> | </BCol> | ||||
| </BRow> | </BRow> | ||||
| @@ -466,22 +563,29 @@ | |||||
| </template> | </template> | ||||
| <script> | <script> | ||||
| import addAttribute from "@/components/modals/attribute/addAttribute.vue"; | |||||
| import moment from "moment"; | import moment from "moment"; | ||||
| import { toast } from "vue3-toastify"; | import { toast } from "vue3-toastify"; | ||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| import { ref, onMounted } from "vue"; | |||||
| import { ref, onMounted, watch } from "vue"; | |||||
| import Layout from "@/layout/custom.vue"; | import Layout from "@/layout/custom.vue"; | ||||
| import DatePicker from "vue3-persian-datetime-picker"; | import DatePicker from "vue3-persian-datetime-picker"; | ||||
| import addIdentity from "@/components/modals/identity/addIdentity.vue"; | |||||
| export default { | export default { | ||||
| name: "SAMPLE-PAGE", | name: "SAMPLE-PAGE", | ||||
| components: { | components: { | ||||
| Layout, | Layout, | ||||
| DatePicker, | DatePicker, | ||||
| addIdentity, | |||||
| addAttribute, | |||||
| }, | }, | ||||
| setup() { | setup() { | ||||
| const attributeValues = ref(); | |||||
| const relatedAttrebutes = ref([]); | |||||
| const countInCarton = ref(); | |||||
| const selectedAttributes = ref(); | const selectedAttributes = ref(); | ||||
| const selectedIdentities = ref(); | |||||
| const expire = ref(); | const expire = ref(); | ||||
| const chosenPrice = ref(); | const chosenPrice = ref(); | ||||
| const spescialPrice = ref(); | const spescialPrice = ref(); | ||||
| @@ -491,7 +595,7 @@ export default { | |||||
| const productAttributes = ref([]); | const productAttributes = ref([]); | ||||
| const images = ref([{ file: null, preview: null }]); | const images = ref([{ file: null, preview: null }]); | ||||
| const brands = ref(); | const brands = ref(); | ||||
| const attrebutes = ref(); | |||||
| const attrebutes = ref([]); | |||||
| const selectedBrand = ref(); | const selectedBrand = ref(); | ||||
| const selectedCat = ref(); | const selectedCat = ref(); | ||||
| const date = 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 = () => { | const getBrands = () => { | ||||
| ApiServiece.get(`admin/brands`) | ApiServiece.get(`admin/brands`) | ||||
| .then((resp) => { | .then((resp) => { | ||||
| @@ -539,7 +668,7 @@ export default { | |||||
| }; | }; | ||||
| const getAttrebuteValues = () => { | const getAttrebuteValues = () => { | ||||
| ApiServiece.get(`admin/attribute-values`) | |||||
| ApiServiece.get(`admin/attribute-values?attribute_id=1`) | |||||
| .then((resp) => { | .then((resp) => { | ||||
| attrebutes.value = resp.data.data; | attrebutes.value = resp.data.data; | ||||
| console.log(attrebutes.value); | console.log(attrebutes.value); | ||||
| @@ -604,7 +733,7 @@ export default { | |||||
| errors.value = {}; | errors.value = {}; | ||||
| if (!title.value) errors.value.title = "وارد کردن عنوان محصول الزامی است"; | if (!title.value) errors.value.title = "وارد کردن عنوان محصول الزامی است"; | ||||
| if (!slug.value) | if (!slug.value) | ||||
| errors.value.slug = "وارد کردن اسلاگ محصول ضروری می باشد"; | |||||
| errors.value.slug = "وارد کردن کلمه کلیدی محصول ضروری می باشد"; | |||||
| if (!summary.value) | if (!summary.value) | ||||
| errors.value.summary = "وارد کردن خلاصه محصول ضروری می باشد"; | errors.value.summary = "وارد کردن خلاصه محصول ضروری می باشد"; | ||||
| if (!selectedCat.value) | if (!selectedCat.value) | ||||
| @@ -633,6 +762,10 @@ export default { | |||||
| if (!selectedBrand.value) | if (!selectedBrand.value) | ||||
| errors.value.selectedBrand = "انتخاب برند برای محصول ضروری می باشد"; | errors.value.selectedBrand = "انتخاب برند برای محصول ضروری می باشد"; | ||||
| if (!countInCarton.value) | |||||
| errors.value.countInCarton = | |||||
| "انتخاب تعداد محصول در هر کارتن ضروری می باشد"; | |||||
| if (images.value.length <= 0) | if (images.value.length <= 0) | ||||
| errors.value.images = "انتخاب عکس برای محصول ضروری می باشد"; | 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; | return Object.keys(errors.value).length === 0; | ||||
| }; | }; | ||||
| @@ -661,10 +808,10 @@ export default { | |||||
| getCats(); | getCats(); | ||||
| getBrands(); | getBrands(); | ||||
| getAttrebuteValues(); | getAttrebuteValues(); | ||||
| getAttributeValues(); | |||||
| }); | }); | ||||
| let id = ""; | let id = ""; | ||||
| const submitForm = () => { | const submitForm = () => { | ||||
| console.log(expire.value); | |||||
| if (!validateForm()) return; | if (!validateForm()) return; | ||||
| loading.value = true; | loading.value = true; | ||||
| @@ -713,6 +860,7 @@ export default { | |||||
| formData.append("brand_id", selectedBrand.value); | formData.append("brand_id", selectedBrand.value); | ||||
| formData.append("category_id", selectedCat.value); | formData.append("category_id", selectedCat.value); | ||||
| formData.append("count_in_carton", countInCarton.value); | |||||
| formData.append("image", image.value); | formData.append("image", image.value); | ||||
| ApiServiece.post(`admin/products`, formData, { | ApiServiece.post(`admin/products`, formData, { | ||||
| @@ -740,8 +888,28 @@ export default { | |||||
| const jsonString = JSON.stringify(finalPayload, null, 2); | const jsonString = JSON.stringify(finalPayload, null, 2); | ||||
| console.log(jsonString); | 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); | console.log(resp); | ||||
| images.value.map((image) => { | images.value.map((image) => { | ||||
| console.log(image.file); | console.log(image.file); | ||||
| @@ -752,25 +920,25 @@ export default { | |||||
| "content-type": "multipart", | "content-type": "multipart", | ||||
| Authorization: `Bearer ${localStorage.getItem("token")}`, | Authorization: `Bearer ${localStorage.getItem("token")}`, | ||||
| }, | }, | ||||
| }) | |||||
| }); | |||||
| }); | }); | ||||
| } | |||||
| ); | |||||
| }); | |||||
| }) | |||||
| .then(() => { | |||||
| loading.value = false; | |||||
| toast.success("!محصول با موفقیت اضافه شد", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | |||||
| }) | }) | ||||
| .catch((error) => { | .catch((error) => { | ||||
| console.error(error); | console.error(error); | ||||
| toast.error("!مشکلی در ایجاد محصوا بوجود آمد", { | |||||
| toast.error(`${error?.response?.data?.message}`, { | |||||
| position: "top-right", | position: "top-right", | ||||
| autoClose: 1000, | autoClose: 1000, | ||||
| }); | }); | ||||
| }) | |||||
| .finally(() => { | |||||
| loading.value = false; | |||||
| toast.success("!محصول با موفقیت اضافه شد", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | |||||
| }); | }); | ||||
| }; | }; | ||||
| @@ -811,6 +979,11 @@ export default { | |||||
| chosenPrice, | chosenPrice, | ||||
| expire, | expire, | ||||
| handleInput, | handleInput, | ||||
| countInCarton, | |||||
| relatedAttrebutes, | |||||
| selectedIdentities, | |||||
| attributeValues, | |||||
| handleAttributeUpdated, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -29,12 +29,12 @@ | |||||
| <!-- Second Input Field (Slug) --> | <!-- Second Input Field (Slug) --> | ||||
| <BCol md="6"> | <BCol md="6"> | ||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label class="form-label">اسلاگ</label> | |||||
| <label class="form-label">کلمه کلیدی</label> | |||||
| <input | <input | ||||
| type="text" | type="text" | ||||
| v-model="slug" | v-model="slug" | ||||
| class="form-control" | class="form-control" | ||||
| placeholder="اسلاگ محصول" | |||||
| placeholder="کلمه کلیدی محصول" | |||||
| :class="{ 'is-invalid': errors.slug }" | :class="{ 'is-invalid': errors.slug }" | ||||
| @input="clearError('slug')" | @input="clearError('slug')" | ||||
| /> | /> | ||||
| @@ -95,7 +95,6 @@ | |||||
| alt="Image Preview" | alt="Image Preview" | ||||
| class="img-fluid rounded shadow-sm Image-Preview" | class="img-fluid rounded shadow-sm Image-Preview" | ||||
| /> | /> | ||||
| </div> | </div> | ||||
| <small v-if="errors.image" class="text-danger"> | <small v-if="errors.image" class="text-danger"> | ||||
| @@ -112,10 +111,8 @@ | |||||
| v-model="productType" | v-model="productType" | ||||
| class="form-control" | class="form-control" | ||||
| @select="clearError('productType')" | @select="clearError('productType')" | ||||
| placeholder="انتخاب حالت محصول" | |||||
| > | > | ||||
| <option value="" disabled selected> | |||||
| انتخاب حالت محصول | |||||
| </option> | |||||
| <option value="1">تک</option> | <option value="1">تک</option> | ||||
| <option value="2">عمده</option> | <option value="2">عمده</option> | ||||
| <option value="3">هردو</option> | <option value="3">هردو</option> | ||||
| @@ -143,6 +140,23 @@ | |||||
| </small> | </small> | ||||
| </BCol> | </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"> | <BCol v-if="productType == 1 || productType == 3" md="6"> | ||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label class="form-label">قیمت (تک) </label> | <label class="form-label">قیمت (تک) </label> | ||||
| @@ -168,10 +182,8 @@ | |||||
| v-model="isChosen" | v-model="isChosen" | ||||
| class="form-control" | class="form-control" | ||||
| @select="clearError('isChosen')" | @select="clearError('isChosen')" | ||||
| placeholder="انتخاب حالت منتخب" | |||||
| > | > | ||||
| <option value="" disabled selected> | |||||
| انتخاب حالت منتخب | |||||
| </option> | |||||
| <option value="1">هست</option> | <option value="1">هست</option> | ||||
| <option value="0">نیست</option> | <option value="0">نیست</option> | ||||
| </select> | </select> | ||||
| @@ -206,8 +218,8 @@ | |||||
| v-model="spescial" | v-model="spescial" | ||||
| class="form-control" | class="form-control" | ||||
| @select="clearError('spescial')" | @select="clearError('spescial')" | ||||
| placeholder="انتخاب حالت ویژه" | |||||
| > | > | ||||
| <option value="" disabled selected>انتخاب حالت ویژه</option> | |||||
| <option value="1">هست</option> | <option value="1">هست</option> | ||||
| <option value="0">نیست</option> | <option value="0">نیست</option> | ||||
| </select> | </select> | ||||
| @@ -241,10 +253,9 @@ | |||||
| </label> | </label> | ||||
| <DatePicker | <DatePicker | ||||
| format="YYYY/MM/DD HH:mm:ss" | |||||
| :format="'jYYYY/jMM/jDD HH:mm:ss'" | |||||
| type="datetime" | type="datetime" | ||||
| v-model="expire" | v-model="expire" | ||||
| @input="handleInput" | |||||
| ></DatePicker> | ></DatePicker> | ||||
| </div> | </div> | ||||
| <small v-if="errors.expire" class="text-danger"> | <small v-if="errors.expire" class="text-danger"> | ||||
| @@ -260,10 +271,8 @@ | |||||
| v-model="selectedCat" | v-model="selectedCat" | ||||
| class="form-control" | class="form-control" | ||||
| @input="clearError('selectedCat')" | @input="clearError('selectedCat')" | ||||
| placeholder="انتخاب دسته محصول" | |||||
| > | > | ||||
| <option value="" disabled selected> | |||||
| انتخاب دسته محصول | |||||
| </option> | |||||
| <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | <option v-for="cat in cats" :key="cat.id" :value="cat.id"> | ||||
| {{ cat.title }} | {{ cat.title }} | ||||
| </option> | </option> | ||||
| @@ -282,10 +291,8 @@ | |||||
| v-model="selectedBrand" | v-model="selectedBrand" | ||||
| class="form-control" | class="form-control" | ||||
| @select="clearError('selectedBrand')" | @select="clearError('selectedBrand')" | ||||
| placeholder="انتخاب برند محصول" | |||||
| > | > | ||||
| <option value="" disabled selected> | |||||
| انتخاب برند محصول | |||||
| </option> | |||||
| <option | <option | ||||
| v-for="brand in brands" | v-for="brand in brands" | ||||
| :key="brand.id" | :key="brand.id" | ||||
| @@ -304,6 +311,13 @@ | |||||
| <div class="card-header"> | <div class="card-header"> | ||||
| <h5 class="mb-0">ویرایش ویژگی ها</h5> | <h5 class="mb-0">ویرایش ویژگی ها</h5> | ||||
| </div> | </div> | ||||
| <template v-if="locals.length === 0"> | |||||
| <BCol> | |||||
| <div class="alert alert-info text-center"> | |||||
| هیچ ویژگی برای این محصول انتخاب نکرده اید ... | |||||
| </div> | |||||
| </BCol> | |||||
| </template> | |||||
| <BRow class="g-3 mt-2"> | <BRow class="g-3 mt-2"> | ||||
| <!-- Loop through attributes --> | <!-- Loop through attributes --> | ||||
| <BCol | <BCol | ||||
| @@ -398,9 +412,149 @@ | |||||
| <BCard> | <BCard> | ||||
| <div class="card-header"> | <div class="card-header"> | ||||
| <h5 class="mb-0">اضافه کردن ویژگی ها</h5> | |||||
| <h5 class="mb-0">ویرایش مشخصه ها</h5> | |||||
| </div> | </div> | ||||
| <template v-if="localIdentities.length === 0"> | |||||
| <BCol> | |||||
| <div class="alert alert-info text-center"> | |||||
| برای شما مشخصهای ثبت نشده است. برای ساختن یک مشخصه کلیک | |||||
| کنید. | |||||
| </div> | |||||
| </BCol> | |||||
| </template> | |||||
| <BRow class="g-3 mt-2"> | <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 --> | <!-- Loop through attributes --> | ||||
| <BCol | <BCol | ||||
| v-for="attrebute in attrebutes" | v-for="attrebute in attrebutes" | ||||
| @@ -459,6 +613,105 @@ | |||||
| </BRow> | </BRow> | ||||
| </BCard> | </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> | <BCard> | ||||
| <!-- Card Header --> | <!-- Card Header --> | ||||
| <div | <div | ||||
| @@ -548,6 +801,11 @@ | |||||
| </button> | </button> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <addIdentity | |||||
| :cats="cats" | |||||
| @attribute-updated="handleAttributeUpdated()" | |||||
| /> | |||||
| <addAttribute @attribute-updated="handleAttributeUpdated()" /> | |||||
| </BCardFooter> | </BCardFooter> | ||||
| </BCard> | </BCard> | ||||
| </BCol> | </BCol> | ||||
| @@ -562,8 +820,10 @@ import { toast } from "vue3-toastify"; | |||||
| import "vue3-toastify/dist/index.css"; | import "vue3-toastify/dist/index.css"; | ||||
| import moment from "moment"; | import moment from "moment"; | ||||
| import ApiServiece from "@/services/ApiService"; | import ApiServiece from "@/services/ApiService"; | ||||
| import { ref, onMounted } from "vue"; | |||||
| import { ref, onMounted, watch } from "vue"; | |||||
| import Layout from "@/layout/custom.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"; | import DatePicker from "vue3-persian-datetime-picker"; | ||||
| export default { | export default { | ||||
| @@ -571,15 +831,10 @@ export default { | |||||
| components: { | components: { | ||||
| Layout, | Layout, | ||||
| DatePicker, | DatePicker, | ||||
| addIdentity, | |||||
| addAttribute, | |||||
| }, | }, | ||||
| setup() { | setup() { | ||||
| const selectedAttrebutes = ref([ | |||||
| { | |||||
| inventory: null, | |||||
| type: null, | |||||
| attribute_value_id: null, | |||||
| }, | |||||
| ]); | |||||
| const locals = ref([ | const locals = ref([ | ||||
| { | { | ||||
| id: null, | id: null, | ||||
| @@ -589,13 +844,18 @@ export default { | |||||
| isChecked: null, | isChecked: null, | ||||
| }, | }, | ||||
| ]); | ]); | ||||
| const localIdentities = ref([]); | |||||
| const countInCarton = ref(); | |||||
| const files = ref([]); | const files = ref([]); | ||||
| const productValueId = ref(); | const productValueId = ref(); | ||||
| const localsIds = ref(); | const localsIds = ref(); | ||||
| const identities = ref([]); | |||||
| const localIdentitiesIds = ref([]); | |||||
| const selectedAttributes = ref(); | const selectedAttributes = ref(); | ||||
| const selectedidentities = ref([]); | |||||
| const product = ref(); | const product = ref(); | ||||
| const route = useRoute(); | const route = useRoute(); | ||||
| const imagesTosend = ref() | |||||
| const imagesTosend = ref(); | |||||
| const expire = ref(); | const expire = ref(); | ||||
| const chosenPrice = ref(); | const chosenPrice = ref(); | ||||
| const spescialPrice = ref(); | const spescialPrice = ref(); | ||||
| @@ -606,7 +866,7 @@ export default { | |||||
| const images = ref([{ file: null, preview: null }]); | const images = ref([{ file: null, preview: null }]); | ||||
| const localImages = ref([{ preview: null }]); | const localImages = ref([{ preview: null }]); | ||||
| const brands = ref(); | const brands = ref(); | ||||
| const attrebutes = ref(); | |||||
| const attrebutes = ref([]); | |||||
| const selectedBrand = ref(); | const selectedBrand = ref(); | ||||
| const selectedCat = ref(); | const selectedCat = ref(); | ||||
| const date = ref(); | const date = ref(); | ||||
| @@ -616,7 +876,8 @@ export default { | |||||
| const loading = ref(false); | const loading = ref(false); | ||||
| const image = ref(); | const image = ref(); | ||||
| const imagePreview = ref(); | const imagePreview = ref(); | ||||
| const repeatedIdentity = ref(false); | |||||
| const repeatedAttrebute = ref(false); | |||||
| const errors = ref({}); | const errors = ref({}); | ||||
| const title = ref(""); | const title = ref(""); | ||||
| const slug = 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 = () => { | const getBrands = () => { | ||||
| ApiServiece.get(`admin/brands`) | ApiServiece.get(`admin/brands`) | ||||
| .then((resp) => { | .then((resp) => { | ||||
| @@ -655,12 +962,11 @@ export default { | |||||
| }; | }; | ||||
| const getAttrebuteValues = () => { | const getAttrebuteValues = () => { | ||||
| ApiServiece.get(`admin/attribute-values`) | |||||
| ApiServiece.get(`admin/attribute-values?attribute_id=1`) | |||||
| .then((resp) => { | .then((resp) => { | ||||
| console.log(resp); | |||||
| attrebutes.value = resp.data.data; | attrebutes.value = resp.data.data; | ||||
| console.log("Attributes before filtering:", attrebutes.value); | console.log("Attributes before filtering:", attrebutes.value); | ||||
| console.log("Filtered attributes:", attrebutes.value); | |||||
| }) | }) | ||||
| .then(() => { | .then(() => { | ||||
| getProduct(); | getProduct(); | ||||
| @@ -675,7 +981,7 @@ export default { | |||||
| const deleteAttribute = (id) => { | const deleteAttribute = (id) => { | ||||
| Swal.fire({ | Swal.fire({ | ||||
| title: `آیا می خواهید این ویژگی را حذف کنید ؟`, | |||||
| text: `آیا می خواهید این ویژگی را حذف کنید؟`, | |||||
| icon: "warning", | icon: "warning", | ||||
| showCancelButton: true, | showCancelButton: true, | ||||
| confirmButtonColor: "#3085d6", | 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) => { | const deletImage = (name) => { | ||||
| Swal.fire({ | Swal.fire({ | ||||
| title: `آیا می خواهید این عکس را حذف کنید ؟`, | |||||
| text: `آیا می خواهید این عکس را حذف کنید؟`, | |||||
| icon: "warning", | icon: "warning", | ||||
| showCancelButton: true, | showCancelButton: true, | ||||
| confirmButtonColor: "#3085d6", | confirmButtonColor: "#3085d6", | ||||
| @@ -811,7 +1151,7 @@ export default { | |||||
| errors.value = {}; | errors.value = {}; | ||||
| if (!title.value) errors.value.title = "وارد کردن عنوان محصول الزامی است"; | if (!title.value) errors.value.title = "وارد کردن عنوان محصول الزامی است"; | ||||
| if (!slug.value) | if (!slug.value) | ||||
| errors.value.slug = "وارد کردن اسلاگ محصول ضروری می باشد"; | |||||
| errors.value.slug = "وارد کردن کلمه کلیدی محصول ضروری می باشد"; | |||||
| if (!summary.value) | if (!summary.value) | ||||
| errors.value.summary = "وارد کردن خلاصه محصول ضروری می باشد"; | errors.value.summary = "وارد کردن خلاصه محصول ضروری می باشد"; | ||||
| if (!selectedCat.value) | if (!selectedCat.value) | ||||
| @@ -840,6 +1180,10 @@ export default { | |||||
| if (!selectedBrand.value) | if (!selectedBrand.value) | ||||
| errors.value.selectedBrand = "انتخاب برند برای محصول ضروری می باشد"; | errors.value.selectedBrand = "انتخاب برند برای محصول ضروری می باشد"; | ||||
| if (!countInCarton.value) | |||||
| errors.value.countInCarton = | |||||
| "انتخاب تعداد محصول در هر کارتن ضروری می باشد"; | |||||
| if (images.value.length <= 0) | if (images.value.length <= 0) | ||||
| errors.value.images = "انتخاب عکس برای محصول ضروری می باشد"; | 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; | return Object.keys(errors.value).length === 0; | ||||
| }; | }; | ||||
| @@ -865,57 +1223,88 @@ export default { | |||||
| }; | }; | ||||
| const getProduct = () => { | 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) => { | 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(() => { | onMounted(() => { | ||||
| getCats(); | getCats(); | ||||
| getBrands(); | getBrands(); | ||||
| @@ -959,6 +1383,7 @@ export default { | |||||
| let id = ""; | let id = ""; | ||||
| const submitForm = () => { | const submitForm = () => { | ||||
| console.log(errors.value); | console.log(errors.value); | ||||
| if (!validateForm()) return; | if (!validateForm()) return; | ||||
| loading.value = true; | loading.value = true; | ||||
| @@ -969,6 +1394,7 @@ export default { | |||||
| formData.append("slug", slug.value); | formData.append("slug", slug.value); | ||||
| formData.append("summary", summary.value); | formData.append("summary", summary.value); | ||||
| formData.append("description", description.value); | formData.append("description", description.value); | ||||
| formData.append("count_in_carton", countInCarton.value); | |||||
| if (productType.value == 2) { | if (productType.value == 2) { | ||||
| formData.append("wholesale_price", wholesalePrice.value); | formData.append("wholesale_price", wholesalePrice.value); | ||||
| } | } | ||||
| @@ -1021,6 +1447,7 @@ export default { | |||||
| }, | }, | ||||
| }) | }) | ||||
| .then((resp) => { | .then((resp) => { | ||||
| loading.value = false; | |||||
| id = resp.data.data.id; | id = resp.data.data.id; | ||||
| console.log(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) { | if (files.value.length > 0) { | ||||
| console.log(files.value); | console.log(files.value); | ||||
| imagesTosend.value = images.value.filter((image) => { | |||||
| imagesTosend.value = images.value.filter((image) => { | |||||
| return !localImages.value.some( | return !localImages.value.some( | ||||
| (localImage) => localImage.preview === image.preview | (localImage) => localImage.preview === image.preview | ||||
| ); | ); | ||||
| }); | }); | ||||
| imagesTosend.value.map((image) => { | |||||
| // Collect all promises | |||||
| const uploadPromises = imagesTosend.value.map((image) => { | |||||
| const formData = new FormData(); | const formData = new FormData(); | ||||
| formData.append("image", image.file); | formData.append("image", image.file); | ||||
| ApiServiece.post( | |||||
| return ApiServiece.post( | |||||
| `admin/products/${route.params.id}/images`, | `admin/products/${route.params.id}/images`, | ||||
| formData, | formData, | ||||
| { | { | ||||
| @@ -1066,26 +1512,42 @@ export default { | |||||
| Authorization: `Bearer ${localStorage.getItem("token")}`, | 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) => { | .catch((error) => { | ||||
| loading.value = false; | |||||
| console.error(error); | console.error(error); | ||||
| toast.error("!مشکلی در ویرایش محصول ایحاد شد", { | toast.error("!مشکلی در ویرایش محصول ایحاد شد", { | ||||
| position: "top-right", | position: "top-right", | ||||
| autoClose: 1000, | autoClose: 1000, | ||||
| }); | }); | ||||
| }) | |||||
| .finally(() => { | |||||
| loading.value = false; | |||||
| toast.success("!محصول با موفقیت ویرایش شد", { | |||||
| position: "top-right", | |||||
| autoClose: 1000, | |||||
| }); | |||||
| }); | }); | ||||
| }; | }; | ||||
| @@ -1117,7 +1579,6 @@ export default { | |||||
| images, | images, | ||||
| addImage, | addImage, | ||||
| handleImageUpload, | handleImageUpload, | ||||
| selectedAttrebutes, | |||||
| description, | description, | ||||
| onCheckboxChange, | onCheckboxChange, | ||||
| retailePrice, | retailePrice, | ||||
| @@ -1125,12 +1586,21 @@ export default { | |||||
| spescialPrice, | spescialPrice, | ||||
| chosenPrice, | chosenPrice, | ||||
| expire, | expire, | ||||
| handleInput, | |||||
| selectedAttributes, | selectedAttributes, | ||||
| selectedidentities, | |||||
| locals, | locals, | ||||
| editAttribute, | editAttribute, | ||||
| deleteAttribute, | deleteAttribute, | ||||
| deletImage, | deletImage, | ||||
| countInCarton, | |||||
| localIdentities, | |||||
| deleteIdentity, | |||||
| identities, | |||||
| handleAttributeUpdated, | |||||
| editIdentity, | |||||
| repeatedIdentity, | |||||
| repeatedAttrebute, | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -18,6 +18,9 @@ export default { | |||||
| const totalPages = ref(1); | const totalPages = ref(1); | ||||
| const paginate = ref(20); | const paginate = ref(20); | ||||
| const page = ref(1); | const page = ref(1); | ||||
| function formatWithCommas(number) { | |||||
| return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); | |||||
| } | |||||
| const filterLoading = ref(false); | const filterLoading = ref(false); | ||||
| const searchQuery = ref(""); | const searchQuery = ref(""); | ||||
| @@ -73,7 +76,7 @@ export default { | |||||
| const deleteProduct = (id, title) => { | const deleteProduct = (id, title) => { | ||||
| Swal.fire({ | Swal.fire({ | ||||
| title: `می خواهید محصول ${title} را حذف کنید ؟`, | |||||
| text: `می خواهید محصول ${title} را حذف کنید؟`, | |||||
| icon: "warning", | icon: "warning", | ||||
| showCancelButton: true, | showCancelButton: true, | ||||
| confirmButtonColor: "#3085d6", | confirmButtonColor: "#3085d6", | ||||
| @@ -105,7 +108,7 @@ export default { | |||||
| const restoreProduct = (id, title) => { | const restoreProduct = (id, title) => { | ||||
| Swal.fire({ | Swal.fire({ | ||||
| title: `می خواهید محصول ${title} را بازیابی کنید ؟`, | |||||
| text: `می خواهید محصول ${title} را بازیابی کنید؟`, | |||||
| icon: "warning", | icon: "warning", | ||||
| showCancelButton: true, | showCancelButton: true, | ||||
| confirmButtonColor: "#3085d6", | confirmButtonColor: "#3085d6", | ||||
| @@ -126,7 +129,7 @@ export default { | |||||
| }) | }) | ||||
| .catch((err) => { | .catch((err) => { | ||||
| console.log(err); | console.log(err); | ||||
| toast.error("!مشکلی در بایابی محصول پیش آمد", { | |||||
| toast.error("!مشکلی در بازیابی محصول پیش آمد", { | |||||
| position: "top-right", | position: "top-right", | ||||
| autoClose: 3000, | autoClose: 3000, | ||||
| }); | }); | ||||
| @@ -187,6 +190,7 @@ export default { | |||||
| searchPage, | searchPage, | ||||
| visiblePages, | visiblePages, | ||||
| restoreProduct, | restoreProduct, | ||||
| formatWithCommas | |||||
| }; | }; | ||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -197,7 +201,7 @@ export default { | |||||
| <div class="col-md-12"> | <div class="col-md-12"> | ||||
| <div class="card shadow-sm border-0 rounded"> | <div class="card shadow-sm border-0 rounded"> | ||||
| <div | <div | ||||
| class="card-header d-flex justify-content-between align-items-center p-3 bg-primary text-white" | |||||
| class="card-header d-flex justify-content-between align-items-center p-3" | |||||
| dir="rtl" | dir="rtl" | ||||
| > | > | ||||
| <div class="d-flex align-items-center"> | <div class="d-flex align-items-center"> | ||||
| @@ -253,36 +257,31 @@ export default { | |||||
| <td>{{ product.sold_count }}</td> | <td>{{ product.sold_count }}</td> | ||||
| <td> | <td> | ||||
| <span v-if="product.is_chosen == 0"> | <span v-if="product.is_chosen == 0"> | ||||
| نیست | |||||
| <i | <i | ||||
| class="fas fa-times-circle status-icon unavailable" | class="fas fa-times-circle status-icon unavailable" | ||||
| ></i> | ></i> | ||||
| </span> | </span> | ||||
| <span v-if="product.is_chosen == 1"> | <span v-if="product.is_chosen == 1"> | ||||
| انتخاب شده | |||||
| <i | <i | ||||
| class="fas fa-check-circle status-icon available" | class="fas fa-check-circle status-icon available" | ||||
| ></i> | ></i> | ||||
| </span> | </span> | ||||
| </td> | </td> | ||||
| <td v-if="product?.chosen_price"> | <td v-if="product?.chosen_price"> | ||||
| {{ product?.chosen_price }} | |||||
| {{ formatWithCommas(product?.chosen_price) }} | |||||
| </td> | </td> | ||||
| <td v-if="!product.chosen_price"> | <td v-if="!product.chosen_price"> | ||||
| ندارد | |||||
| <i | <i | ||||
| class="fas fa-times-circle status-icon unavailable" | class="fas fa-times-circle status-icon unavailable" | ||||
| ></i> | ></i> | ||||
| </td> | </td> | ||||
| <td> | <td> | ||||
| <span v-if="product.is_special == 0"> | <span v-if="product.is_special == 0"> | ||||
| نیست | |||||
| <i | <i | ||||
| class="fas fa-times-circle status-icon unavailable" | class="fas fa-times-circle status-icon unavailable" | ||||
| ></i> | ></i> | ||||
| </span> | </span> | ||||
| <span v-if="product.is_special == 1"> | <span v-if="product.is_special == 1"> | ||||
| انتخاب شده | |||||
| <i | <i | ||||
| class="fas fa-check-circle status-icon available" | class="fas fa-check-circle status-icon available" | ||||
| ></i> | ></i> | ||||
| @@ -290,10 +289,10 @@ export default { | |||||
| </td> | </td> | ||||
| <td v-if="product?.wholesale_price"> | <td v-if="product?.wholesale_price"> | ||||
| {{ product.wholesale_price }} | |||||
| {{ formatWithCommas(product.wholesale_price) }} | |||||
| </td> | </td> | ||||
| <td v-if="product?.retail_price"> | <td v-if="product?.retail_price"> | ||||
| {{ product?.retail_price }} | |||||
| {{ formatWithCommas(product?.retail_price) }} | |||||
| </td> | </td> | ||||
| <td v-if="!product.deleted_at"> | <td v-if="!product.deleted_at"> | ||||
| @@ -498,7 +497,6 @@ export default { | |||||
| user-select: none; | user-select: none; | ||||
| } | } | ||||
| .table { | .table { | ||||
| background-color: #f9f9f9; | |||||
| border-radius: 8px; | border-radius: 8px; | ||||
| overflow: hidden; | overflow: hidden; | ||||
| font-size: 0.9rem; | font-size: 0.9rem; | ||||
| @@ -513,19 +511,10 @@ export default { | |||||
| vertical-align: middle; | vertical-align: middle; | ||||
| } | } | ||||
| .table-light { | |||||
| background-color: #f8f9fa; | |||||
| } | |||||
| .table th { | .table th { | ||||
| font-weight: bold; | font-weight: bold; | ||||
| } | } | ||||
| .table td { | |||||
| border: 1px solid #e0e0e0; | |||||
| color: #333; | |||||
| } | |||||
| .table-hover tbody tr:hover { | .table-hover tbody tr:hover { | ||||
| background-color: #e9ecef; | background-color: #e9ecef; | ||||
| cursor: pointer; | cursor: pointer; | ||||
| @@ -577,19 +566,14 @@ table tbody tr:hover { | |||||
| } | } | ||||
| td, | td, | ||||
| th { | |||||
| color: #5f6368; | |||||
| } | |||||
| table[dir="rtl"] td, | table[dir="rtl"] td, | ||||
| table[dir="rtl"] th { | table[dir="rtl"] th { | ||||
| text-align: center; | text-align: center; | ||||
| } | } | ||||
| td:last-child { | td:last-child { | ||||
| display: flex; | |||||
| justify-content: center; | justify-content: center; | ||||
| gap: 10px; | |||||
| align-items: center; | align-items: center; | ||||
| } | } | ||||
| </style> | </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) --> | <!-- Second Input Field (Slug) --> | ||||
| <BCol md="6"> | <BCol md="6"> | ||||
| <div class="form-group"> | <div class="form-group"> | ||||
| <label class="form-label">اسلاگ</label> | |||||
| <label class="form-label">ککلمه کلیدی</label> | |||||
| <input | <input | ||||
| type="text" | type="text" | ||||
| v-model="slug" | v-model="slug" | ||||
| class="form-control" | class="form-control" | ||||
| placeholder="اسلاگ محصول" | |||||
| placeholder="کلمه کلیدی محصول" | |||||
| :class="{ 'is-invalid': errors.slug }" | :class="{ 'is-invalid': errors.slug }" | ||||
| @input="clearError('slug')" | @input="clearError('slug')" | ||||
| /> | /> | ||||
| @@ -586,7 +586,7 @@ export default { | |||||
| errors.value = {}; | errors.value = {}; | ||||
| if (!title.value) errors.value.title = "وارد کردن عنوان محصول الزامی است"; | if (!title.value) errors.value.title = "وارد کردن عنوان محصول الزامی است"; | ||||
| if (!slug.value) | if (!slug.value) | ||||
| errors.value.slug = "وارد کردن اسلاگ محصول ضروری می باشد"; | |||||
| errors.value.slug = "وارد کردن کلمه کلیدی محصول ضروری می باشد"; | |||||
| if (!summary.value) | if (!summary.value) | ||||
| errors.value.summary = "وارد کردن خلاصه محصول ضروری می باشد"; | errors.value.summary = "وارد کردن خلاصه محصول ضروری می باشد"; | ||||
| if (!blogCat.value) | if (!blogCat.value) | ||||
| @@ -114,7 +114,7 @@ export default { | |||||
| const blockUser = (id) => { | const blockUser = (id) => { | ||||
| Swal.fire({ | Swal.fire({ | ||||
| title: "آیا مطمئن هستید؟", | |||||
| text: "آیا میخواهید این کاربر را مسدود کنید؟", | text: "آیا میخواهید این کاربر را مسدود کنید؟", | ||||
| icon: "warning", | icon: "warning", | ||||
| showCancelButton: true, | showCancelButton: true, | ||||
| @@ -147,7 +147,7 @@ export default { | |||||
| const unBlockUser = (id) => { | const unBlockUser = (id) => { | ||||
| Swal.fire({ | Swal.fire({ | ||||
| title: "آیا مطمئن هستید؟", | |||||
| text: "آیا میخواهید این کاربر را فعال نمایید؟", | text: "آیا میخواهید این کاربر را فعال نمایید؟", | ||||
| icon: "warning", | icon: "warning", | ||||
| showCancelButton: true, | showCancelButton: true, | ||||
| @@ -242,15 +242,16 @@ export default { | |||||
| <li><a class="dropdown-item" href="#">بلاک</a></li> | <li><a class="dropdown-item" href="#">بلاک</a></li> | ||||
| </ul> | </ul> | ||||
| </div> --> | </div> --> | ||||
| </div> | |||||
| <button | |||||
| <button | |||||
| data-bs-toggle="modal" | data-bs-toggle="modal" | ||||
| data-bs-target="#addUser" | data-bs-target="#addUser" | ||||
| class="btn btn-add-user" | |||||
| class="btn btn-add-user me-3" | |||||
| > | > | ||||
| <i class="ti ti-plus"></i> افزودن کاربر | |||||
| افزودن کاربر | |||||
| </button> | </button> | ||||
| </div> | |||||
| </div> | </div> | ||||
| <div class="table-responsive"> | <div class="table-responsive"> | ||||
| @@ -262,7 +263,7 @@ export default { | |||||
| <th>موبایل</th> | <th>موبایل</th> | ||||
| <th>نقش</th> | <th>نقش</th> | ||||
| <th>تاریخ ایجاد</th> | <th>تاریخ ایجاد</th> | ||||
| <th>فعالیت</th> | |||||
| <th>وضعیت</th> | |||||
| </tr> | </tr> | ||||
| </thead> | </thead> | ||||
| <tbody> | <tbody> | ||||