Parcourir la source

admin panel updates

master
unknown il y a 10 mois
Parent
révision
1ad3d6b865
28 fichiers modifiés avec 941 ajouts et 384 suppressions
  1. +3
    -0
      src/assets/scss/themes/layouts/_style-dark.scss
  2. +2
    -2
      src/components/customSidebar.vue
  3. +10
    -8
      src/components/modals/Brands/addBrand.vue
  4. +5
    -0
      src/components/modals/addUser.vue
  5. +2
    -0
      src/components/modals/attribute/addAttribute.vue
  6. +2
    -0
      src/components/modals/blogCat/addBlogCat.vue
  7. +10
    -2
      src/components/modals/categories/addCat.vue
  8. +1
    -0
      src/components/modals/editUser.vue
  9. +46
    -45
      src/components/modals/identity/addIdentity.vue
  10. +0
    -15
      src/components/modals/profile/addAddress.vue
  11. +18
    -10
      src/views/live-preview/pages/attributes/attributes.vue
  12. +24
    -37
      src/views/live-preview/pages/auth2/otpLogin.vue
  13. +24
    -18
      src/views/live-preview/pages/banners/addBanner.vue
  14. +63
    -14
      src/views/live-preview/pages/banners/banners.vue
  15. +19
    -17
      src/views/live-preview/pages/blogCats/blogCat.vue
  16. +4
    -3
      src/views/live-preview/pages/blogs/blogs.vue
  17. +4
    -3
      src/views/live-preview/pages/brands/brands.vue
  18. +12
    -19
      src/views/live-preview/pages/calls/calls.vue
  19. +20
    -21
      src/views/live-preview/pages/catrgories/cats.vue
  20. +132
    -20
      src/views/live-preview/pages/comments/comments.vue
  21. +156
    -12
      src/views/live-preview/pages/discounts/discounts.vue
  22. +7
    -6
      src/views/live-preview/pages/faqs/faqs.vue
  23. +68
    -16
      src/views/live-preview/pages/identity/idenities.vue
  24. +77
    -66
      src/views/live-preview/pages/orders/approvedOrders.vue
  25. +28
    -5
      src/views/live-preview/pages/orders/orders.vue
  26. +141
    -24
      src/views/live-preview/pages/products/products.vue
  27. +6
    -6
      src/views/live-preview/pages/profile/profile.vue
  28. +57
    -15
      src/views/live-preview/pages/users/users.vue

+ 3
- 0
src/assets/scss/themes/layouts/_style-dark.scss Voir le fichier

@@ -6,6 +6,8 @@
--bs-body-bg: #{$dark-layout-color};
--bs-body-bg-rgb: #{to-rgb($dark-layout-color)};
--pc-heading-color: rgba(255, 255, 255, 0.8);


// Navbar
--pc-sidebar-background: #1D2630;
@@ -13,6 +15,7 @@
--pc-sidebar-color-rgb: #{to-rgb(#FFFFFF)};
--pc-sidebar-submenu-border-color: var(--bs-gray-600);
--pc-sidebar-caption-color: #748892;
--vs-text-color: #18181b;

