Преглед изворни кода

admin panel updates

master
unknown пре 10 месеци
родитељ
комит
ecc35c22a0
26 измењених фајлова са 1091 додато и 184 уклоњено
  1. +8
    -0
      package-lock.json
  2. +2
    -0
      package.json
  3. +12
    -2
      public/index.html
  4. +3
    -0
      src/assets/images/location.svg
  5. +116
    -26
      src/components/customSidebar.vue
  6. +46
    -40
      src/components/modals/identity/addIdentity.vue
  7. +5
    -5
      src/components/modals/identity/editIdentity.vue
  8. +1
    -1
      src/router/index.js
  9. +19
    -6
      src/router/routes.js
  10. +67
    -4
      src/views/live-preview/pages/auth2/forgot-password.vue
  11. +66
    -2
      src/views/live-preview/pages/auth2/login.vue
  12. +74
    -4
      src/views/live-preview/pages/auth2/otpLogin.vue
  13. +12
    -11
      src/views/live-preview/pages/banners/addBanner.vue
  14. +13
    -0
      src/views/live-preview/pages/banners/banners.vue
  15. +19
    -6
      src/views/live-preview/pages/blogs/blogs.vue
  16. +14
    -4
      src/views/live-preview/pages/brands/brands.vue
  17. +13
    -4
      src/views/live-preview/pages/catrgories/cats.vue
  18. +8
    -1
      src/views/live-preview/pages/comments/comments.vue
  19. +4
    -4
      src/views/live-preview/pages/discounts/addDiscount.vue
  20. +26
    -15
      src/views/live-preview/pages/faqs/faqs.vue
  21. +21
    -4
      src/views/live-preview/pages/products/addProduct.vue
  22. +2
    -2
      src/views/live-preview/pages/products/products.vue
  23. +242
    -0
      src/views/live-preview/pages/profile/address.vue
  24. +12
    -10
      src/views/live-preview/pages/profile/profile.vue
  25. +232
    -0
      src/views/live-preview/pages/settings/setting.vue
  26. +54
    -33
      src/views/live-preview/pages/users/users.vue

+ 8
- 0
package-lock.json Прегледај датотеку

@@ -42,6 +42,8 @@
"jalaali-js": "^1.2.7",
"jalali-moment": "^3.3.11",
"jquery": "^3.7.1",
"leaflet": "^1.9.4",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"multi-range-slider-vue": "^1.1.4",
"not": "^0.1.0",
@@ -9016,6 +9018,12 @@
"launch-editor": "^2.9.1"
}
},
"node_modules/leaflet": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
"license": "BSD-2-Clause"
},
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",


+ 2
- 0
package.json Прегледај датотеку

@@ -42,6 +42,8 @@
"jalaali-js": "^1.2.7",
"jalali-moment": "^3.3.11",
"jquery": "^3.7.1",
"leaflet": "^1.9.4",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"multi-range-slider-vue": "^1.1.4",
"not": "^0.1.0",


+ 12
- 2
public/index.html Прегледај датотеку

@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.svg" />
<link rel="icon" id="favicon" href="<%= BASE_URL %>favicon.svg" />
<link rel="stylesheet" href="<%= BASE_URL %>fonts/vazir.css" />
<script defer src="https://bazarce.liara.run/script.js" data-website-id="7baabdd5-3224-41c1-9267-d2a1abd29d01"></script>
<title><%= htmlWebpackPlugin.options.title %></title>
@@ -19,6 +19,16 @@
>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script>
document.addEventListener("DOMContentLoaded", function () {
const faviconUrl = localStorage.getItem("logo");
if (faviconUrl) {
const faviconLink = document.getElementById("favicon");
faviconLink.href = faviconUrl;
}
});
</script>
</body>
</html>

+ 3
- 0
src/assets/images/location.svg Прегледај датотеку

@@ -0,0 +1,3 @@
<svg width="24" height="29" viewBox="0 0 24 29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0418 3.77847C15.7461 -0.517301 8.78126 -0.517301 4.4855 3.77847L4.13194 4.13202C-0.359085 8.62305 -0.359085 15.9044 4.13194 20.3955L12.2637 28.5272L20.3954 20.3955C24.8864 15.9044 24.8864 8.62305 20.3954 4.13202L20.0418 3.77847ZM14.7322 6.28885C13.5606 5.11728 11.6611 5.11727 10.4895 6.28885L7.12132 9.65705C5.94975 10.8286 5.94974 12.7281 7.12132 13.8997L10.4895 17.2679C11.6611 18.4395 13.5606 18.4395 14.7322 17.2679L18.1004 13.8997C19.2719 12.7281 19.2719 10.8286 18.1004 9.65705L14.7322 6.28885Z" fill="#424038"/>
</svg>

+ 116
- 26
src/components/customSidebar.vue Прегледај датотеку

@@ -1,5 +1,6 @@
<script>
import { ref, onMounted, onUnmounted, computed } from "vue";
import ApiServiece from "@/services/ApiService";
import { useRouter } from "vue-router";
import { ChevronDownIcon } from "@zhuowenli/vue-feather-icons";
import simplebar from "simplebar-vue";
@@ -17,6 +18,8 @@ export default {
};
},
setup() {
const settings = ref()
const logo = ref()
const router = useRouter();
const currentLogo = ref(logoDark);
const store = useStore();
@@ -52,11 +55,29 @@ export default {

const gotoAccount = () => {
router.push({ name: "profile" });
console.log("asdasd");
};

const getSettings = async () => {
const localLogo = localStorage.getItem("logo")
if(localLogo){
logo.value = localLogo;
}
else {
try {
const response = await ApiServiece.get("settings/logo_fav");
settings.value = response.data.data;
logo.value = settings.value.value;
localStorage.setItem("logo" , logo.value)
} catch (error) {
console.error("Error fetching settings:", error);
}
}
};
onMounted(() => {
updateLogo();

getSettings();
const observer = new MutationObserver(() => {
updateLogo();
});
@@ -71,7 +92,7 @@ export default {
});
});

return { currentLogo, user, logoutUser, gotoAccount };
return { currentLogo, user, logoutUser, gotoAccount , logo , settings };
},
components: {
ChevronDownIcon,
@@ -213,29 +234,26 @@ export default {
}
},
};

</script>

<template>
<div class="navbar-wrapper" id="navbar-wrapper">
<div class="m-header">
<router-link to="/" class="b-brand text-primary">
<!-- ======== Change your logo from here ============ -->
<img
v-if="currentLogo === logoDark"
:src="logoDark"
alt="logo image"
class="logo-lg custom_logo"
/>
<img
v-else
:src="logoWhite"
alt="logo image"
class="logo-lg custom_logo"
/>
<img src="@/assets/images/favicon.svg" alt="" class="logo logo-sm" />
</router-link>
</div>
<!-- ======== Change your logo from here ============ -->
<img
v-if="currentLogo === logoDark"
:src="logo"
alt="logo image"
class="logo-img"
/>
<img
v-else
:src="logo"
alt="logo image"
class="logo-img"
/>
</div>
<simplebar data-simplebar class="navbar-content pc-trigger">
<ul class="pc-navbar">
<li class="pc-item" :class="{ active: this.$route.path === '/users' }">
@@ -342,24 +360,25 @@ export default {
>
</li>

<!-- سفاراشات -->
<li class="pc-item pc-hasmenu">
<BLink
class="pc-link"
data-bs-toggle="collapse"
href="#collapse26"
href="#collapse-orders"
role="button"
aria-expanded="false"
aria-controls="collapse26"
aria-controls="collapse-orders"
>
<span class="pc-micon">
<i class="ph-duotone ph-shopping-cart"></i>
</span>
<span class="pc-mtext">سفارشات</span
><span class="pc-arrow">
<span class="pc-mtext">سفارشات</span>
<span class="pc-arrow">
<ChevronDownIcon></ChevronDownIcon>
</span>
</BLink>
<div class="collapse" id="collapse26">
<div class="collapse" id="collapse-orders">
<ul class="pc-submenu">
<li
class="pc-item"
@@ -384,6 +403,47 @@ export default {
</ul>
</div>
</li>

<!-- دسته ها -->
<li class="pc-item pc-hasmenu">
<BLink
class="pc-link"
data-bs-toggle="collapse"
href="#collapse-categories"
role="button"
aria-expanded="false"
aria-controls="collapse-categories"
>
<span class="pc-micon">
<i class="ph-duotone ph-folder"></i>
</span>
<span class="pc-mtext">دسته ها</span>
<span class="pc-arrow">
<ChevronDownIcon></ChevronDownIcon>
</span>
</BLink>
<div class="collapse" id="collapse-categories">
<ul class="pc-submenu">
<li
class="pc-item"
:class="{ active: this.$route.path === '/cats' }"
>
<router-link to="/cats" class="pc-link">
<span class="pc-mtext">دسته محصولات</span></router-link
>
</li>
<li
class="pc-item"
:class="{ active: this.$route.path === '/blogCat' }"
>
<router-link to="/blogCat" class="pc-link">
<span class="pc-mtext">دسته بلاگ</span></router-link
>
</li>
</ul>
</div>
</li>

<li
class="pc-item"
:class="{ active: this.$route.path === '/comments' }"
@@ -453,7 +513,16 @@ export default {
<span class="pc-micon">
<i class="ph-duotone ph-clock"></i>
</span>
<span class="pc-mtext">پیگیری</span></router-link
<span class="pc-mtext">برسی فرم ها</span></router-link
>
</li>

<li class="pc-item" :class="{ active: this.$route.path === '/settings' }">
<router-link to="/settings" class="pc-link">
<span class="pc-micon">
<i class="ph-duotone ph-gear"></i>
</span>
<span class="pc-mtext">تنظیمات</span></router-link
>
</li>

@@ -504,3 +573,24 @@ export default {
</BCard>
</div>
</template>


<style scoped>
.logo-img {
width: auto; /* Prevent scaling */
height: 80px; /* Set a larger fixed height */
max-width: 100%; /* Ensure the image doesn't overflow */
display: block; /* Center the image if necessary */
margin: 0 auto; /* Center the logo horizontally */
border-radius: 8px; /* Soft rounded corners */
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); /* Soft shadow for depth */
transition: transform 0.3s ease-in-out, box-shadow 0.3s ease-in-out; /* Smooth transition */
}
.m-header {
display: flex;
justify-content: center; /* Center the logo horizontally */
align-items: center; /* Center the logo vertically */
padding: 20px 0; /* Add vertical padding around the header */
}

</style>

+ 46
- 40
src/components/modals/identity/addIdentity.vue Прегледај датотеку

@@ -130,8 +130,7 @@ export default {
errors.value = {};
if (!title.value)
errors.value.title = "وارد کردن عنوان مشخصه ضروری می باشد";
if (!selectedCat.value)
errors.value.selectedCat = "انتخاب دسته ضروری می باشد";

return Object.keys(errors.value).length === 0;
};

@@ -140,44 +139,51 @@ export default {
};

const addIdentity = () => {
if (!validateForm()) {
toast.error("لطفا فیلد های لازم را وارد نمایید", {
position: "top-right",
autoClose: 1000,
});
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;
});
};
if (!validateForm()) {
toast.error("لطفا فیلد های لازم را وارد نمایید", {
position: "top-right",
autoClose: 1000,
});
return;
}
loading.value = true;

const formData = new FormData();
if (selectedCat.value) {
formData.append("category_id", selectedCat.value);
}
formData.append("title", title.value);

ApiServiece.post(`admin/attributes`, formData)
.then((resp) => {
console.log(resp);
toast.success("!مشخصه با موفقیت اضافه شد", {
position: "top-right",
autoClose: 1000,
});

// **Reset form fields**
title.value = "";
selectedCat.value = null;
})
.then(() => {
setTimeout(() => {
document.getElementById("close").click();
emit("identity-updated");
}, 500);
})
.catch((error) => {
console.error(error);
toast.error("!اضافه کردن مشخصه با مشکل مواجه شد", {
position: "top-right",
autoClose: 1000,
});
})
.finally(() => {
loading.value = false;
});
};


return {
errors,


+ 5
- 5
src/components/modals/identity/editIdentity.vue Прегледај датотеку

@@ -77,7 +77,7 @@
type="button"
class="btn btn-secondary"
data-bs-dismiss="modal"
id="close"
id="closeIdentityModal"
>
بستن
</button>
@@ -156,7 +156,6 @@ export default {
errors.value = {};
if (!localTitle.value)
errors.value.localTitle = "وارد کردن عنوان مشخصه ضروری می باشد";
if (!localCat.value) errors.value.localCat = "انتخاب دسته ضروری می باشد";
return Object.keys(errors.value).length === 0;
};

@@ -173,9 +172,10 @@ export default {
return;
}
loading.value = true;

const formData = new FormData();
formData.append("category_id", localCatId.value);
if (localCatId.value) {
formData.append("category_id", localCatId.value);
}
formData.append("title", localTitle.value);

ApiServiece.put(`admin/attributes/${localId.value}`, formData)
@@ -188,7 +188,7 @@ export default {
})
.then(() => {
setTimeout(() => {
document.getElementById("close").click();
document.getElementById("closeIdentityModal").click();
emit("attribute-updated");
}, 500);
})


+ 1
- 1
src/router/index.js Прегледај датотеку

@@ -4,7 +4,7 @@ import appConfig from "../../app.config";
import store from "../state/store";

const router = createRouter({
history: createWebHistory("/vue/"),
history: createWebHistory("/"),
routes,
});



+ 19
- 6
src/router/routes.js Прегледај датотеку

@@ -233,6 +233,15 @@ export default [
},
component: () => import("../views/live-preview/pages/profile/profile.vue"),
},
{
path: "/address",
name: "address",
meta: {
title: "پروفایل",
requiresAuth: true,
},
component: () => import("../views/live-preview/pages/profile/address.vue"),
},
{
path: "/banners",
name: "banners",
@@ -282,6 +291,15 @@ export default [
},
component: () => import("../views/live-preview/pages/calls/calls.vue"),
},
{
path: "/settings",
name: "settings",
meta: {
title: "تنظیمات",
requiresAuth: true,
},
component: () => import("../views/live-preview/pages/settings/setting.vue"),
},
{
path: "/",
name: "live-preview",
@@ -333,12 +351,7 @@ export default [
meta: { title: "sampl Page" },
component: () => import("../views/live-preview/other/sample-page.vue"),
},
{
path: "/setting",
name: "setting",
meta: { title: "Setting" },
component: () => import("../views/live-preview/ui-kit/setting.vue"),
},

{
path: "/application/plans",
name: "Apps Plans",


+ 67
- 4
src/views/live-preview/pages/auth2/forgot-password.vue Прегледај датотеку

@@ -1,7 +1,7 @@
<script>
import Rightbar from "@/components/right-bar.vue";

import { ref, onUnmounted } from "vue";
import { ref, onUnmounted, onMounted } from "vue";
import { useRouter } from "vue-router";
import { useStore } from "vuex";
import ApiServiece from "@/services/ApiService";
@@ -12,6 +12,8 @@ export default {
Rightbar,
},
setup() {
const settings = ref();
const logo = ref();
const verifyOtpLoading = ref(false);
const sendOtpLoading = ref(false);
const errors = ref({});
@@ -78,7 +80,7 @@ export default {
const resendOtp = () => {
otpSent.value = false;
otpCode.value = "";
timer.value = 20;
timer.value = 120;
resendAvailable.value = false;
};

@@ -97,7 +99,7 @@ export default {
verifyOtpLoading.value = false;
Swal.fire({
icon: "error",
title: "اوه! انگار چیزی اشتباه شد",
title: "خطایی رخ داد",
text: `${error.message}`,
confirmButtonText: "باشه",
});
@@ -127,6 +129,21 @@ export default {
if (timerInterval) clearInterval(timerInterval);
});

const getSettings = async () => {
logo.value = localStorage.getItem("logo");
try {
const response = await ApiServiece.get("settings/logo_fav");
settings.value = response.data.data;
} catch (error) {
console.error("Error fetching settings:", error);
}
};

onMounted(() => {
getSettings();
});

return {
sendOtp,
resendOtp,
@@ -141,6 +158,8 @@ export default {
validateVerifyOtpForm,
sendOtpLoading,
verifyOtpLoading,
logo,
settings,
};
},
};
@@ -150,10 +169,12 @@ export default {
<div class="auth-main v2">
<div class="bg-overlay bg-dark"></div>
<div class="auth-wrapper">
<div class="auth-sidecontent"></div>
<div class="auth-form">
<div class="card my-5 mx-3">
<div class="card-body" style="direction: rtl">
<div class="text-center mb-4">
<img :src="logo" alt="Logo" class="styled-logo" />
</div>
<h4 class="f-w-500 mb-1">فراموشی رمز عبور</h4>

<p class="mb-3">
@@ -248,3 +269,45 @@ export default {
</div>
<Rightbar />
</template>

<style scoped>
.styled-logo {
display: block;
max-width: 120px; /* Adjust the logo size here */
width: 100%;
height: auto;
border-radius: 10px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
margin: 0 auto 30px;
margin-bottom: 90px;
}

.card {
border: none;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}

.card-body {
padding: 40px;
}

h4 {
font-size: 1.5rem;
font-weight: 500;
}

.text-center {
text-align: center;
}

.mb-3 {
margin-bottom: 1.5rem;
}

.form-control {
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 0.8rem 1rem;
}
</style>

+ 66
- 2
src/views/live-preview/pages/auth2/login.vue Прегледај датотеку

@@ -2,10 +2,12 @@
<div class="auth-main v2">
<div class="bg-overlay bg-dark"></div>
<div class="auth-wrapper">
<div class="auth-sidecontent"></div>
<div class="auth-form">
<div class="card my-5 mx-3">
<div class="card-body" style="direction: rtl">
<div class="text-center mb-4">
<img :src="logo" alt="Logo" class="styled-logo" />
</div>
<h4 class="f-w-500 mb-1">ورود با شماره موبایل</h4>

<p class="mb-3">
@@ -80,12 +82,15 @@

<script>
import { useRouter } from "vue-router";
import { computed, ref } from "vue";
import ApiServiece from "@/services/ApiService";
import { computed, ref, onMounted } from "vue";
import { useStore } from "vuex";
import Swal from "sweetalert2";
export default {
name: "LoginForm",
setup() {
const settings = ref();
const logo = ref();
const errors = ref({});
const store = useStore();
const mobile = ref("");
@@ -130,6 +135,21 @@ export default {
errors.value[field] = "";
};

const getSettings = async () => {
logo.value = localStorage.getItem("logo");
try {
const response = await ApiServiece.get("settings/logo_fav");
settings.value = response.data.data;
} catch (error) {
console.error("Error fetching settings:", error);
}
};

onMounted(() => {
getSettings();
});

return {
mobile,
password,
@@ -139,7 +159,51 @@ export default {
validateForm,
clearError,
errors,
logo,
settings,
};
},
};
</script>

<style scoped>
.styled-logo {
display: block;
max-width: 120px; /* Adjust the logo size here */
width: 100%;
height: auto;
border-radius: 10px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
margin: 0 auto 30px;
margin-bottom: 90px;
}

.card {
border: none;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}

.card-body {
padding: 40px;
}

h4 {
font-size: 1.5rem;
font-weight: 500;
}

.text-center {
text-align: center;
}

.mb-3 {
margin-bottom: 1.5rem;
}

.form-control {
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 0.8rem 1rem;
}
</style>

+ 74
- 4
src/views/live-preview/pages/auth2/otpLogin.vue Прегледај датотеку

@@ -1,6 +1,6 @@
<script>
import Rightbar from "@/components/right-bar.vue";
import { ref, onUnmounted } from "vue";
import { ref, onUnmounted, onMounted } from "vue";
import { useRouter } from "vue-router";
import { useStore } from "vuex";
import ApiServiece from "@/services/ApiService";
@@ -11,6 +11,8 @@ export default {
Rightbar,
},
setup() {
const settings = ref();
const logo = ref();
const verifyOtpLoading = ref(false);
const sendOtpLoading = ref(false);
const errors = ref({});
@@ -18,7 +20,7 @@ export default {
const store = useStore();
const mobile = ref("");
const otpSent = ref(false);
const timer = ref(20);
const timer = ref(120);
const otpCode = ref("");
const resendAvailable = ref(false);
let timerInterval = null;
@@ -77,7 +79,7 @@ export default {
const resendOtp = () => {
otpSent.value = false;
otpCode.value = "";
timer.value = 20;
timer.value = 120;
resendAvailable.value = false;
};

@@ -122,10 +124,32 @@ export default {
errors.value[field] = "";
};

const getSettings = async () => {
const localLogo = localStorage.getItem("logo")
if(localLogo){
logo.value = localLogo;
}
else {
try {
const response = await ApiServiece.get("settings/logo_fav");
settings.value = response.data.data;
logo.value = settings.value.value;
localStorage.setItem("logo" , logo.value)
} catch (error) {
console.error("Error fetching settings:", error);
}
}
};

onUnmounted(() => {
if (timerInterval) clearInterval(timerInterval);
});

onMounted(() => {
getSettings();
});

return {
sendOtp,
resendOtp,
@@ -140,6 +164,8 @@ export default {
validateVerifyOtpForm,
sendOtpLoading,
verifyOtpLoading,
settings,
logo,
};
},
};
@@ -149,10 +175,12 @@ export default {
<div class="auth-main v2">
<div class="bg-overlay bg-dark"></div>
<div class="auth-wrapper">
<div class="auth-sidecontent"></div>
<div class="auth-form">
<div class="card my-5 mx-3">
<div class="card-body" style="direction: rtl">
<div class="text-center mb-4">
<img :src="logo" alt="Logo" class="styled-logo" />
</div>
<h4 class="f-w-500 mb-1">ورود با کد یکبار مصرف</h4>

<p class="mb-3">
@@ -247,3 +275,45 @@ export default {
</div>
<Rightbar />
</template>

<style scoped>
.styled-logo {
display: block;
max-width: 120px; /* Adjust the logo size here */
width: 100%;
height: auto;
border-radius: 10px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
margin: 0 auto 30px;
margin-bottom: 90px;
}

.card {
border: none;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}

.card-body {
padding: 40px;
}

h4 {
font-size: 1.5rem;
font-weight: 500;
}

.text-center {
text-align: center;
}

.mb-3 {
margin-bottom: 1.5rem;
}

.form-control {
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 0.8rem 1rem;
}
</style>

+ 12
- 11
src/views/live-preview/pages/banners/addBanner.vue Прегледај датотеку

@@ -287,17 +287,9 @@
@change="clearError('selectedLoc')"
:class="{ 'is-invalid': errors.selectedLoc }"
placeholder="موقعیت بنر"
:value="(selectedLoc = 'A')"
>
<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>
<option value="A">A-Slideshow</option>
</select>
</div>
<small v-if="errors.selectedLoc" class="text-danger">
@@ -538,6 +530,8 @@ export default {
formData.append("page_id", selectedCatPage.value);
}


if (pageType.value === "brand") {
formData.append("page_id", selectedBrandPage.value);
}
@@ -567,7 +561,14 @@ export default {
formData.append("location", selectedLoc.value);
}
formData.append("panel", pannel.value);
formData.append("page_type", pageType.value);
if (pannel.value == "wholesale") {
formData.append("page_type", null);
}

if (pannel.value !== "wholesale") {
formData.append("page_type", pageType.value);
}

formData.append("image", image.value);
formData.append("sort", 1);



+ 13
- 0
src/views/live-preview/pages/banners/banners.vue Прегледај датотеку

@@ -229,6 +229,8 @@ export default {
<tr>
<th>عکس</th>
<th>عنوان</th>
<th>حالت صفحه</th>
<th>شناسه صفحه</th>
<td>موقعیت</td>
<td>نوع</td>
<th>تاریخ ایجاد</th>
@@ -263,6 +265,17 @@ export default {
</span>
</div>
</td>
<td>
{{
{
category: "دسته‌بندی",
brand: "برند",
special_page: "صفحه ویژه",
main_page: "صفحه اصلی",
}[banner.page_type] || banner.page_type
}}
</td>
<td>{{ banner.page_id || "ندارد" }}</td>
<td>{{ banner.location }}</td>
<td v-if="banner.type === 'slider'">اسلایدر</td>
<td v-if="banner.type === 'banner'">بنر</td>


+ 19
- 6
src/views/live-preview/pages/blogs/blogs.vue Прегледај датотеку

@@ -18,20 +18,34 @@ export default {
const totalPages = ref(1);
const paginate = ref(20);
const page = ref(1);

const filterLoading = ref(false);
const searchQuery = ref("");
const blogs = ref();
let searchTimeout = null; // Store the timeout reference

const convertToJalali = (date) => {
return moment(date, "YYYY-MM-DD HH:mm:ss")
.locale("fa")
.format("YYYY/MM/DD");
};

// A function to handle search with a delay before making the API call
const handleSearchChange = () => {
// Clear the previous timeout
clearTimeout(searchTimeout);

// Set a new timeout to trigger the API call after 500ms delay
searchTimeout = setTimeout(() => {
getBlogs();
page.value = 1; // Reset page to 1 when search query changes
}, 500);
};

// Watch the searchQuery and call the handleSearchChange method
watch(searchQuery, () => {
getBlogs();
page.value = 1;
handleSearchChange();
});

const getBlogs = () => {
filterLoading.value = true;
ApiServiece.get(
@@ -113,9 +127,7 @@ export default {
}
}

watch(searchQuery, () => {
getBlogs();
});

watch(page, () => {
getBlogs();
@@ -156,6 +168,7 @@ export default {
},
};
</script>

<template>
<Layout>
<BRow>


+ 14
- 4
src/views/live-preview/pages/brands/brands.vue Прегледај датотеку

@@ -30,13 +30,25 @@ export default {
const brandTitle = ref();
const brandId = ref();
const brandImage = ref();
let searchTimeout = null;
const convertToJalali = (date) => {
return moment(date, "YYYY-MM-DD HH:mm:ss")
.locale("fa")
.format("YYYY/MM/DD");
};


const handleSearchChange = () => {
clearTimeout(searchTimeout);

searchTimeout = setTimeout(() => {
getBrands();
page.value = 1;
}, 500);
};

watch(searchQuery, () => {
getBrands();
handleSearchChange();
});
const getBrands = () => {
@@ -151,9 +163,7 @@ export default {
return pages;
});

watch(searchQuery, () => {
getBrands();
});

onMounted(() => {
getBrands();


+ 13
- 4
src/views/live-preview/pages/catrgories/cats.vue Прегледај датотеку

@@ -1,5 +1,6 @@
<script>
import Layout from "@/layout/custom.vue";
import { debounce } from "lodash";
import ApiServiece from "@/services/ApiService";
import moment from "jalali-moment";
import { onMounted, ref, watch, computed } from "vue";
@@ -37,13 +38,11 @@ export default {
.locale("fa")
.format("YYYY/MM/DD");
};
watch(searchQuery, () => {
getCats();
});

const getCats = () => {
filterLoading.value = true;
ApiServiece.get(
`admin/categories?title=${searchQuery.value}&paginate=${
`admin/categories?title=${searchQuery.value || ""}&paginate=${
paginate.value || 10
}&page=${page.value || 1}`
)
@@ -91,6 +90,16 @@ export default {
}
}

const debouncedSearch = debounce(() => {
console.log("Searching for:", searchQuery.value);
getCats();
}, 2000);

// Use the watcher to react to changes in `searchQuery`
watch(searchQuery, () => {
debouncedSearch(); // Call the debounced function, no need to pass `newQuery`
});

const nextPage = () => {
if (currentPage.value < totalPages.value) {
page.value++;


+ 8
- 1
src/views/live-preview/pages/comments/comments.vue Прегледај датотеку

@@ -217,7 +217,7 @@ export default {
<div class="col-md-12">
<div class="card shadow-sm border-0 rounded">
<div
class="card-header d-flex justify-content-between align-items-center p-3 "
class="card-header d-flex justify-content-between align-items-center p-3"
dir="rtl"
>
<!-- <div class="d-flex align-items-center">
@@ -236,8 +236,11 @@ export default {
<table class="table table-hover table-bordered m-0" dir="rtl">
<thead class="table-light">
<tr>
<th>نویسنده</th>
<th>عنوان</th>
<th>نام محصول</th>
<td>امتیاز</td>
<th>نظر</th>
<th>وضعیت</th>
@@ -247,11 +250,14 @@ export default {
</thead>
<tbody>
<tr v-for="comment in comments" :key="comment.id">
<td>{{ comment?.user?.name }}</td>
<td v-if="comment?.commentable_type === 'product'">
محصول
</td>
<td v-if="comment?.commentable_type === 'blog'">بلاگ</td>
<td>{{ comment.commentable.title }}</td>
<td>
<span
v-for="n in 5"
@@ -261,6 +267,7 @@ export default {
"
></span>
</td>
<td
data-bs-toggle="modal"
data-bs-target="#showDescription"


+ 4
- 4
src/views/live-preview/pages/discounts/addDiscount.vue Прегледај датотеку

@@ -311,10 +311,10 @@ export default {
});

function convertJalaliToGregorian(jalaliDate) {
return moment(jalaliDate, "jYYYY/jMM/jDD HH:mm:ss").format(
"YYYY-MM-DD HH:mm:ss"
);
}
return moment(jalaliDate, "jYYYY/jMM/jDD HH:mm:ss")
.locale("fa") // Ensure Persian locale
.format("YYYY-MM-DD HH:mm:ss"); // Convert to Gregorian format
}

const submitForm = () => {
startDate.value = convertJalaliToGregorian(startDate.value);


+ 26
- 15
src/views/live-preview/pages/faqs/faqs.vue Прегледај датотеку

@@ -15,11 +15,12 @@ export default {
showDescription,
},
setup() {
const selectedStatus = ref("");
const comment = ref();
const searchPage = ref();
const currentPage = ref(1);
const totalPages = ref(1);
const paginate = ref(5);
const paginate = ref(20);
const page = ref(1);
const filterLoading = ref(false);
const searchQuery = ref("");
@@ -30,16 +31,17 @@ export default {
.locale("fa")
.format("YYYY/MM/DD");
};
watch(searchQuery, () => {
watch( selectedStatus, () => {
getFaqs();
page.value = 1;
});

const getFaqs = () => {
filterLoading.value = true;
ApiServiece.get(
`admin/faqs?title=${searchQuery.value || ""}&paginate=${
paginate.value || 10
}&page=${page.value || 1}`
`admin/faqs?title=${searchQuery.value || ""}&status=${
selectedStatus.value || ""
}&paginate=${paginate.value || 10}&page=${page.value || 1}`
)
.then((resp) => {
filterLoading.value = false;
@@ -160,6 +162,7 @@ export default {
visiblePages,
modalData,
comment,
selectedStatus
};
},
};
@@ -170,18 +173,17 @@ export default {
<div class="col-md-12">
<div class="card shadow-sm border-0 rounded">
<div
class="card-header d-flex justify-content-between align-items-center p-3 "
class="card-header d-flex justify-content-between align-items-center p-3"
dir="rtl"
>
<!-- <div class="d-flex align-items-center">
<input
v-model="searchQuery"
type="text"
placeholder="جستجو..."
class="form-control form-control-sm d-inline-block me-2"
style="width: 250px; border-radius: 15px"
/>
</div> -->
<div class="d-flex align-items-center">
<select class="form-select filter-input" v-model="selectedStatus">
<option value="" disabled selected>وضعیت</option>
<option value="">همه</option>
<option value="confirmed">تایید شده</option>
<option value="rejected">رد شده</option>
</select>
</div>
</div>
<div v-if="!filterLoading" class="card-body table-border-style p-0">
<div class="table-responsive">
@@ -489,4 +491,13 @@ export default {
.modal-body {
text-align: justify;
}
.filter-input {
padding: 10px 16px;
width: 100px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 8px;
margin-right: 16px;
transition: all 0.3s ease;
}
</style>

+ 21
- 4
src/views/live-preview/pages/products/addProduct.vue Прегледај датотеку

@@ -146,7 +146,7 @@
</small>
</BCol>

<BCol md="6">
<BCol md="6" v-if="productType != 1">
<div class="form-group">
<label class="form-label">تعداد در کارتن</label>
<input
@@ -316,7 +316,7 @@

<BCard>
<div class="card-header">
<h5 class="mb-0">اضافه کردن ویژگی ها</h5>
<h5 class="mb-0">اضافه کردن رنگ</h5>
</div>
<BRow class="g-3 mt-2">
<template v-if="attrebutes.length === 0">
@@ -549,7 +549,7 @@
</div>
</div>
</BCardFooter>
<addIdentity :cats="cats" />
<addIdentity :cats="cats" @identity-updated="handleIdentityUpdate" />
<addAttribute
:attributeValues="attributeValues"
@attribute-updated="handleAttributeUpdated()"
@@ -645,6 +645,17 @@ export default {
});
});

const handleIdentityUpdate = () => {
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)
@@ -762,7 +773,7 @@ export default {
if (!selectedBrand.value)
errors.value.selectedBrand = "انتخاب برند برای محصول ضروری می باشد";

if (!countInCarton.value)
if (productType.value != 1 && !countInCarton.value)
errors.value.countInCarton =
"انتخاب تعداد محصول در هر کارتن ضروری می باشد";

@@ -804,6 +815,8 @@ export default {
errors.value[field] = "";
};


onMounted(() => {
getCats();
getBrands();
@@ -812,6 +825,8 @@ export default {
});
let id = "";
const submitForm = () => {
console.log(expire.value , "test")
if (!validateForm()) {
toast.error("لطفا فیلد های لازم را وارد نمایید", {
position: "top-right",
@@ -853,6 +868,7 @@ export default {

if (spescial.value == 1 && isChosen.value == 0) {
formData.append("special_price", parseInt(spescialPrice.value, 10));
formData.append("special_expires_at", expire.value);
}

@@ -991,6 +1007,7 @@ export default {
selectedIdentities,
attributeValues,
handleAttributeUpdated,
handleIdentityUpdate,
};
},
};


+ 2
- 2
src/views/live-preview/pages/products/products.vue Прегледај датотеку

@@ -230,7 +230,7 @@ export default {
<td>مدل</td>
<td>تعداد فروش</td>
<td>برگزیده</td>
<td>قیمت انتخاب شده</td>
<td>تخفیف برگزیده</td>
<td>ویژه</td>
<td>قیمت عمده</td>
<td>قیمت تک</td>
@@ -268,7 +268,7 @@ export default {
</span>
</td>
<td v-if="product?.chosen_price">
{{ formatWithCommas(product?.chosen_price) }}
%{{ formatWithCommas(product?.chosen_price) }}
</td>
<td v-if="!product.chosen_price">
<i


+ 242
- 0
src/views/live-preview/pages/profile/address.vue Прегледај датотеку

@@ -0,0 +1,242 @@
<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-12">
<div id="map" style="height: 300px;"></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, onMounted } from "vue";
import { toast } from "vue3-toastify";
import "vue3-toastify/dist/index.css";
import ApiServiece from "@/services/ApiService";
import L from "leaflet";
import "leaflet/dist/leaflet.css";
export default {
setup() {
const loading = ref(false);
const title = ref();
const address = ref();
const lat = ref(51.505); // Default lat
const long = ref(-0.09); // Default long
const city = ref();
const town = ref();
const postcode = ref();
const errors = ref({});
let map;
let marker;
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,
});
});
};
// Initialize map and marker
onMounted(() => {
map = L.map("map").setView([lat.value, long.value], 13);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
marker = L.marker([lat.value, long.value]).addTo(map);
map.on("click", function (e) {
lat.value = e.latlng.lat;
long.value = e.latlng.lng;
marker.setLatLng(e.latlng);
});
});
return {
clearError,
errors,
createAddress,
address,
lat,
long,
postcode,
town,
city,
title,
loading,
};
},
};
</script>
<style>
#map {
width: 100%;
height: 300px;
}
</style>

+ 12
- 10
src/views/live-preview/pages/profile/profile.vue Прегледај датотеку

@@ -46,7 +46,11 @@ export default {
<Layout>
<BRow>
<BCol class="col-sm-12">
<BCard v-if="addresses.length == 0 " no-body class="alert alert-warning p-0">
<BCard
no-body
class="alert alert-warning p-0"
>
<BCardBody>
<div class="d-flex align-items-center">
<div class="flex-grow-1 me-3">
@@ -102,20 +106,18 @@ export default {
حساب
</span>
</a>
<a
<router-link
class="nav-link list-group-item list-group-item-action"
id="user-set-information-tab"
data-bs-toggle="pill"
href="#user-set-information"
to="/address"
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>
<span class="f-w-500">
<i class="ph-duotone ph-clipboard-text m-r-10"></i> اضافه
کردن آدرس
</span>
</router-link>
<a
class="nav-link list-group-item list-group-item-action"
id="user-list-address-tab"


+ 232
- 0
src/views/live-preview/pages/settings/setting.vue Прегледај датотеку

@@ -0,0 +1,232 @@
<script setup>
import { toast } from "vue3-toastify";
import "vue3-toastify/dist/index.css";
import moment from "jalali-moment";
import { ref, onMounted, nextTick } from "vue";
import Layout from "@/layout/custom.vue";
import "dropzone/dist/dropzone.css";
import Quill from "quill";
import "quill/dist/quill.snow.css";
import ApiService from "@/services/ApiService";
import DatePicker from "vue3-persian-datetime-picker";

const settings = ref([]);
const editors = ref({});
const loading = ref({}); // Track loading state per setting

const getSettings = async () => {
try {
const response = await ApiService.get("admin/settings");
settings.value = response.data.data;
settings.value.forEach((setting) => {
if (setting.type === "image" && setting.value) {
setting.preview = setting.value;
localStorage.setItem("logo" , setting.value)
}

loading.value[setting.id] = false;
});

await nextTick();
initQuillEditors();
} catch (error) {
console.error("Error fetching settings:", error);
}
};

const initQuillEditors = () => {
settings.value.forEach((setting) => {
if (setting.type === "longtext") {
const quillContainer = document.getElementById(`editor-${setting.id}`);
if (quillContainer) {
const quill = new Quill(quillContainer, {
theme: "snow",
modules: {
toolbar: [
[{ header: "1" }, { header: "2" }, { font: [] }],
[{ list: "ordered" }, { list: "bullet" }],
[{ align: [] }],
["bold", "italic", "underline"],
["link", "image"],
[{ script: "sub" }, { script: "super" }],
[{ direction: "rtl" }],
],
},
});

quill.root.setAttribute("dir", "rtl");
quill.format("direction", "rtl");

quill.root.innerHTML = setting.value || "";
quill.on("text-change", () => {
setting.value = quill.root.innerHTML;
});

editors.value[setting.id] = quill;
}
}
});
};

const previewImage = (event, setting) => {
const file = event.target.files[0];
if (file) {
setting.preview = URL.createObjectURL(file);
setting.file = file;
}
};

const saveSetting = async (setting) => {
loading.value[setting.id] = true;
try {
if (setting.type === "image" && setting.preview) {
const formData = new FormData();
formData.append("value", setting.file);
formData.append("_method", "put");

await ApiService.post(`admin/settings/${setting.id}`, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
} else if (setting.type === "date" && setting.value) {
const georgianDate = moment(
setting.value,
"jYYYY/jMM/jDD HH:mm:ss"
).format("YYYY-MM-DD HH:mm:ss");
await ApiService.put(`admin/settings/${setting.id}`, {
value: georgianDate,
});
} else {
await ApiService.put(`admin/settings/${setting.id}`, {
value: setting.value,
});
}
getSettings()
toast.success("!تنظیمات با موفیت ذخیره شد", {
position: "top-right",
autoClose: 1000,
});
} catch (error) {
console.error("Error saving setting:", error);
toast.error(`${error?.response?.data?.message}`, {
position: "top-right",
autoClose: 1000,
});
} finally {
loading.value[setting.id] = false;
}
};

onMounted(() => {
getSettings();
});
</script>

<style>
.vue-dropzone {
border: 2px dashed #dbe0e5;
}
.quill-editor {
min-height: 150px;
}
</style>

<template>
<Layout>
<BRow>
<div class="col-sm-12">
<BCard no-body>
<BCardHeader>
<h5>تنظیمات سایت</h5>
</BCardHeader>
<BCardBody>
<div
v-for="setting in settings"
:key="setting.id"
class="mb-3 row align-items-center"
>
<label class="col-form-label col-sm-3 text-sm-end">
{{ setting.key }}
</label>
<div class="col-lg-6 col-sm-7">
<template v-if="setting.type === 'longtext'">
<div class="form-group">
<div
:id="'editor-' + setting.id"
class="quill-editor"
></div>
</div>
</template>

<template v-else-if="setting.type === 'string'">
<input
type="text"
v-model="setting.value"
class="form-control"
/>
</template>

<template v-else-if="setting.type === 'number'">
<input
type="text"
v-model="setting.value"
class="form-control"
/>
</template>

<template v-else-if="setting.type === 'date'">
<DatePicker
v-model="setting.value"
type="datetime"
:format="'jYYYY/jMM/jDD HH:mm:ss'"
class="form-control"
/>
</template>

<template v-else-if="setting.type === 'image'">
<div class="form-group">
<input
type="file"
accept="image/*"
class="form-control"
@change="previewImage($event, setting)"
/>
<!-- Wrap image preview in a container to control layout -->
<div
v-if="setting.preview"
class="mt-2 d-flex justify-content-end"
>
<img
:src="setting.preview"
alt="Preview"
class="img-thumbnail"
width="150"
/>
</div>
</div>
</template>
</div>

<!-- Button Container -->
<div class="col-lg-3 col-sm-2 text-start">
<button
class="btn btn-primary"
@click="saveSetting(setting)"
:disabled="loading[setting.id]"
>
<span
v-if="loading[setting.id]"
class="spinner-border spinner-border-sm"
></span>
<span v-else>ذخیره</span>
</button>
</div>
</div>
</BCardBody>
</BCard>
</div>
</BRow>
</Layout>
</template>

+ 54
- 33
src/views/live-preview/pages/users/users.vue Прегледај датотеку

@@ -1,4 +1,5 @@
<script>
import { debounce } from "lodash";
import Layout from "@/layout/custom.vue";
import { onMounted, ref, watch, computed } from "vue";
import Swal from "sweetalert2";
@@ -27,6 +28,7 @@ export default {
const userMobile = ref();
const userId = ref();
const userRole = ref();
const selectedRole = ref("");
const convertToJalali = (date) => {
return moment(date, "YYYY-MM-DD HH:mm:ss")
.locale("fa")
@@ -37,7 +39,7 @@ export default {
ApiServiece.get(
`admin/users?name=${searchQuery.value || ""}&paginate=${
paginate.value || 10
}&page=${page.value || 1}`
}&page=${page.value || 1}&role=${selectedRole.value || "admin"}`
).then((resp) => {
console.log(resp.data.data);
users.value = resp.data.data.data;
@@ -80,10 +82,21 @@ export default {
return pages;
});

watch(searchQuery, () => {
watch(selectedRole, () => {
getUsers();
});


watch(searchQuery, (newQuery) => {
debouncedSearch(newQuery);
});

const debouncedSearch = debounce((query) => {
console.log("Searching for:", query);
getUsers();
}, 2000);

watch(page, () => {
getUsers();
});
@@ -199,6 +212,7 @@ export default {
visiblePages,
page,
searchPage,
selectedRole,
};
},
};
@@ -211,7 +225,8 @@ export default {
<div class="card table-card user-profile-list">
<div class="card-body">
<div class="filter-container">
<div class="search-filters">
<div class="search-filters d-flex align-items-center gap-3">
<!-- Search Input -->
<input
type="text"
class="form-control search-input"
@@ -220,32 +235,34 @@ export default {
v-model="searchQuery"
/>

<!-- <div class="dropdown">
<button
class="btn btn-dropdown"
type="button"
id="filtersDropdown"
data-bs-toggle="dropdown"
aria-expanded="false"
>
فیلترها
</button>
<ul class="dropdown-menu" aria-labelledby="filtersDropdown">
<li><a class="dropdown-item" href="#">همه کاربران</a></li>
<li><a class="dropdown-item" href="#">فقط مدیران</a></li>
<li><a class="dropdown-item" href="#">فقط مشتریان</a></li>
<li><a class="dropdown-item" href="#">فعال</a></li>
<li><a class="dropdown-item" href="#">بلاک</a></li>
</ul>
</div> -->
<button
data-bs-toggle="modal"
data-bs-target="#addUser"
class="btn btn-add-user me-3"
<!-- User Role Selector -->
<select class="form-select filter-input" v-model="selectedRole">
<option value="" disabled selected>نقش کاربر</option>
<option value="">همه</option>
<option value="admin">فقط مدیران</option>
<option value="client">فقط مشتریان</option>
<option value="operator">فقط اپراتورها</option>
</select>

<!-- User Status Selector -->
<!-- <select
v-model="selectedStatus"
class="form-select filter-input"
>
افزودن کاربر
</button>
<option value="" disabled selected>انتخاب وضعیت کاربر</option>
<option value="active">فعال</option>
<option value="blocked">بلاک</option>
</select> -->

<!-- Add User Button -->
</div>
<button
data-bs-toggle="modal"
data-bs-target="#addUser"
class="btn btn-add-user btn btn-light text-primary btn-sm px-3"
>
افزودن کاربر
</button>
</div>

<div class="table-responsive">
@@ -453,6 +470,15 @@ export default {
transition: all 0.3s ease;
}

.filter-input {
padding: 10px 16px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 8px;
margin-right: 16px;
transition: all 0.3s ease;
}

.search-input:focus {
outline: none;
border-color: #007bff;
@@ -493,10 +519,9 @@ export default {
}

.btn-add-user {
display: flex;
width: 100px;
align-items: center;
padding: 10px 20px;
background-color: #007bff;
color: white;
font-size: 14px;
border-radius: 8px;
@@ -508,10 +533,6 @@ export default {
margin-right: 8px;
}

.btn-add-user:hover {
background-color: #0056b3;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.search-number {
display: flex;
align-items: center;


Loading…
Откажи
Сачувај