// header
--pc-header-background: rgba(#{var(--bs-body-bg-rgb)}, 0.7);


+ 2
- 2
src/components/customSidebar.vue Voir le fichier

@@ -405,7 +405,7 @@ export default {
</li>

<!-- دسته ها -->
<li class="pc-item pc-hasmenu">
<!-- <li class="pc-item pc-hasmenu">
<BLink
class="pc-link"
data-bs-toggle="collapse"
@@ -442,7 +442,7 @@ export default {
</li>
</ul>
</div>
</li>
</li> -->

<li
class="pc-item"


+ 10
- 8
src/components/modals/Brands/addBrand.vue Voir le fichier

@@ -219,22 +219,24 @@ export default {
console.log(resp);
toast.success("!برند با موفقیت اضافه شد", {
position: "top-right",
autoClose: 1000
autoClose: 1000,
});
})
.then(()=>{
.then(() => {
setTimeout(() => {
document.getElementById("close").click();
emit("brand-updated");
}, 500);
document.getElementById("close").click();
emit("brand-updated");
title.value = "";
description.value = "";
image.value = null;
imagePreview.value = null;
}, 500);
})
.catch((error) => {
console.error(error);
toast.error("!مشکلی در ایجاد برند پیش آمد", {
position: "top-right",
autoClose: 1000
autoClose: 1000,
});
})
.finally(() => {


+ 5
- 0
src/components/modals/addUser.vue Voir le fichier

@@ -202,6 +202,11 @@ export default {
position: "top-right",
autoClose: 1000,
});
mobile.value = "";
name.value = "";
role.value = "";
password.value = "";
repeatPassword.value = "";
})
.then(() => {
setTimeout(() => {


+ 2
- 0
src/components/modals/attribute/addAttribute.vue Voir le fichier

@@ -165,6 +165,8 @@ export default {
setTimeout(() => {
document.getElementById("close").click();
emit("attribute-updated");
colorName.value = "";
colorCode.value = "";
}, 500);
})
.catch((error) => {


+ 2
- 0
src/components/modals/blogCat/addBlogCat.vue Voir le fichier

@@ -160,6 +160,8 @@ export default {
setTimeout(() => {
document.getElementById("closeAddBlogCat").click();
emit("cat-updated");
title.value = "";
selectedIcon.value = "";
}, 500);
})
.catch((error) => {


+ 10
- 2
src/components/modals/categories/addCat.vue Voir le fichier

@@ -101,8 +101,11 @@
<BCol lg="6">
<div class="form-group">
<label class="form-label">انتخاب پدر</label>
<select v-model="selectedPaernt" class="form-control" placeholder="انتخاب کنید">
<select
v-model="selectedPaernt"
class="form-control"
placeholder="انتخاب کنید"
>
<option
v-for="parent in localParents"
:key="parent.id"
@@ -301,6 +304,11 @@ export default {
setTimeout(() => {
document.getElementById("close").click();
emit("cat-updated");
title.value = "";
imagePreview.value = "";
image.value = null;
description.value = "";
selectedIcon.value = "";
}, 500);
})
.catch((error) => {


+ 1
- 0
src/components/modals/editUser.vue Voir le fichier

@@ -214,6 +214,7 @@ export default {
autoClose: 1000,
onClose: () => emit("user-updated"),
});
})
.then(() => {
setTimeout(() => {


+ 46
- 45
src/components/modals/identity/addIdentity.vue Voir le fichier

@@ -139,51 +139,52 @@ export default {
};

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

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

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

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

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

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

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

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

return {
errors,


+ 0
- 15
src/components/modals/profile/addAddress.vue Voir le fichier

@@ -204,21 +204,6 @@ export default {
console.log(resp);
addrerssId.value = resp.data.data.id;
})
.then(() => {
if (!localShowCard.value && localOrderId.value) {
ApiServiece.post(
`wholesale/orders/${localOrderId.value}/addresses`,
{
user_address_id: addrerssId.value,
}
).then(() => {
toast.success("!این آدرس به سفارش اضافه گردید", {
position: "top-right",
autoClose: 1000,
});
});
}
})
.catch(() => {
loading.value = false;
toast.error("!مشکلی در ایجاد آدرس بوجود آمد", {


+ 18
- 10
src/views/live-preview/pages/attributes/attributes.vue Voir le fichier

@@ -28,13 +28,22 @@ export default {
const attributeTitle = ref();
const attributeId = ref();
const attributeCode = ref();
let searchTimeout = null;
const convertToJalali = (date) => {
return moment(date, "YYYY-MM-DD HH:mm:ss")
.locale("fa")
.format("YYYY/MM/DD");
};
const handleSearchChange = () => {
clearTimeout(searchTimeout);

searchTimeout = setTimeout(() => {
getAttributes();
page.value = 1;
}, 500);
};
watch(searchQuery, () => {
getAttributes();
handleSearchChange();
});
const getAttributes = () => {
filterLoading.value = true;
@@ -47,7 +56,7 @@ export default {
)
.then((resp) => {
filterLoading.value = false;
console.log(resp.data)
console.log(resp.data);
attributes.value = resp.data.data.data;
currentPage.value = resp.data.data.current_page;
totalPages.value = resp.data.data.last_page;
@@ -150,13 +159,11 @@ export default {
attributeCode.value = code;
};

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

const getAttributeValues = () => {
ApiServiece.get(`admin/attributes`).then((resp) => {
console.log(resp)
console.log(resp);
attributeValues.value = resp.data.data;
});
};
@@ -196,7 +203,7 @@ export default {
<div class="col-md-12">
<div class="card shadow-sm border-0 rounded">
<div
class="card-header d-flex justify-content-between align-items-center p-3 "
class="card-header d-flex justify-content-between align-items-center p-3"
dir="rtl"
>
<div class="d-flex align-items-center">
@@ -207,14 +214,15 @@ export default {
class="form-control form-control-sm d-inline-block me-2"
style="width: 250px; border-radius: 15px"
/>
<button
</div>
<button
data-bs-toggle="modal"
data-bs-target="#addAttribute"
class="btn btn-light text-primary btn-sm px-3"
>
افزودن رنگ
</button>
</div>
</div>
<div v-if="!filterLoading" class="card-body table-border-style p-0">
<div class="table-responsive">
@@ -223,7 +231,7 @@ export default {
<tr>
<th>نام</th>
<th>رنگ</th>
<td>کد رنگ</td>
<th>کد رنگ</th>
<th>تاریخ ایجاد</th>
<th>عملیات</th>
</tr>


+ 24
- 37
src/views/live-preview/pages/auth2/otpLogin.vue Voir le fichier

@@ -20,7 +20,7 @@ export default {
const store = useStore();
const mobile = ref("");
const otpSent = ref(false);
const timer = ref(120);
const timer = ref(60);
const otpCode = ref("");
const resendAvailable = ref(false);
let timerInterval = null;
@@ -28,9 +28,8 @@ export default {
const sendOtp = () => {
if (!validateSendOtpForm()) return;
sendOtpLoading.value = true;
ApiServiece.post(`auth/send-otp`, { mobile: mobile.value })
.then((resp) => {
console.log(resp.data);
ApiServiece.post("auth/send-otp", { mobile: mobile.value })
.then(() => {
otpSent.value = true;
resendAvailable.value = false;
sendOtpLoading.value = false;
@@ -43,25 +42,14 @@ export default {
startTimer();
})
.catch((err) => {
sendOtpLoading.value = false;
console.log(err);
const status = err.status;
if (status == 429) {
Swal.fire({
icon: "error",
title: "تعداد تلاش‌ها زیاد شد",
text: "لطفاً بعداً دوباره امتحان کنید",
confirmButtonText: "باشه",
});
}
if (status == 400) {
Swal.fire({
icon: "warning",
title: "کد تایید قبلاً ارسال شده",
text: "برای این شماره، کد ورود قبلاً ارسال شده است. لطفاً کمی صبر کنید تا کد قبلی منقضی شود",
confirmButtonText: "باشه",
});
}
Swal.fire({
icon: "error",
title: "خطا در ارسال کد",
text: err.response.data.message,
confirmButtonText: "متوجه شدم",
});
sendOtpLoading.value = false;
});
};

@@ -77,10 +65,11 @@ export default {
};

const resendOtp = () => {
otpSent.value = false;
otpCode.value = "";
timer.value = 120;
timer.value = 60;
resendAvailable.value = false;
sendOtp(); // Resend OTP
startTimer(); // Restart the countdown
};

const verifyOtp = async () => {
@@ -91,14 +80,13 @@ export default {
mobile: mobile.value,
otpCode: otpCode.value,
});
console.log("worked");
verifyOtpLoading.value = false;
router.push({ name: "dashPage" });
router.push({ name: "products" });
} catch (error) {
verifyOtpLoading.value = false;
Swal.fire({
icon: "error",
title: "اوه! انگار چیزی اشتباه شد",
title: "انگار چیزی اشتباه شد",
text: `${error.message}`,
confirmButtonText: "باشه",
});
@@ -243,7 +231,7 @@ export default {

<div class="d-grid mt-3">
<button
v-if="!otpSent && !resendAvailable"
v-if="!otpSent"
@click="sendOtp"
type="button"
class="btn btn-primary"
@@ -258,15 +246,14 @@ export default {
<span v-else> ارسال کد ورود </span>
</button>

<div class="d-grid mt-3" v-if="resendAvailable && otpSent">
<button
@click="resendOtp"
type="button"
class="btn btn-primary"
>
ارسال مجدد کد
</button>
</div>
<button
v-if="resendAvailable && otpSent"
@click="resendOtp"
type="button"
class="btn btn-primary"
>
ارسال مجدد کد
</button>
</div>
</div>
</div>


+ 24
- 18
src/views/live-preview/pages/banners/addBanner.vue Voir le fichier

@@ -192,6 +192,7 @@
v-model="selectedLandingProduct"
:options="formattedProducts"
placeholder="محصولی را انتخاب کنید"
@search="handleSearch"
/>
<small v-if="errors.selectedLandingProduct" class="text-danger">
{{ errors.selectedLandingProduct }}
@@ -436,16 +437,30 @@ export default {
});
};

const getProduct = () => {
ApiServiece.get(`admin/products`)
.then((resp) => {
products.value = resp.data.data;
})
.catch((err) => {
console.log(err);
});
const handleSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;

try {
const response = await ApiServiece.get(
`admin/products?title=${searchTerm}`
);
products.value = response.data.data;
console.log(products.value, "products");
} catch (error) {
console.error("Error fetching products:", error);
products.value = []; // ✅ Reset to an empty array on error
}
};

const formattedProducts = computed(() =>
Array.isArray(products.value) // ✅ Check if products.value is an array
? products.value.map((product) => ({
value: product.id,
label: product.title,
}))
: []
);

const handleImageUpload = (event) => {
const file = event.target.files[0];

@@ -462,13 +477,6 @@ export default {
}
};

const formattedProducts = computed(() => {
return products.value.map((product) => ({
value: product.id,
label: product.title,
}));
});

const validateForm = () => {
errors.value = {};
if (!title.value) errors.value.title = "وارد کردن عنوان بنر الزامی است";
@@ -510,7 +518,6 @@ export default {
onMounted(() => {
getCats();
getBrands();
getProduct();
});

const submitForm = () => {
@@ -530,8 +537,6 @@ export default {
formData.append("page_id", selectedCatPage.value);
}


if (pageType.value === "brand") {
formData.append("page_id", selectedBrandPage.value);
}
@@ -631,6 +636,7 @@ export default {
imagePreview,
loading,
brands,
handleSearch,
};
},
};


+ 63
- 14
src/views/live-preview/pages/banners/banners.vue Voir le fichier

@@ -16,6 +16,8 @@ export default {
showDescription,
},
setup() {
const selectedPageType = ref("");
let searchTimeout = null;
const searchPage = ref();
const currentPage = ref(1);
const totalPages = ref(1);
@@ -34,13 +36,26 @@ export default {
.locale("fa")
.format("YYYY/MM/DD");
};

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

searchTimeout = setTimeout(() => {
getBanners();
page.value = 1;
}, 500);
};
watch(searchQuery, () => {
getBanners();
handleSearchChange();
});
const getBanners = () => {
filterLoading.value = true;
ApiServiece.get(
`admin/banners?paginate=${paginate.value || 10}&page=${page.value || 1}`
`admin/banners?paginate=${paginate.value || 10}&page=${
page.value || 1
}&title=${searchQuery.value || ""}&page_type=${
selectedPageType.value || ""
}`
)
.then((resp) => {
filterLoading.value = false;
@@ -136,11 +151,11 @@ export default {
catDescription.value = desc;
};

watch(searchQuery, () => {
watch(page, () => {
getBanners();
});

watch(page, () => {
watch(selectedPageType, () => {
getBanners();
});

@@ -201,6 +216,7 @@ export default {
handlePageInput,
searchPage,
visiblePages,
selectedPageType,
};
},
};
@@ -211,28 +227,53 @@ export default {
<div class="col-md-12">
<div class="card shadow-sm border-0 rounded">
<div
class="card-header d-flex justify-content-between align-items-center p-3"
class="card-header d-flex justify-content-between align-items-center p-3 shadow-sm rounded"
dir="rtl"
>
<div class="d-flex align-items-center">
<router-link to="/addBanner">
<button class="btn btn-light text-primary btn-sm px-3">
افزودن بنر
</button>
</router-link>
<div class="d-flex align-items-center gap-2">
<input
v-model="searchQuery"
type="text"
placeholder="جستجو..."
class="form-control form-control-sm"
style="
width: 250px;
border-radius: 15px;
border: 1px solid #ddd;
"
/>

<select
class="form-select form-select-sm"
v-model="selectedPageType"
style="width: 120px; border-radius: 15px"
>
<option value="" disabled selected>نوع صفحه</option>
<option value="">همه</option>
<option value="special_page">صفحه ویژه</option>
<option value="category">دسته بندی</option>
<option value="brand">برند</option>
</select>
</div>

<router-link to="/addBanner">
<button class="btn btn-light text-primary btn-sm px-3">
افزودن بنر
</button>
</router-link>
</div>

<div v-if="!filterLoading" class="card-body table-border-style p-0">
<div class="table-responsive">
<table class="table table-hover table-bordered m-0" dir="rtl">
<thead class="table-light">
<thead class="table">
<tr>
<th>عکس</th>
<th>عنوان</th>
<th>حالت صفحه</th>
<th>شناسه صفحه</th>
<td>موقعیت</td>
<td>نوع</td>
<th>موقعیت</th>
<th>نوع</th>
<th>تاریخ ایجاد</th>
<th>عملیات</th>
</tr>
@@ -483,4 +524,12 @@ export default {
cursor: pointer;
user-select: none;
}
.filter-input {
padding: 10px 16px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 8px;
margin-right: 16px;
transition: all 0.3s ease;
}
</style>

+ 19
- 17
src/views/live-preview/pages/blogCats/blogCat.vue Voir le fichier

@@ -2,7 +2,7 @@
import Layout from "@/layout/custom.vue";
import ApiServiece from "@/services/ApiService";
import moment from "jalali-moment";
import { onMounted, ref, watch , computed } from "vue";
import { onMounted, ref, watch, computed } from "vue";
import { toast } from "vue3-toastify";
import "vue3-toastify/dist/index.css";
import Swal from "sweetalert2";
@@ -17,6 +17,7 @@ export default {
editBlogCat,
},
setup() {
let searchTimeout = null;
const searchPage = ref();
const currentPage = ref(1);
const totalPages = ref(1);
@@ -34,8 +35,17 @@ export default {
.locale("fa")
.format("YYYY/MM/DD");
};

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

searchTimeout = setTimeout(() => {
getCats();
page.value = 1;
}, 500);
};
watch(searchQuery, () => {
getCats();
handleSearchChange();
});
const getCats = () => {
filterLoading.value = true;
@@ -49,14 +59,12 @@ export default {
cats.value = resp.data.data.data;
currentPage.value = resp.data.data.current_page;
totalPages.value = resp.data.data.last_page;
})
.catch(() => {
filterLoading.value = false;
});
};


const visiblePages = computed(() => {
const pages = [];
if (totalPages.value <= 5) {
@@ -137,7 +145,6 @@ export default {
};

const editModalData = (id, title, icon) => {
catId.value = id;
catTitle.value = title;
catIcon.value = icon;
@@ -147,11 +154,6 @@ export default {
getCats();
});


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

onMounted(() => {
getCats();
});
@@ -195,14 +197,14 @@ export default {
class="form-control form-control-sm d-inline-block me-2"
style="width: 250px; border-radius: 15px"
/>
<button
data-bs-toggle="modal"
data-bs-target="#addBlogCat"
class="btn btn-light text-primary btn-sm px-3"
>
افزودن دسته
</button>
</div>
<button
data-bs-toggle="modal"
data-bs-target="#addBlogCat"
class="btn btn-light text-primary btn-sm px-3"
>
افزودن دسته
</button>
</div>
<div v-if="!filterLoading" class="card-body table-border-style p-0">
<div class="table-responsive">


+ 4
- 3
src/views/live-preview/pages/blogs/blogs.vue Voir le fichier

@@ -186,13 +186,14 @@ export default {
class="form-control form-control-sm d-inline-block me-2"
style="width: 250px; border-radius: 15px"
/>
<router-link
</div>
<router-link
to="/addBlog"
class="btn btn-light text-primary btn-sm px-3"
>
افزودن بلاگ
</router-link>
</div>
</div>
<div v-if="!filterLoading" class="card-body table-border-style p-0">
<div class="table-responsive">
@@ -201,7 +202,7 @@ export default {
<tr>
<th>عکس</th>
<th>عنوان</th>
<td>کلمه کلیدی</td>
<th>کلمه کلیدی</th>
<th>تاریخ ایجاد</th>
<th>عملیات</th>
</tr>


+ 4
- 3
src/views/live-preview/pages/brands/brands.vue Voir le fichier

@@ -211,14 +211,15 @@ export default {
class="form-control form-control-sm d-inline-block me-2"
style="width: 250px; border-radius: 15px"
/>
<button
</div>
<button
data-bs-toggle="modal"
data-bs-target="#addBrand"
class="btn btn-light text-primary btn-sm px-3"
>
افزودن برند
</button>
</div>
</div>
<div v-if="!filterLoading" class="card-body table-border-style p-0">
<div class="table-responsive">
@@ -227,7 +228,7 @@ export default {
<tr>
<th>عکس</th>
<th>عنوان</th>
<td>توضیحات</td>
<th>توضیحات</th>
<th>تاریخ ایجاد</th>
<th>عملیات</th>
</tr>


+ 12
- 19
src/views/live-preview/pages/calls/calls.vue Voir le fichier

@@ -16,10 +16,10 @@ export default {
const searchPage = ref();
const currentPage = ref(1);
const totalPages = ref(1);
const paginate = ref(5);
const paginate = ref(20);
const page = ref(1);
const filterLoading = ref(false);
const selectedStatus = ref();
const selectedStatus = ref("");
const calls = ref();
const callText = ref();
const convertToJalali = (date) => {
@@ -35,7 +35,7 @@ export default {
filterLoading.value = true;
ApiServiece.get(
`admin/forms?status=${selectedStatus.value || ""}&paginate=${
paginate.value || 10
paginate.value || 20
}&page=${page.value || 1}`
)
.then((resp) => {
@@ -223,13 +223,13 @@ export default {
dir="rtl"
>
<div class="d-flex align-items-center">
<label for="statusSelect" class="form-label me-2"> وضعیت </label>
<select
class="form-select form-select-sm"
v-model="selectedStatus"
id="statusSelect"
class="form-control form-control-sm d-inline-block me-2"
style="width: 250px; border-radius: 15px"
style="width: 120px; border-radius: 15px"
>
<option value="" disabled selected>وضعیت</option>
<option value="">همه</option>
<option value="answered">پاسخ داده شده</option>
<option value="waiting">در انتظار</option>
</select>
@@ -259,18 +259,11 @@ export default {
></i>
</td>
<td>{{ call?.subject }}</td>
<td>
<textarea
:value="call.text"
disabled
readonly
style="
width: 100%;
resize: none;
border: none;
background: transparent;
"
></textarea>
<td
class="text-center"
style=" background: transparent"
>
{{ call.text }}
</td>
<td>
<span class="badge" :class="getStatusClass(call.status)">


+ 20
- 21
src/views/live-preview/pages/catrgories/cats.vue Voir le fichier

@@ -1,6 +1,6 @@
<script>
import Layout from "@/layout/custom.vue";
import { debounce } from "lodash";
import ApiServiece from "@/services/ApiService";
import moment from "jalali-moment";
import { onMounted, ref, watch, computed } from "vue";
@@ -19,6 +19,7 @@ export default {
editCat,
},
setup() {
let searchTimeout = null;
const catIcon = ref();
const searchPage = ref();
const currentPage = ref(1);
@@ -90,16 +91,6 @@ export default {
}
}

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

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

const nextPage = () => {
if (currentPage.value < totalPages.value) {
page.value++;
@@ -160,10 +151,18 @@ export default {
catDescription.value = desc;
};

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

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

watch(searchQuery, () => {
getCats();
handleSearchChange();
});

const restoreCat = (id, title) => {
Swal.fire({
text: `می خواهید دسته ${title} را بازیابی کنید؟`,
@@ -244,14 +243,14 @@ export default {
class="form-control form-control-sm d-inline-block me-2"
style="width: 250px; border-radius: 15px"
/>
<button
data-bs-toggle="modal"
data-bs-target="#addCat"
class="btn btn-light text-primary btn-sm px-3"
>
افزودن دسته
</button>
</div>
<button
data-bs-toggle="modal"
data-bs-target="#addCat"
class="btn btn-light text-primary btn-sm px-3"
>
افزودن دسته
</button>
</div>
<div v-if="!filterLoading" class="card-body table-border-style p-0">
<div class="table-responsive">
@@ -260,7 +259,7 @@ export default {
<tr>
<th>عکس</th>
<th>عنوان</th>
<td>توضیحات</td>
<th>توضیحات</th>
<th>تاریخ ایجاد</th>
<th>پدر</th>
<th>عملیات</th>


+ 132
- 20
src/views/live-preview/pages/comments/comments.vue Voir le fichier

@@ -2,6 +2,7 @@
import Layout from "@/layout/custom.vue";
import ApiServiece from "@/services/ApiService";
import moment from "jalali-moment";
import VueSelect from "vue3-select-component";
import { onMounted, ref, watch, computed } from "vue";
import { toast } from "vue3-toastify";
import "vue3-toastify/dist/index.css";
@@ -12,16 +13,22 @@ export default {
name: "BORDER",
components: {
Layout,
VueSelect,
showDescription,
},
setup() {
const products = ref([]);
const selectedProduct = ref();
const blogs = ref([]);
const selectedBlog = ref();
const selectedCommentType = ref("");
const comment = ref();
const searchPage = ref();
const currentPage = ref(1);
const totalPages = ref(1);
const paginate = ref(20);
const page = ref(1);
const selectedStatus = ref("");
const filterLoading = ref(false);
const searchQuery = ref("");
const comments = ref();
@@ -38,9 +45,11 @@ export default {
const getComments = () => {
filterLoading.value = true;
ApiServiece.get(
`admin/comments?title=${searchQuery.value || ""}&paginate=${
paginate.value || 10
}&page=${page.value || 1}`
`admin/comments?type=${selectedCommentType.value || ""}&status=${
selectedStatus.value || ""
}&commentable_id=${
selectedBlog.value ?? selectedProduct.value ?? ""
}&paginate=${paginate.value || 10}&page=${page.value || 1}`
)
.then((resp) => {
filterLoading.value = false;
@@ -54,6 +63,50 @@ export default {
});
};

const handleBlogSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;

try {
const response = await ApiServiece.get(
`admin/blogs?title=${searchTerm}`
);
blogs.value = response.data.data;
} catch (error) {
blogs.value = [];
}
};

const formattedBlog = computed(() =>
Array.isArray(blogs.value)
? blogs.value.map((blog) => ({
value: blog.id,
label: blog.title,
}))
: []
);

const handleProductsSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;

try {
const response = await ApiServiece.get(
`admin/products?title=${searchTerm}`
);
products.value = response.data.data;
} catch (error) {
products.value = [];
}
};

const formattedProducts = computed(() =>
Array.isArray(products.value)
? products.value.map((product) => ({
value: product.id,
label: product.title,
}))
: []
);

const modalData = (text) => {
comment.value = text;
};
@@ -173,6 +226,20 @@ export default {
getComments();
});

watch(selectedStatus, () => {
getComments();
});

watch(selectedCommentType, () => {
selectedProduct.value = "";
selectedBlog.value = "";
getComments();
});

watch([selectedBlog, selectedProduct], () => {
getComments();
});

const nextPage = () => {
if (currentPage.value < totalPages.value) {
page.value++;
@@ -207,6 +274,14 @@ export default {
visiblePages,
modalData,
comment,
selectedCommentType,
selectedStatus,
handleBlogSearch,
formattedBlog,
selectedBlog,
formattedProducts,
handleProductsSearch,
selectedProduct,
};
},
};
@@ -220,28 +295,67 @@ export default {
class="card-header d-flex justify-content-between align-items-center p-3"
dir="rtl"
>
<!-- <div class="d-flex align-items-center">
<input
v-model="searchQuery"
type="text"
placeholder="جستجو..."
class="form-control form-control-sm d-inline-block me-2"
style="width: 250px; border-radius: 15px"
<div class="d-flex align-items-center">
<select
class="form-select form-select-sm"
v-model="selectedCommentType"
style="width: 120px; border-radius: 15px"
>
<option value="" disabled selected>نوع نظر</option>
<option value="">همه</option>
<option value="product">محصول</option>
<option value="blog">بلاگ</option>
</select>

<select
class="form-select form-select-sm"
v-model="selectedStatus"
style="width: 120px; border-radius: 15px; margin-right: 7px"
>
<option value="" disabled selected>وضعیت</option>
<option value="">همه</option>
<option value="confirmed">تایید شده</option>
<option value="rejected">رد شده</option>
<option value="pending">معلق</option>
</select>

<VueSelect
v-if="selectedCommentType === 'blog'"
style="
--vs-border-radius: 16px;
margin-right: 7px;
--vs-min-height: 18px;
"
v-model="selectedBlog"
:options="formattedBlog"
placeholder="بلاگی را انتخاب کنید"
@search="handleBlogSearch"
/>
</div> -->

<VueSelect
v-if="selectedCommentType === 'product'"
style="
--vs-border-radius: 16px;
margin-right: 7px;
--vs-min-height: 18px;
"
v-model="selectedProduct"
:options="formattedProducts"
placeholder="محصولی را انتخاب کنید"
@search="handleProductsSearch"
/>
</div>
</div>
<div v-if="!filterLoading" class="card-body table-border-style p-0">
<div class="table-responsive">
<table class="table table-hover table-bordered m-0" dir="rtl">
<thead class="table-light">
<tr>
<th>نویسنده</th>
<th>عنوان</th>
<th>نام محصول</th>
<td>امتیاز</td>
<th>امتیاز</th>
<th>نظر</th>
<th>وضعیت</th>
<th>تاریخ ایجاد</th>
@@ -250,9 +364,8 @@ export default {
</thead>
<tbody>
<tr v-for="comment in comments" :key="comment.id">
<td>{{ comment?.user?.name }}</td>
<td v-if="comment?.commentable_type === 'product'">
محصول
</td>
@@ -267,7 +380,7 @@ export default {
"
></span>
</td>
<td
data-bs-toggle="modal"
data-bs-target="#showDescription"
@@ -532,7 +645,6 @@ export default {
display: inline-block;
max-width: 100%;
font-size: 14px;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;


+ 156
- 12
src/views/live-preview/pages/discounts/discounts.vue Voir le fichier

@@ -1,5 +1,6 @@
<script>
import Layout from "@/layout/custom.vue";
import VueSelect from "vue3-select-component";
import ApiServiece from "@/services/ApiService";
import moment from "jalali-moment";
import { onMounted, ref, watch, computed } from "vue";
@@ -14,8 +15,16 @@ export default {
components: {
Layout,
showDescription,
VueSelect,
},
setup() {
let searchTimeout = null;
const selectedDiscountFormat = ref("");
const selectedDiscountType = ref("");
const categories = ref([]);
const products = ref([]);
const selectedcategory = ref();
const selectedProduct = ref();
const searchPage = ref();
const currentPage = ref(1);
const totalPages = ref(1);
@@ -31,14 +40,27 @@ export default {
.locale("fa")
.format("YYYY/MM/DD");
};

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

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

watch(searchQuery, () => {
getDiscounts();
handleSearchChange();
page.value = 1;
});

const getDiscounts = () => {
filterLoading.value = true;
ApiServiece.get(
`admin/discounts?title=${searchQuery.value || ""}&paginate=${
`admin/discounts?title=${searchQuery.value || ""}&product_id=${
selectedProduct.value || ""
}&category_id=${selectedcategory.value || ""}&type=${selectedDiscountFormat.value || ""}&paginate=${
paginate.value || 10
}&page=${page.value || 1}`
)
@@ -118,11 +140,85 @@ export default {
}
}

watch(searchQuery, () => {
const handleCategorySearch = async (searchTerm) => {
if (searchTerm.length < 3) return;

try {
const response = await ApiServiece.get(
`admin/categories?title=${searchTerm}`
);
categories.value = response.data.data;
console.log(categories.value, "products");
} catch (error) {
console.error("Error fetching products:", error);
categories.value = [];
}
};

const formattedCategories = computed(() =>
Array.isArray(categories.value)
? categories.value.map((category) => ({
value: category.id,
label: category.title,
}))
: []
);

const handleProductsSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;

try {
const response = await ApiServiece.get(
`admin/products?title=${searchTerm}`
);
products.value = response.data.data;
} catch (error) {
console.error("Error fetching products:", error);
products.value = [];
}
};

const formattedProducts = computed(() =>
Array.isArray(products.value)
? products.value.map((product) => ({
value: product.id,
label: product.title,
}))
: []
);

watch(page, () => {
getDiscounts();
});

watch(page, () => {
watch(selectedDiscountType, () => {
selectedProduct.value = "",
selectedcategory.value = ""
if (selectedDiscountType.value === "") {
filterLoading.value = true;
ApiServiece.get(
`admin/discounts?title=${searchQuery.value || ""}&paginate=${
paginate.value || 10
}&page=${page.value || 1}`
)
.then((resp) => {
filterLoading.value = false;
discounts.value = resp.data.data.data;
console.log(resp.data.data);
currentPage.value = resp.data.data.current_page;
totalPages.value = resp.data.data.last_page;
})
.catch(() => {
filterLoading.value = false;
});
}
});

watch([selectedProduct, selectedcategory], () => {
getDiscounts();
});

watch(selectedDiscountFormat, () => {
getDiscounts();
});

@@ -157,6 +253,14 @@ export default {
handlePageInput,
searchPage,
visiblePages,
selectedDiscountType,
handleCategorySearch,
formattedCategories,
formattedProducts,
handleProductsSearch,
selectedcategory,
selectedProduct,
selectedDiscountFormat,
};
},
};
@@ -178,13 +282,53 @@ export default {
class="form-control form-control-sm d-inline-block me-2"
style="width: 250px; border-radius: 15px"
/>
<router-link
to="/addDiscount"
class="btn btn-light text-primary btn-sm px-3 "

<select
class="form-select form-select-sm"
v-model="selectedDiscountFormat"
style="width: 120px; border-radius: 15px"
>
<option value="" disabled selected>حالت</option>
<option value="">همه</option>
<option value="percentage">درصدی</option>
<option value="const">مبلغی</option>
</select>

<select
class="form-select form-select-sm"
v-model="selectedDiscountType"
style="width: 120px; border-radius: 15px ; margin-right: 7px;"
>
افزودن تخفیف
</router-link>
<option value="" disabled selected>اعمال بر</option>
<option value="">همه</option>
<option value="products">محصولات</option>
<option value="categories">دسته ها</option>
</select>

<VueSelect
v-if="selectedDiscountType === 'categories'"
style="--vs-border-radius: 16px; margin-right: 7px"
v-model="selectedcategory"
:options="formattedCategories"
placeholder="دسته ای را انتخاب کنید"
@search="handleCategorySearch"
/>

<VueSelect
v-if="selectedDiscountType === 'products'"
style="--vs-border-radius: 16px; margin-right: 7px"
v-model="selectedProduct"
:options="formattedProducts"
placeholder="محصولی را انتخاب کنید"
@search="handleProductsSearch"
/>
</div>
<router-link
to="/addDiscount"
class="btn btn-light text-primary btn-sm px-3"
>
افزودن تخفیف
</router-link>
</div>
<div v-if="!filterLoading" class="card-body table-border-style p-0">
<div class="table-responsive">
@@ -192,9 +336,9 @@ export default {
<thead class="table-light">
<tr>
<th>عنوان</th>
<td>مدل</td>
<td>مقدار</td>
<td>حداقل سفارش</td>
<th>مدل</th>
<th>مقدار</th>
<th>حداقل سفارش</th>
<th>تاریخ ایجاد</th>
<th>تاریخ انقضا</th>
<th>عملیات</th>


+ 7
- 6
src/views/live-preview/pages/faqs/faqs.vue Voir le fichier

@@ -31,7 +31,7 @@ export default {
.locale("fa")
.format("YYYY/MM/DD");
};
watch( selectedStatus, () => {
watch(selectedStatus, () => {
getFaqs();
page.value = 1;
});
@@ -162,7 +162,7 @@ export default {
visiblePages,
modalData,
comment,
selectedStatus
selectedStatus,
};
},
};
@@ -177,7 +177,11 @@ export default {
dir="rtl"
>
<div class="d-flex align-items-center">
<select class="form-select filter-input" v-model="selectedStatus">
<select
class="form-select form-select-sm"
v-model="selectedStatus"
style="width: 120px; border-radius: 15px"
>
<option value="" disabled selected>وضعیت</option>
<option value="">همه</option>
<option value="confirmed">تایید شده</option>
@@ -429,7 +433,6 @@ export default {
display: inline-block;
max-width: 100%;
font-size: 14px;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -450,13 +453,11 @@ export default {
}

.table-hover tbody tr:hover {
background-color: #f1f1f1;
cursor: pointer;
}

.comment-td {
cursor: pointer;
color: #007bff;
text-decoration: underline;
}



+ 68
- 16
src/views/live-preview/pages/identity/idenities.vue Voir le fichier

@@ -6,6 +6,7 @@ import { onMounted, ref, watch, computed } from "vue";
import { toast } from "vue3-toastify";
import "vue3-toastify/dist/index.css";
import Swal from "sweetalert2";
import VueSelect from "vue3-select-component";
import addIdentity from "@/components/modals/identity/addIdentity.vue";
import editIdentity from "@/components/modals/identity/editIdentity.vue";
export default {
@@ -14,8 +15,12 @@ export default {
Layout,
addIdentity,
editIdentity,
VueSelect,
},
setup() {
let searchTimeout = null;
const selectedcategory = ref();
const categories = ref([]);
const cats = ref([]);
const searchPage = ref();
const currentPage = ref(1);
@@ -30,19 +35,58 @@ export default {
const attributeId = ref();
const attrebuteCat = ref();

const handleSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;

try {
const response = await ApiServiece.get(
`admin/categories?title=${searchTerm}`
);
categories.value = response.data.data;
console.log(categories.value, "products");
} catch (error) {
console.error("Error fetching products:", error);
categories.value = [];
}
};

const formattedCategories = computed(() =>
Array.isArray(categories.value)
? categories.value.map((category) => ({
value: category.id,
label: category.title,
}))
: []
);

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

searchTimeout = setTimeout(() => {
getAttributes();
page.value = 1;
}, 500);
};
watch(searchQuery, () => {
handleSearchChange();
});

watch(selectedcategory, () => {
getAttributes();
});

const getAttributes = () => {
filterLoading.value = true;
ApiServiece.get(
`admin/attributes?title=${searchQuery.value || ""}
&paginate=${paginate.value || 10}&page=${page.value || 1}
&paginate=${paginate.value || 10}&page=${page.value || 1}&category_id=${
selectedcategory.value || ""
}
`
)
.then((resp) => {
@@ -157,10 +201,6 @@ export default {
attrebuteCat.value = cat;
};

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

onMounted(() => {
getAttributes();
getCategories();
@@ -188,6 +228,9 @@ export default {
visiblePages,
getCategories,
cats,
formattedCategories,
handleSearch,
selectedcategory,
};
},
};
@@ -198,10 +241,10 @@ export default {
<div class="col-md-12">
<div class="card shadow-sm border-0 rounded">
<div
class="card-header d-flex justify-content-between align-items-center p-3 "
class="card-header d-flex justify-content-between align-items-center p-3"
dir="rtl"
>
<div class="d-flex align-items-center">
<div class="d-flex align-items-center gap-2">
<input
v-model="searchQuery"
type="text"
@@ -209,14 +252,21 @@ export default {
class="form-control form-control-sm d-inline-block me-2"
style="width: 250px; border-radius: 15px"
/>
<button
data-bs-toggle="modal"
data-bs-target="#addIdentity"
class="btn btn-light text-primary btn-sm px-3"
>
افزودن مشخصه
</button>
<VueSelect
style="--vs-border-radius: 16px"
v-model="selectedcategory"
:options="formattedCategories"
placeholder="دسته ای را انتخاب کنید"
@search="handleSearch"
/>
</div>
<button
data-bs-toggle="modal"
data-bs-target="#addIdentity"
class="btn btn-light text-primary btn-sm px-3"
>
افزودن مشخصه
</button>
</div>
<div v-if="!filterLoading" class="card-body table-border-style p-0">
<div class="table-responsive">
@@ -237,7 +287,9 @@ export default {
backgroundColor: attribute.code,
textAlign: 'center',
}"
></td>
>
{{ attribute?.category?.title }}
</td>
<td>{{ convertToJalali(attribute?.created_at) }}</td>
<td>
<button
@@ -279,7 +331,7 @@ export default {
:title="attributeTitle"
:catId="attrebuteCat"
:id="attributeId"
:cats="cats"
:cats="cats"
/>
</BRow>
<BRow>


+ 77
- 66
src/views/live-preview/pages/orders/approvedOrders.vue Voir le fichier

@@ -34,24 +34,14 @@ export default {
}&paginate=${paginate.value || 10}&page=${page.value || 1}`
)
.then((resp) => {
console.log(resp);
filterLoading.value = false;
allProducts.value = resp.data.data.data;

currentPage.value = resp.data.data.current_page;
totalPages.value = resp.data.data.last_page;
})
.catch((err) => {
console.log(err);
});
};

const getAllBrands = () => {
ApiServiece.get("admin/brands")
.then((resp) => {
console.log(resp);
brands.value = resp.data.data;
})
.catch((err) => {
console.log(err);
.catch(() => {
filterLoading.value = false;
});
};

@@ -61,13 +51,6 @@ export default {
.format("YYYY/MM/DD");
};

const formattedBrands = computed(() => {
return brands.value.map((brand) => ({
value: brand?.id,
label: brand?.title,
}));
});

const getFile = () => {
isLoading.value = true;
ApiServiece.post(
@@ -164,9 +147,31 @@ export default {
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}

const handleBrandSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;

try {
const response = await ApiServiece.get(
`admin/brands?title=${searchTerm}`
);
brands.value = response.data.data;
} catch (error) {
console.error("Error fetching products:", error);
brands.value = [];
}
};

const formattedBrands = computed(() =>
Array.isArray(brands.value)
? brands.value.map((brand) => ({
value: brand.id,
label: brand.title,
}))
: []
);

onMounted(() => {
getAllProducts();
getAllBrands();
});
return {
allProducts,
@@ -186,6 +191,8 @@ export default {
date,
isLoading,
formatWithCommas,
handleBrandSearch,
filterLoading,
};
},
};
@@ -197,52 +204,49 @@ export default {
<BCol class="col-sm-12">
<BCard no-body class="table-card">
<BCardBody>
<div class="text-end p-sm-4 pb-sm-2">
<!-- Button to Trigger Export -->
<BRow>
<BCol sm="3" class="mt-3">

<VueSelect
style="--vs-border-radius: 8px"
v-model="selectedBrand"
:options="formattedBrands"
placeholder="انتخاب برند"
/>
</BCol>
<BCol style="margin-right: 180px" class="mt-3" sm="3">
<div class="form-group">
<DatePicker
format="YYYY/MM/DD HH:mm:ss"
type="date"
:range="true"
v-model="date"
@input="handleInput"
></DatePicker>
</div>
</BCol>
<BCol sm="4" class="mt-3">
<button
@click="getFile"
type="button"
class="btn btn-primary"
:disabled="isLoading"
>
<span
v-if="isLoading"
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
></span>
<span v-else>گرفتن خروجی</span>
</button>
</BCol>
</BRow>

<!-- Product Selection Section -->
<div
class="card-header d-flex justify-content-between align-items-center p-3"
dir="rtl"
>
<div class="d-flex align-items-center gap-3">
<!-- VueSelect for Brand Selection -->
<VueSelect
style="--vs-border-radius: 8px; min-width: 180px"
v-model="selectedBrand"
:options="formattedBrands"
placeholder="برندی را انتخاب کنید"
@search="handleBrandSearch"
/>

<!-- Date Picker with Proper Width -->
<DatePicker
format="YYYY/MM/DD HH:mm:ss"
type="date"
:range="true"
v-model="date"
@input="handleInput"
class="custom-datepicker"
/>
</div>

<!-- Export Button with Better Spacing -->
<button
@click="getFile"
type="button"
class="btn btn-light text-primary btn-sm px-4"
:disabled="isLoading"
>
<span
v-if="isLoading"
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
></span>
<span v-else>گرفتن خروجی</span>
</button>
</div>

<div class="table-responsive">
<div v-if="!filterLoading">
<table class="table table-hover tbl-product" id="pc-dt-simple">
<thead>
<tr>
@@ -327,6 +331,10 @@ export default {
</tbody>
</table>
</div>
<div
v-else
class="filter-loader card table-card user-profile-list"
></div>
</BCardBody>
</BCard>
</BCol>
@@ -461,4 +469,7 @@ export default {
cursor: pointer;
user-select: none;
}
.custom-datepicker {
min-width: 340px;
}
</style>

+ 28
- 5
src/views/live-preview/pages/orders/orders.vue Voir le fichier

@@ -13,10 +13,11 @@ export default {
Layout,
},
setup() {
const selectedStatus = ref("");
const searchPage = ref();
const currentPage = ref(1);
const totalPages = ref(1);
const paginate = ref(5);
const paginate = ref(20);
const page = ref(1);

const filterLoading = ref(false);
@@ -35,9 +36,9 @@ export default {
const getOrders = () => {
filterLoading.value = true;
ApiServiece.get(
`admin/orders?title=${searchQuery.value || ""}&paginate=${
paginate.value || 10
}&page=${page.value || 1}`
`admin/orders?title=${searchQuery.value || ""}&status=${
selectedStatus.value || ""
}&paginate=${paginate.value || 10}&page=${page.value || 1}`
)
.then((resp) => {
filterLoading.value = false;
@@ -185,6 +186,10 @@ export default {
getOrders();
});

watch(selectedStatus, () => {
getOrders();
});

const nextPage = () => {
if (currentPage.value < totalPages.value) {
page.value++;
@@ -219,6 +224,7 @@ export default {
visiblePages,
getStatusClass,
getStatusLabel,
selectedStatus,
};
},
};
@@ -240,6 +246,23 @@ export default {
class="form-control form-control-sm d-inline-block me-2"
style="width: 250px; border-radius: 15px"
/>

<select
class="form-select form-select-sm"
v-model="selectedStatus"
style="width: 120px; border-radius: 15px"
>
<option value="" disabled selected>وضعیت</option>
<option value="">همه</option>
<option value="waiting">در انتظار</option>
<option value="paid">پرداخت شده</option>
<option value="un_paid">پرداخت نشده</option>
<option value="approved">تایید شده</option>
<option value="processing">در حال پردازش</option>
<option value="shipping">در حال ارسال</option>
<option value="delivered">تحویل شده</option>
<option value="canceled">لغو شده</option>
</select>
</div>
</div>
<div v-if="!filterLoading" class="card-body table-border-style p-0">
@@ -248,7 +271,7 @@ export default {
<thead class="table-light">
<tr>
<th>شناسه</th>
<td>وضعیت</td>
<th>وضعیت</th>

<th>تاریخ ایجاد</th>



+ 141
- 24
src/views/live-preview/pages/products/products.vue Voir le fichier

@@ -1,6 +1,7 @@
<script>
import Layout from "@/layout/custom.vue";
import ApiServiece from "@/services/ApiService";
import VueSelect from "vue3-select-component";
import moment from "jalali-moment";
import { onMounted, ref, watch, computed } from "vue";
import { toast } from "vue3-toastify";
@@ -11,8 +12,15 @@ export default {
name: "BORDER",
components: {
Layout,
VueSelect,
},
setup() {
let searchTimeout = null;
const selectedProductType = ref("");
const categories = ref([]);
const brands = ref();
const selectedcategory = ref();
const selectedBrand = ref();
const searchPage = ref();
const currentPage = ref(1);
const totalPages = ref(1);
@@ -31,14 +39,36 @@ export default {
.locale("fa")
.format("YYYY/MM/DD");
};
const handleSearchChange = () => {
clearTimeout(searchTimeout);

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

watch(searchQuery, () => {
getProducts();
handleSearchChange();
page.value = 1;
});

const getProducts = () => {
filterLoading.value = true;
let isSpecial = 0;
let isChosen = 0;
if (selectedProductType.value === "special") {
isSpecial = 1;
}
if (selectedProductType.value === "chosen") {
isChosen = 1;
}
ApiServiece.get(
`admin/products?title=${searchQuery.value || ""}&paginate=${
`admin/products?title=${searchQuery.value || ""}&category_id=${
selectedcategory.value || ""
}&brand_id=${
selectedBrand.value || ""
}&is_chosen=${isChosen}&is_special=${isSpecial}&paginate=${
paginate.value || 10
}&page=${page.value || 1}`
)
@@ -150,11 +180,15 @@ export default {
}
}

watch(searchQuery, () => {
watch(page, () => {
getProducts();
});

watch(page, () => {
watch([selectedcategory, selectedBrand], () => {
getProducts();
});

watch(selectedProductType, () => {
getProducts();
});

@@ -172,6 +206,53 @@ export default {
}
};

const handleSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;

try {
const response = await ApiServiece.get(
`admin/categories?title=${searchTerm}`
);
categories.value = response.data.data;
console.log(categories.value, "products");
} catch (error) {
console.error("Error fetching products:", error);
categories.value = [];
}
};

const formattedCategories = computed(() =>
Array.isArray(categories.value)
? categories.value.map((category) => ({
value: category.id,
label: category.title,
}))
: []
);

const handleBrandSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;

try {
const response = await ApiServiece.get(
`admin/brands?title=${searchTerm}`
);
brands.value = response.data.data;
} catch (error) {
console.error("Error fetching products:", error);
brands.value = [];
}
};

const formattedBrands = computed(() =>
Array.isArray(brands.value)
? brands.value.map((brand) => ({
value: brand.id,
label: brand.title,
}))
: []
);

onMounted(() => {
getProducts();
});
@@ -191,6 +272,13 @@ export default {
visiblePages,
restoreProduct,
formatWithCommas,
handleSearch,
formattedCategories,
selectedcategory,
selectedProductType,
formattedBrands,
handleBrandSearch,
selectedBrand,
};
},
};
@@ -212,13 +300,47 @@ export default {
class="form-control form-control-sm d-inline-block me-2"
style="width: 250px; border-radius: 15px"
/>
<router-link
to="/addProduct"
class="btn btn-light text-primary btn-sm px-3"
<select
class="form-select form-select-sm"
v-model="selectedProductType"
style="width: 120px; border-radius: 15px"
>
افزودن محصول
</router-link>
<option value="" disabled selected>نوع محصول</option>
<option value="">همه</option>
<option value="chosen">برگزیده</option>
<option value="special">ویژه</option>
</select>

<VueSelect
style="
--vs-border-radius: 16px;
--vs-min-height: 18px;
margin-right: 7px;
"
v-model="selectedcategory"
:options="formattedCategories"
placeholder="دسته ای را انتخاب کنید"
@search="handleSearch"
/>

<VueSelect
style="
--vs-border-radius: 16px;
--vs-min-height: 18px;
margin-right: 7px;
"
v-model="selectedBrand"
:options="formattedBrands"
placeholder="برندی را انتخاب کنید"
@search="handleBrandSearch"
/>
</div>
<router-link
to="/addProduct"
class="btn btn-light text-primary btn-sm px-3"
>
افزودن محصول
</router-link>
</div>
<div v-if="!filterLoading" class="card-body table-border-style p-0">
<div class="table-responsive">
@@ -227,14 +349,14 @@ export default {
<tr>
<th>عکس</th>
<th>عنوان</th>
<td>مدل</td>
<td>تعداد فروش</td>
<td>برگزیده</td>
<td>تخفیف برگزیده</td>
<td>ویژه</td>
<td>قیمت عمده</td>
<td>قیمت تک</td>
<td>وضعیت</td>
<th>مدل</th>
<th>تعداد فروش</th>
<th>برگزیده</th>
<th>تخفیف برگزیده</th>
<th>ویژه</th>
<th>قیمت عمده</th>
<th>قیمت تک</th>
<th>وضعیت</th>
<th>عملیات</th>
</tr>
</thead>
@@ -516,10 +638,7 @@ export default {
font-weight: bold;
}

.table-hover tbody tr:hover {
background-color: #e9ecef;
cursor: pointer;
}


.Product-Image {
width: 50px;
@@ -562,9 +681,7 @@ export default {
color: #fff;
}

table tbody tr:hover {
background-color: #f1f1f1;
}


td,
table[dir="rtl"] td,


+ 6
- 6
src/views/live-preview/pages/profile/profile.vue Voir le fichier

@@ -46,7 +46,7 @@ export default {
<Layout>
<BRow>
<BCol class="col-sm-12">
<BCard
<!-- <BCard
no-body
class="alert alert-warning p-0"
@@ -70,7 +70,7 @@ export default {
</div>
</div>
</BCardBody>
</BCard>
</BCard> -->
<BRow>
<BCol class="col-lg-5 col-xxl-3">
<BCard no-body class="overflow-hidden">
@@ -106,7 +106,7 @@ export default {
حساب
</span>
</a>
<a
<!-- <a
class="nav-link list-group-item list-group-item-action"
id="user-set-information-tab"
data-bs-toggle="pill"
@@ -119,8 +119,8 @@ export default {
><i class="ph-duotone ph-clipboard-text m-r-10"></i> اضافه
کردن آدرس</span
>
</a>
<a
</a> -->
<!-- <a
class="nav-link list-group-item list-group-item-action"
id="user-list-address-tab"
data-bs-toggle="pill"
@@ -132,7 +132,7 @@ export default {
<span class="f-w-500"
><i class="ph-duotone ph-map-pin m-r-10"></i> مشاهده آدرس ها
</span>
</a>
</a> -->
</div>
</BCard>
</BCol>


+ 57
- 15
src/views/live-preview/pages/users/users.vue Voir le fichier

@@ -17,6 +17,8 @@ export default {
addUser,
},
setup() {
const selectedStatus = ref("");
const filterLoading = ref(false);
const searchPage = ref();
const currentPage = ref(1);
const totalPages = ref(1);
@@ -36,16 +38,24 @@ export default {
};

const getUsers = () => {
filterLoading.value = true;
ApiServiece.get(
`admin/users?name=${searchQuery.value || ""}&paginate=${
paginate.value || 10
}&page=${page.value || 1}&role=${selectedRole.value || "admin"}`
).then((resp) => {
console.log(resp.data.data);
users.value = resp.data.data.data;
currentPage.value = resp.data.data.current_page;
totalPages.value = resp.data.data.last_page;
});
}&page=${page.value || 1}&role=${
selectedRole.value || "admin"
}&trashed=${selectedStatus.value || ""}`
)
.then((resp) => {
console.log(resp.data.data);
users.value = resp.data.data.data;
currentPage.value = resp.data.data.current_page;
totalPages.value = resp.data.data.last_page;
filterLoading.value = false;
})
.catch(() => {
filterLoading.value = false;
});
};

const nextPage = () => {
@@ -86,7 +96,9 @@ export default {
getUsers();
});

watch(selectedStatus, () => {
getUsers();
});

watch(searchQuery, (newQuery) => {
debouncedSearch(newQuery);
@@ -95,7 +107,7 @@ export default {
const debouncedSearch = debounce((query) => {
console.log("Searching for:", query);
getUsers();
}, 2000);
}, 1000);

watch(page, () => {
getUsers();
@@ -213,6 +225,8 @@ export default {
page,
searchPage,
selectedRole,
selectedStatus,
filterLoading,
};
},
};
@@ -228,15 +242,19 @@ export default {
<div class="search-filters d-flex align-items-center gap-3">
<!-- Search Input -->
<input
type="text"
class="form-control search-input"
placeholder="جستجو بر اساس نام کاربر"
id="search-users"
v-model="searchQuery"
type="text"
placeholder="جستجو..."
class="form-control form-control-sm d-inline-block me-2"
style="width: 250px; border-radius: 15px"
/>

<!-- User Role Selector -->
<select class="form-select filter-input" v-model="selectedRole">
<select
class="form-select form-select-sm"
v-model="selectedRole"
style="width: 120px; border-radius: 15px"
>
<option value="" disabled selected>نقش کاربر</option>
<option value="">همه</option>
<option value="admin">فقط مدیران</option>
@@ -244,6 +262,17 @@ export default {
<option value="operator">فقط اپراتورها</option>
</select>

<select
class="form-select form-select-sm"
v-model="selectedStatus"
style="width: 120px; border-radius: 15px"
>
<option value="" disabled selected>وضعیت</option>
<option value="">همه</option>
<option value="0">فعال</option>
<option value="1">بلاک</option>
</select>

<!-- User Status Selector -->
<!-- <select
v-model="selectedStatus"
@@ -265,7 +294,7 @@ export default {
</button>
</div>

<div class="table-responsive">
<div v-if="!filterLoading" class="table-responsive">
<table class="table table-hover" id="pc-dt-simple">
<thead>
<tr>
@@ -352,6 +381,10 @@ export default {
</tbody>
</table>
</div>
<div
v-else
class="filter-loader card table-card user-profile-list"
></div>
</div>
</div>
</div>
@@ -575,4 +608,13 @@ export default {
cursor: pointer;
user-select: none;
}
.filter-loader {
border: 4px solid rgba(0, 123, 255, 0.3);
border-top: 4px solid #007bff;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 20px auto;
}
</style>

Chargement…
Annuler
Enregistrer