Selaa lähdekoodia

admin panell updates

master
unknown 9 kuukautta sitten
vanhempi
commit
ef3349aaab
15 muutettua tiedostoa jossa 696 lisäystä ja 445 poistoa
  1. +2
    -2
      package-lock.json
  2. +1
    -1
      package.json
  3. +20
    -33
      src/views/live-preview/pages/auth2/forgot-password.vue
  4. +64
    -2
      src/views/live-preview/pages/auth2/reset-password.vue
  5. +4
    -2
      src/views/live-preview/pages/blogs/addBlog.vue
  6. +12
    -2
      src/views/live-preview/pages/comments/comments.vue
  7. +107
    -74
      src/views/live-preview/pages/discounts/addDiscount.vue
  8. +8
    -7
      src/views/live-preview/pages/discounts/discounts.vue
  9. +119
    -69
      src/views/live-preview/pages/discounts/editDiscount.vue
  10. +24
    -4
      src/views/live-preview/pages/identity/idenities.vue
  11. +24
    -9
      src/views/live-preview/pages/orders/approvedOrders.vue
  12. +15
    -4
      src/views/live-preview/pages/orders/orders.vue
  13. +170
    -123
      src/views/live-preview/pages/products/addProduct.vue
  14. +114
    -104
      src/views/live-preview/pages/products/editProduct.vue
  15. +12
    -9
      src/views/live-preview/pages/products/products.vue

+ 2
- 2
package-lock.json Näytä tiedosto

@@ -1,11 +1,11 @@
{ {
"name": "LightAble",
"name": "NovinPlast",
"version": "0.1.0", "version": "0.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "LightAble",
"name": "NovinPlast",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@amcharts/amcharts4": "^4.10.38", "@amcharts/amcharts4": "^4.10.38",


+ 1
- 1
package.json Näytä tiedosto

@@ -1,5 +1,5 @@
{ {
"name": "LightAble",
"name": "NovinPlast",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {


+ 20
- 33
src/views/live-preview/pages/auth2/forgot-password.vue Näytä tiedosto

@@ -21,7 +21,7 @@ export default {
const store = useStore(); const store = useStore();
const mobile = ref(""); const mobile = ref("");
const otpSent = ref(false); const otpSent = ref(false);
const timer = ref(120);
const timer = ref(60);
const otpCode = ref(""); const otpCode = ref("");
const resendAvailable = ref(false); const resendAvailable = ref(false);
let timerInterval = null; let timerInterval = null;
@@ -45,24 +45,12 @@ export default {
}) })
.catch((err) => { .catch((err) => {
sendOtpLoading.value = false; sendOtpLoading.value = false;
console.log(err);
const status = err.status;
if (status == 429) {
Swal.fire({
icon: "error",
title: "تعداد تلاش‌ها زیاد شد",
text: "لطفاً بعداً دوباره امتحان کنید",
confirmButtonText: "باشه",
});
}
if (status == 400) {
Swal.fire({
icon: "warning",
title: "کد تایید قبلاً ارسال شده",
text: "برای این شماره، کد ورود قبلاً ارسال شده است. لطفاً کمی صبر کنید تا کد قبلی منقضی شود",
confirmButtonText: "باشه",
});
}
Swal.fire({
icon: "error",
title: "خطا در ارسال کد",
text: err.response.data.message,
confirmButtonText: "متوجه شدم",
});
}); });
}; };


@@ -78,10 +66,11 @@ export default {
}; };


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


const verifyOtp = async () => { const verifyOtp = async () => {
@@ -134,7 +123,6 @@ export default {
try { try {
const response = await ApiServiece.get("settings/logo_fav"); const response = await ApiServiece.get("settings/logo_fav");
settings.value = response.data.data; settings.value = response.data.data;
} catch (error) { } catch (error) {
console.error("Error fetching settings:", error); console.error("Error fetching settings:", error);
} }
@@ -230,14 +218,14 @@ export default {
role="status" role="status"
aria-hidden="true" aria-hidden="true"
></span> ></span>
<span v-else> ورود</span>
<span v-else>ورود</span>
</button> </button>
</div> </div>
</div> </div>


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


+ 64
- 2
src/views/live-preview/pages/auth2/reset-password.vue Näytä tiedosto

@@ -1,12 +1,15 @@
<script> <script>
import { useStore } from "vuex"; import { useStore } from "vuex";
import { ref } from "vue";
import ApiServiece from "@/services/ApiService";
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
export default { export default {
name: "RESET-PASSWORD", name: "RESET-PASSWORD",
components: {}, components: {},
setup() { setup() {
const logo = ref();
const settings = ref()
const router = useRouter(); const router = useRouter();
const loading = ref(false); const loading = ref(false);
const errors = ref({}); const errors = ref({});
@@ -58,6 +61,20 @@ export default {
errors.value[field] = ""; 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 { return {
newPassword, newPassword,
confirmPassword, confirmPassword,
@@ -65,6 +82,7 @@ export default {
clearError, clearError,
errors, errors,
loading, loading,
logo,
}; };
}, },
}; };
@@ -74,10 +92,12 @@ export default {
<div class="auth-main v2"> <div class="auth-main v2">
<div class="bg-overlay bg-dark"></div> <div class="bg-overlay bg-dark"></div>
<div class="auth-wrapper"> <div class="auth-wrapper">
<div class="auth-sidecontent"></div>
<div class="auth-form"> <div class="auth-form">
<div class="card my-5 mx-3"> <div class="card my-5 mx-3">
<div style="direction: rtl" class="card-body"> <div style="direction: rtl" class="card-body">
<div class="text-center mb-4">
<img :src="logo" alt="Logo" class="styled-logo" />
</div>
<h4 class="f-w-500 mb-1">بازنشانی رمز عبور</h4> <h4 class="f-w-500 mb-1">بازنشانی رمز عبور</h4>
<p class="mb-3"> <p class="mb-3">
باز گشت به صفحه باز گشت به صفحه
@@ -134,3 +154,45 @@ export default {
</div> </div>
<Rightbar /> <Rightbar />
</template> </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>

+ 4
- 2
src/views/live-preview/pages/blogs/addBlog.vue Näytä tiedosto

@@ -309,12 +309,14 @@ export default {
Authorization: `Bearer ${localStorage.getItem("token")}`, Authorization: `Bearer ${localStorage.getItem("token")}`,
}, },
}) })
.then((resp) => {
console.log(resp);
.then(() => {
toast.success("!بلاگ با موفقیت اضافه شد", { toast.success("!بلاگ با موفقیت اضافه شد", {
position: "top-right", position: "top-right",
autoClose: 1000, autoClose: 1000,
}); });
setTimeout(() => {
window.location.reload();
}, 1500);
}) })


.catch((error) => { .catch((error) => {


+ 12
- 2
src/views/live-preview/pages/comments/comments.vue Näytä tiedosto

@@ -17,6 +17,8 @@ export default {
showDescription, showDescription,
}, },
setup() { setup() {
const productSeletorLoader = ref(false);
const blogSelectorLoader = ref(false);
const products = ref([]); const products = ref([]);
const selectedProduct = ref(); const selectedProduct = ref();
const blogs = ref([]); const blogs = ref([]);
@@ -65,13 +67,15 @@ export default {


const handleBlogSearch = async (searchTerm) => { const handleBlogSearch = async (searchTerm) => {
if (searchTerm.length < 3) return; if (searchTerm.length < 3) return;
blogSelectorLoader.value = true;
try { try {
const response = await ApiServiece.get( const response = await ApiServiece.get(
`admin/blogs?title=${searchTerm}` `admin/blogs?title=${searchTerm}`
); );
blogs.value = response.data.data; blogs.value = response.data.data;
blogSelectorLoader.value = false;
} catch (error) { } catch (error) {
blogSelectorLoader.value = false;
blogs.value = []; blogs.value = [];
} }
}; };
@@ -87,14 +91,16 @@ export default {


const handleProductsSearch = async (searchTerm) => { const handleProductsSearch = async (searchTerm) => {
if (searchTerm.length < 3) return; if (searchTerm.length < 3) return;
productSeletorLoader.value = true;
try { try {
const response = await ApiServiece.get( const response = await ApiServiece.get(
`admin/products?title=${searchTerm}` `admin/products?title=${searchTerm}`
); );
products.value = response.data.data; products.value = response.data.data;
productSeletorLoader.value = false;
} catch (error) { } catch (error) {
products.value = []; products.value = [];
productSeletorLoader.value = false;
} }
}; };


@@ -282,6 +288,8 @@ export default {
formattedProducts, formattedProducts,
handleProductsSearch, handleProductsSearch,
selectedProduct, selectedProduct,
productSeletorLoader,
blogSelectorLoader
}; };
}, },
}; };
@@ -328,6 +336,7 @@ export default {
" "
v-model="selectedBlog" v-model="selectedBlog"
:options="formattedBlog" :options="formattedBlog"
:isLoading="blogSelectorLoader"
placeholder="بلاگی را انتخاب کنید" placeholder="بلاگی را انتخاب کنید"
@search="handleBlogSearch" @search="handleBlogSearch"
/> />
@@ -341,6 +350,7 @@ export default {
" "
v-model="selectedProduct" v-model="selectedProduct"
:options="formattedProducts" :options="formattedProducts"
:isLoading="productSeletorLoader"
placeholder="محصولی را انتخاب کنید" placeholder="محصولی را انتخاب کنید"
@search="handleProductsSearch" @search="handleProductsSearch"
/> />


+ 107
- 74
src/views/live-preview/pages/discounts/addDiscount.vue Näytä tiedosto

@@ -107,28 +107,26 @@
> >
<option value="cat">دسته</option> <option value="cat">دسته</option>
<option value="product">محصول</option> <option value="product">محصول</option>
<option value="both">هردو</option>
<option value="all">همه</option>
</select> </select>
</div> </div>
<small v-if="errors.discountType" class="text-danger">
{{ errors.discountType }}
<small v-if="errors.whichPart" class="text-danger">
{{ errors.whichPart }}
</small> </small>
</BCol> </BCol>


<BCol v-if="whichPart === 'cat' || whichPart === 'both'" md="6">
<BCol v-if="whichPart === 'cat'" md="6">
<div class="form-group"> <div class="form-group">
<label class="form-label">دسته</label> <label class="form-label">دسته</label>
<select
:class="{ 'is-invalid': errors.selectedCat }"

<VueSelect
style="--vs-min-height: 48px; --vs-border-radius: 8px"
:isLoading="categorySelectorLoader"
v-model="selectedCat" v-model="selectedCat"
class="form-control"
@select="clearError('selectedCat')"
placeholder="انتخاب دسته"
>
<option v-for="cat in cats" :key="cat.id" :value="cat.id">
{{ cat.title }}
</option>
</select>
:options="formattedCategories"
placeholder="دسته ای را انتخاب کنید"
@search="handleSearch"
/>
</div> </div>
<small v-if="errors.blogCat" class="text-danger"> <small v-if="errors.blogCat" class="text-danger">
{{ errors.blogCat }} {{ errors.blogCat }}
@@ -136,7 +134,7 @@
</BCol> </BCol>


<BCol <BCol
v-if="whichPart === 'product' || whichPart === 'both'"
v-if="whichPart === 'product'"
sm="6" sm="6"
class="mt-3" class="mt-3"
style="margin-top: 30px" style="margin-top: 30px"
@@ -147,8 +145,10 @@


<VueSelect <VueSelect
style="--vs-min-height: 48px; --vs-border-radius: 8px" style="--vs-min-height: 48px; --vs-border-radius: 8px"
:isLoading="categorySelectorLoader"
v-model="selectedProduct" v-model="selectedProduct"
:options="formattedProducts" :options="formattedProducts"
@search="handleProductSearch"
placeholder="محصولی را انتخاب کنید" placeholder="محصولی را انتخاب کنید"
/> />
<small v-if="errors.selectedProduct" class="text-danger"> <small v-if="errors.selectedProduct" class="text-danger">
@@ -212,11 +212,11 @@


<script> <script>
import VueSelect from "vue3-select-component"; import VueSelect from "vue3-select-component";
import moment from "moment-jalaali";
import moment from "jalali-moment";
import { toast } from "vue3-toastify"; import { toast } from "vue3-toastify";
import "vue3-toastify/dist/index.css"; import "vue3-toastify/dist/index.css";
import ApiServiece from "@/services/ApiService"; import ApiServiece from "@/services/ApiService";
import { ref, onMounted, computed } from "vue";
import { ref, computed } from "vue";
import Layout from "@/layout/custom.vue"; import Layout from "@/layout/custom.vue";
import DatePicker from "vue3-persian-datetime-picker"; import DatePicker from "vue3-persian-datetime-picker";


@@ -229,6 +229,7 @@ export default {
VueSelect, VueSelect,
}, },
setup() { setup() {
const productSelectorLoader = ref(false);
const title = ref(); const title = ref();
const discountType = ref(); const discountType = ref();
const amount = ref(); const amount = ref();
@@ -240,36 +241,58 @@ export default {
const maxUsage = ref(); const maxUsage = ref();
const whichPart = ref(); const whichPart = ref();
const loading = ref(false); const loading = ref(false);
const cats = ref([]);
const categories = ref([]);
const errors = ref({}); const errors = ref({});
const products = ref(); const products = ref();

const getCats = () => {
ApiServiece.get(`admin/categories`)
.then((resp) => {
cats.value = resp.data.data;
})
.catch((err) => {
console.log(err);
});
const categorySelectorLoader = ref(false);

const handleSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;
categorySelectorLoader.value = true;
try {
const response = await ApiServiece.get(
`admin/categories?title=${searchTerm}`
);
categories.value = response.data.data;
categorySelectorLoader.value = false;
} catch (error) {
categorySelectorLoader.value = false;
categories.value = [];
}
}; };


const getProduct = () => {
ApiServiece.get(`admin/products`)
.then((resp) => {
products.value = resp.data.data;
})
.catch((err) => {
console.log(err);
});
const formattedCategories = computed(() =>
Array.isArray(categories.value)
? categories.value.map((category) => ({
value: category.id,
label: category.title,
}))
: []
);

const handleProductSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;
productSelectorLoader.value = true;
try {
const response = await ApiServiece.get(
`admin/products?title=${searchTerm}`
);
products.value = response.data.data;
productSelectorLoader.value = false;
} catch (error) {
productSelectorLoader.value = false;
products.value = [];
}
}; };


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


const validateForm = () => { const validateForm = () => {
errors.value = {}; errors.value = {};
@@ -278,24 +301,14 @@ export default {
errors.value.discountType = "وارد کردن حالت تخفیف الزامی است"; errors.value.discountType = "وارد کردن حالت تخفیف الزامی است";
if (!amount.value) if (!amount.value)
errors.value.amount = "وارد کردن مقدار تخفیف الزامی می باشد"; errors.value.amount = "وارد کردن مقدار تخفیف الزامی می باشد";
if (!minOrder.value)
errors.value.minOrder = "وارد کردن حداقل میزان تخفیف الزامی می باشد";
if (
!selectedCat.value &&
(whichPart.value === "cat" || whichPart.value === "both")
)

if (!selectedCat.value && whichPart.value === "cat")
errors.value.selectedCat = "انتخاب دسته برای تخفیف الزامی می باشد"; errors.value.selectedCat = "انتخاب دسته برای تخفیف الزامی می باشد";
if (
!selectedProduct.value &&
(whichPart.value === "product" || whichPart.value === "both")
)
if (!selectedProduct.value && whichPart.value === "product")
errors.value.selectedProduct = "انتخاب محصول برای تخفیف الزامی می باشد"; errors.value.selectedProduct = "انتخاب محصول برای تخفیف الزامی می باشد";
if (!startDate.value) if (!startDate.value)
errors.value.startDate = "انتخاب تاریخ اعمال تخفیف الزامی می باشد "; errors.value.startDate = "انتخاب تاریخ اعمال تخفیف الزامی می باشد ";
if (!expire.value)
errors.value.expire = "انتخاب تاریخ انقضای تخفیف الزامی می باشد ";
if (!maxUsage.value)
errors.value.maxUsage = "مشخص کنید تخفیف چند بار مصرف است ";

if (!whichPart.value) if (!whichPart.value)
errors.value.whichPart = "مشخص کنید تخفیف بر چه بخشی اعمال شود"; errors.value.whichPart = "مشخص کنید تخفیف بر چه بخشی اعمال شود";
return Object.keys(errors.value).length === 0; return Object.keys(errors.value).length === 0;
@@ -305,21 +318,7 @@ export default {
errors.value[field] = ""; errors.value[field] = "";
}; };


onMounted(() => {
getCats();
getProduct();
});

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

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

if (!validateForm()) { if (!validateForm()) {
toast.error("لطفا فیلد های لازم را وارد نمایید", { toast.error("لطفا فیلد های لازم را وارد نمایید", {
position: "top-right", position: "top-right",
@@ -327,22 +326,38 @@ export default {
}); });
return; return;
} }

loading.value = true; loading.value = true;
const formData = new FormData(); const formData = new FormData();
formData.append("title", title.value); formData.append("title", title.value);
formData.append("type", discountType.value); formData.append("type", discountType.value);
formData.append("amount", amount.value); formData.append("amount", amount.value);
formData.append("min_order", minOrder.value); formData.append("min_order", minOrder.value);
if (whichPart.value === "cat" || whichPart.value === "both") {

if (whichPart.value === "cat") {
formData.append("category_id", selectedCat.value); formData.append("category_id", selectedCat.value);
} }


if (whichPart.value === "product" || whichPart.value === "both") {
if (whichPart.value === "product") {
formData.append("product_id", selectedProduct.value); formData.append("product_id", selectedProduct.value);
} }


formData.append("starts_at", startDate.value);
formData.append("expires_at", expire.value);
if (startDate.value) {
const georgianDate = moment(
startDate.value,
"jYYYY/jMM/jDD HH:mm:ss"
).format("YYYY/MM/DD HH:mm:ss");
formData.append("starts_at", georgianDate);
}

if (expire.value) {
const georgianDate = moment(
expire.value,
"jYYYY/jMM/jDD HH:mm:ss"
).format("YYYY/MM/DD HH:mm:ss");
formData.append("expires_at", georgianDate);
}

formData.append("max_usage", maxUsage.value); formData.append("max_usage", maxUsage.value);


ApiServiece.post(`/admin/discounts`, formData) ApiServiece.post(`/admin/discounts`, formData)
@@ -353,6 +368,18 @@ export default {
autoClose: 1000, autoClose: 1000,
}); });
console.log(resp); console.log(resp);

// Clear form fields after successful submission
title.value = "";
discountType.value = "";
amount.value = "";
minOrder.value = "";
whichPart.value = "";
selectedCat.value = null;
selectedProduct.value = null;
startDate.value = "";
expire.value = "";
maxUsage.value = "";
}) })
.catch((error) => { .catch((error) => {
loading.value = false; loading.value = false;
@@ -365,7 +392,7 @@ export default {
}; };


return { return {
cats,
categories,
errors, errors,
title, title,
products, products,
@@ -381,6 +408,12 @@ export default {
whichPart, whichPart,
clearError, clearError,
loading, loading,

handleSearch,
categorySelectorLoader,
formattedCategories,
handleProductSearch,
productSelectorLoader,
formattedProducts, formattedProducts,
}; };
}, },


+ 8
- 7
src/views/live-preview/pages/discounts/discounts.vue Näytä tiedosto

@@ -60,9 +60,9 @@ export default {
ApiServiece.get( ApiServiece.get(
`admin/discounts?title=${searchQuery.value || ""}&product_id=${ `admin/discounts?title=${searchQuery.value || ""}&product_id=${
selectedProduct.value || "" selectedProduct.value || ""
}&category_id=${selectedcategory.value || ""}&type=${selectedDiscountFormat.value || ""}&paginate=${
paginate.value || 10
}&page=${page.value || 1}`
}&category_id=${selectedcategory.value || ""}&type=${
selectedDiscountFormat.value || ""
}&paginate=${paginate.value || 10}&page=${page.value || 1}`
) )
.then((resp) => { .then((resp) => {
filterLoading.value = false; filterLoading.value = false;
@@ -192,8 +192,7 @@ export default {
}); });


watch(selectedDiscountType, () => { watch(selectedDiscountType, () => {
selectedProduct.value = "",
selectedcategory.value = ""
(selectedProduct.value = ""), (selectedcategory.value = "");
if (selectedDiscountType.value === "") { if (selectedDiscountType.value === "") {
filterLoading.value = true; filterLoading.value = true;
ApiServiece.get( ApiServiece.get(
@@ -297,7 +296,7 @@ export default {
<select <select
class="form-select form-select-sm" class="form-select form-select-sm"
v-model="selectedDiscountType" v-model="selectedDiscountType"
style="width: 120px; border-radius: 15px ; margin-right: 7px;"
style="width: 120px; border-radius: 15px; margin-right: 7px"
> >
<option value="" disabled selected>اعمال بر</option> <option value="" disabled selected>اعمال بر</option>
<option value="">همه</option> <option value="">همه</option>
@@ -339,7 +338,8 @@ export default {
<th>مدل</th> <th>مدل</th>
<th>مقدار</th> <th>مقدار</th>
<th>حداقل سفارش</th> <th>حداقل سفارش</th>
<th>تاریخ ایجاد</th>
<th>حداکثر میزان استفاده</th>
<th>تاریخ شروع</th>
<th>تاریخ انقضا</th> <th>تاریخ انقضا</th>
<th>عملیات</th> <th>عملیات</th>
</tr> </tr>
@@ -351,6 +351,7 @@ export default {
<td v-if="discount.type === 'percentage'">درصدی</td> <td v-if="discount.type === 'percentage'">درصدی</td>
<td>{{ discount.amount }}</td> <td>{{ discount.amount }}</td>
<td>{{ discount.min_order }}</td> <td>{{ discount.min_order }}</td>
<td>{{ discount.max_usage || "" }}</td>
<td>{{ convertToJalali(discount?.starts_at) }}</td> <td>{{ convertToJalali(discount?.starts_at) }}</td>
<td>{{ convertToJalali(discount?.expires_at) }}</td> <td>{{ convertToJalali(discount?.expires_at) }}</td>
<td> <td>


+ 119
- 69
src/views/live-preview/pages/discounts/editDiscount.vue Näytä tiedosto

@@ -108,7 +108,7 @@
> >
<option value="cat">دسته</option> <option value="cat">دسته</option>
<option value="product">محصول</option> <option value="product">محصول</option>
<option value="both">هردو</option>
<option value="all">همه</option>
</select> </select>
</div> </div>
<small v-if="errors.discountType" class="text-danger"> <small v-if="errors.discountType" class="text-danger">
@@ -116,47 +116,33 @@
</small> </small>
</BCol> </BCol>


<BCol v-if="whichPart === 'cat' || whichPart === 'both'" md="6">
<BCol v-if="whichPart === 'cat'" md="6">
<div class="form-group"> <div class="form-group">
<label class="form-label">دسته</label> <label class="form-label">دسته</label>
<select
:class="{ 'is-invalid': errors.selectedCat }"
<VueSelect
style="--vs-min-height: 48px; --vs-border-radius: 8px"
:isLoading="categorySelectorLoader"
v-model="selectedCat" v-model="selectedCat"
class="form-control"
@select="clearError('selectedCat')"
placeholder="انتخاب دسته"
>
<option v-for="cat in cats" :key="cat.id" :value="cat.id">
{{ cat.title }}
</option>
</select>
:options="formattedCategories"
placeholder="دسته ای را انتخاب کنید"
/>
</div> </div>
<small v-if="errors.selectedCat" class="text-danger"> <small v-if="errors.selectedCat" class="text-danger">
{{ errors.selectedCat }} {{ errors.selectedCat }}
</small> </small>
</BCol> </BCol>


<BCol
v-if="whichPart === 'product' || whichPart === 'both'"
md="6"
>
<BCol v-if="whichPart === 'product'" md="6">
<div class="form-group"> <div class="form-group">
<label class="form-label">محصول</label> <label class="form-label">محصول</label>
<select
:class="{ 'is-invalid': errors.selectedProduct }"
<VueSelect
style="--vs-min-height: 48px; --vs-border-radius: 8px"
:isLoading="productSelectorLoader"
v-model="selectedProduct" v-model="selectedProduct"
class="form-control"
@select="clearError('selectedProduct')"
placeholder="انتخاب محصول"
>
<option
v-for="product in products"
:key="product.id"
:value="product.id"
>
{{ product.title }}
</option>
</select>
:options="formattedProducts"
placeholder="محصولی را انتخاب کنید "
@search="handleProductSearch"
/>
</div> </div>
<small v-if="errors.selectedProduct" class="text-danger"> <small v-if="errors.selectedProduct" class="text-danger">
{{ errors.selectedProduct }} {{ errors.selectedProduct }}
@@ -220,12 +206,13 @@
</template> </template>


<script> <script>
import moment from "moment";
import VueSelect from "vue3-select-component";
import moment from "jalali-moment";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { toast } from "vue3-toastify"; import { toast } from "vue3-toastify";
import "vue3-toastify/dist/index.css"; import "vue3-toastify/dist/index.css";
import ApiServiece from "@/services/ApiService"; import ApiServiece from "@/services/ApiService";
import { ref, onMounted } from "vue";
import { ref, onMounted, computed } from "vue";
import Layout from "@/layout/custom.vue"; import Layout from "@/layout/custom.vue";
import DatePicker from "vue3-persian-datetime-picker"; import DatePicker from "vue3-persian-datetime-picker";


@@ -234,39 +221,78 @@ export default {
components: { components: {
Layout, Layout,
DatePicker, DatePicker,
VueSelect,
}, },
setup() { setup() {
const route = useRoute(); const route = useRoute();
const productSelectorLoader = ref(false);
const title = ref(); const title = ref();
const discountType = ref(); const discountType = ref();
const amount = ref(); const amount = ref();
const minOrder = ref(); const minOrder = ref();
const selectedCat = ref(); const selectedCat = ref();
const selectedProduct = ref();
const selectedProduct = ref({
value: 'product-id-123', // Example selected product ID
label: 'Amazing Product', // Example selected product label
});

const startDate = ref(); const startDate = ref();
const expire = ref(); const expire = ref();
const maxUsage = ref(); const maxUsage = ref();
const whichPart = ref(); const whichPart = ref();
const loading = ref(false); const loading = ref(false);
const cats = ref([]);
const categories = ref([]);
const errors = ref({}); const errors = ref({});
const products = ref(); const products = ref();
const discount = ref(); const discount = ref();


const getCats = () => {
ApiServiece.get(`admin/categories`)
.then((resp) => {
cats.value = resp.data.data;
})
.catch((err) => {
console.log(err);
});
const formattedCategories = computed(() =>
Array.isArray(categories.value)
? categories.value.map((category) => ({
value: category.id,
label: category.title,
}))
: []
);

const handleProductSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;
productSelectorLoader.value = true;
try {
const response = await ApiServiece.get(
`admin/products?title=${searchTerm}`
);
products.value = response.data.data;
productSelectorLoader.value = false;
} catch (error) {
productSelectorLoader.value = false;
products.value = [];
}
}; };


const getProduct = () => {
ApiServiece.get(`admin/products`)
const formattedProducts = computed(() => {
// If products are available, map them to options
if (Array.isArray(products.value) && products.value.length > 0) {
return products.value.map((product) => ({
value: product.id,
label: product.title,
}));
}

// If no products are available, show a placeholder option
return [
{
value: "",
label: "برای جستجو شروع به تایپ کنید...",
disabled: true,
},
];
});

const getCats = () => {
ApiServiece.get(`admin/categories`)
.then((resp) => { .then((resp) => {
products.value = resp.data.data;
categories.value = resp.data.data;
}) })
.catch((err) => { .catch((err) => {
console.log(err); console.log(err);
@@ -285,14 +311,30 @@ export default {
if (discount.value.category_id) { if (discount.value.category_id) {
whichPart.value = "cat"; whichPart.value = "cat";
} }
console.log(discount.value.product_id);


selectedProduct.value = discount.value.product_id;
if (!discount.value.category_id && !discount.value.product_id) {
whichPart.value = "all";
}

if (discount.value.product_id) { if (discount.value.product_id) {
whichPart.value = "product"; whichPart.value = "product";
} }
startDate.value = discount.value.starts_at;
expire.value = discount.value.expires_at;

if (discount?.value.starts_at) {
startDate.value = moment(
discount?.value.starts_at,
"YYYY-MM-DD HH:mm:ss"
).format("jYYYY/jMM/jDD HH:mm:ss");
}

if (discount?.value.expires_at) {
expire.value = moment(
discount?.value.expires_at,
"YYYY-MM-DD HH:mm:ss"
).format("jYYYY/jMM/jDD HH:mm:ss");
}

maxUsage.value = discount.value.max_usage; maxUsage.value = discount.value.max_usage;
}) })
.catch((err) => { .catch((err) => {
@@ -328,24 +370,14 @@ export default {
errors.value.discountType = "وارد کردن حالت تخفیف الزامی است"; errors.value.discountType = "وارد کردن حالت تخفیف الزامی است";
if (!amount.value) if (!amount.value)
errors.value.amount = "وارد کردن مقدار تخفیف الزامی می باشد"; errors.value.amount = "وارد کردن مقدار تخفیف الزامی می باشد";
if (!minOrder.value)
errors.value.minOrder = "وارد کردن حداقل میزان تخفیف الزامی می باشد";
if (
!selectedCat.value &&
(whichPart.value === "cat" || whichPart.value === "both")
)

if (!selectedCat.value && whichPart.value === "cat")
errors.value.selectedCat = "انتخاب دسته برای تخفیف الزامی می باشد"; errors.value.selectedCat = "انتخاب دسته برای تخفیف الزامی می باشد";
if (
!selectedProduct.value &&
(whichPart.value === "product" || whichPart.value === "both")
)
if (!selectedProduct.value && whichPart.value === "product")
errors.value.selectedProduct = "انتخاب محصول برای تخفیف الزامی می باشد"; errors.value.selectedProduct = "انتخاب محصول برای تخفیف الزامی می باشد";
if (!startDate.value) if (!startDate.value)
errors.value.startDate = "انتخاب تاریخ اعمال تخفیف الزامی می باشد "; errors.value.startDate = "انتخاب تاریخ اعمال تخفیف الزامی می باشد ";
if (!expire.value)
errors.value.expire = "انتخاب تاریخ انقضای تخفیف الزامی می باشد ";
if (!maxUsage.value)
errors.value.maxUsage = "مشخص کنید تخفیف چند بار مصرف است ";

if (!whichPart.value) if (!whichPart.value)
errors.value.whichPart = "مشخص کنید تخفیف بر چه بخشی اعمال شود"; errors.value.whichPart = "مشخص کنید تخفیف بر چه بخشی اعمال شود";
return Object.keys(errors.value).length === 0; return Object.keys(errors.value).length === 0;
@@ -357,7 +389,7 @@ export default {


onMounted(() => { onMounted(() => {
getCats(); getCats();
getProduct();
getDiscount(); getDiscount();
}); });


@@ -375,16 +407,30 @@ export default {
formData.append("type", discountType.value); formData.append("type", discountType.value);
formData.append("amount", amount.value); formData.append("amount", amount.value);
formData.append("min_order", minOrder.value); formData.append("min_order", minOrder.value);
if (whichPart.value === "cat" || whichPart.value === "both") {
if (whichPart.value === "cat") {
formData.append("category_id", selectedCat.value); formData.append("category_id", selectedCat.value);
} }


if (whichPart.value === "product" || whichPart.value === "both") {
if (whichPart.value === "product") {
formData.append("product_id", selectedProduct.value); formData.append("product_id", selectedProduct.value);
} }


formData.append("starts_at", startDate.value);
formData.append("expires_at", expire.value);
if (startDate.value) {
const georgianDate = moment(
startDate.value,
"jYYYY/jMM/jDD HH:mm:ss"
).format("YYYY/MM/DD HH:mm:ss");
formData.append("starts_at", georgianDate);
}

if (expire.value) {
const georgianDate = moment(
expire.value,
"jYYYY/jMM/jDD HH:mm:ss"
).format("YYYY/MM/DD HH:mm:ss");
formData.append("expires_at", georgianDate);
}

formData.append("max_usage", maxUsage.value); formData.append("max_usage", maxUsage.value);


ApiServiece.post(`/admin/discounts`, formData) ApiServiece.post(`/admin/discounts`, formData)
@@ -407,7 +453,7 @@ export default {
}; };


return { return {
cats,
categories,
errors, errors,
title, title,
products, products,
@@ -425,6 +471,10 @@ export default {
whichPart, whichPart,
clearError, clearError,
loading, loading,
formattedCategories,
formattedProducts,
handleProductSearch,
productSelectorLoader,
}; };
}, },
}; };


+ 24
- 4
src/views/live-preview/pages/identity/idenities.vue Näytä tiedosto

@@ -34,18 +34,19 @@ export default {
const attributeTitle = ref(); const attributeTitle = ref();
const attributeId = ref(); const attributeId = ref();
const attrebuteCat = ref(); const attrebuteCat = ref();
const selectorLoader = ref(false);


const handleSearch = async (searchTerm) => { const handleSearch = async (searchTerm) => {
if (searchTerm.length < 3) return; if (searchTerm.length < 3) return;
selectorLoader.value = true;
try { try {
const response = await ApiServiece.get( const response = await ApiServiece.get(
`admin/categories?title=${searchTerm}` `admin/categories?title=${searchTerm}`
); );
categories.value = response.data.data; categories.value = response.data.data;
console.log(categories.value, "products");
selectorLoader.value = false;
} catch (error) { } catch (error) {
console.error("Error fetching products:", error);
selectorLoader.value = false;
categories.value = []; categories.value = [];
} }
}; };
@@ -231,6 +232,7 @@ export default {
formattedCategories, formattedCategories,
handleSearch, handleSearch,
selectedcategory, selectedcategory,
selectorLoader,
}; };
}, },
}; };
@@ -255,10 +257,17 @@ export default {
<VueSelect <VueSelect
style="--vs-border-radius: 16px" style="--vs-border-radius: 16px"
v-model="selectedcategory" v-model="selectedcategory"
:isLoading="selectorLoader"
:options="formattedCategories" :options="formattedCategories"
placeholder="دسته ای را انتخاب کنید" placeholder="دسته ای را انتخاب کنید"
@search="handleSearch" @search="handleSearch"
/>
>
<template #menu-header>
<div class="menu-header">
<h3>دسته ها</h3>
</div>
</template>
</VueSelect>
</div> </div>
<button <button
data-bs-toggle="modal" data-bs-toggle="modal"
@@ -500,4 +509,15 @@ export default {
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
} }
.menu-header {
position: sticky;
top: 0;
padding: 0.5rem 1rem;
background-color: #f4f4f5;
}

.menu-header h3 {
margin: 0;
color: var(--vs-option-text-color);
}
</style> </style>

+ 24
- 9
src/views/live-preview/pages/orders/approvedOrders.vue Näytä tiedosto

@@ -13,6 +13,7 @@ export default {
DatePicker, DatePicker,
}, },
setup() { setup() {
const selectorLoader = ref(false);
const isLoading = ref(false); const isLoading = ref(false);
const date = ref([]); const date = ref([]);
const brands = ref([]); const brands = ref([]);
@@ -25,6 +26,18 @@ export default {
const selectedBrand = ref(); const selectedBrand = ref();
const allProducts = ref([]); const allProducts = ref([]);
const getAllProducts = () => { const getAllProducts = () => {
if (date.value[0]) {
date.value[0] = moment(date.value[0], "YYYY-MM-DD HH:mm:ss").format(
"YYYY/MM/DD HH:mm:ss"
);
}

if (date.value[1]) {
date.value[1] = moment(date.value[1], "YYYY-MM-DD HH:mm:ss").format(
"YYYY/MM/DD HH:mm:ss"
);
}

filterLoading.value = true; filterLoading.value = true;
ApiServiece.get( ApiServiece.get(
`admin/orders/order-items/approved?brand_id=${ `admin/orders/order-items/approved?brand_id=${
@@ -46,10 +59,10 @@ export default {
}; };


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


const getFile = () => { const getFile = () => {
isLoading.value = true; isLoading.value = true;
@@ -149,14 +162,15 @@ export default {


const handleBrandSearch = async (searchTerm) => { const handleBrandSearch = async (searchTerm) => {
if (searchTerm.length < 3) return; if (searchTerm.length < 3) return;
selectorLoader.value = true;
try { try {
const response = await ApiServiece.get( const response = await ApiServiece.get(
`admin/brands?title=${searchTerm}` `admin/brands?title=${searchTerm}`
); );
brands.value = response.data.data; brands.value = response.data.data;
selectorLoader.value = false;
} catch (error) { } catch (error) {
console.error("Error fetching products:", error);
selectorLoader.value = false;
brands.value = []; brands.value = [];
} }
}; };
@@ -193,6 +207,7 @@ export default {
formatWithCommas, formatWithCommas,
handleBrandSearch, handleBrandSearch,
filterLoading, filterLoading,
selectorLoader,
}; };
}, },
}; };
@@ -214,17 +229,17 @@ export default {
style="--vs-border-radius: 8px; min-width: 180px" style="--vs-border-radius: 8px; min-width: 180px"
v-model="selectedBrand" v-model="selectedBrand"
:options="formattedBrands" :options="formattedBrands"
:isLoading="selectorLoader"
placeholder="برندی را انتخاب کنید" placeholder="برندی را انتخاب کنید"
@search="handleBrandSearch" @search="handleBrandSearch"
/> />


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


+ 15
- 4
src/views/live-preview/pages/orders/orders.vue Näytä tiedosto

@@ -82,6 +82,7 @@ export default {
shipping: "badge-shipping", shipping: "badge-shipping",
delivered: "badge-delivered", delivered: "badge-delivered",
canceled: "badge-canceled", canceled: "badge-canceled",
in_cart: "badge-in-cart",
}; };
return statusClasses[status] || "badge-secondary"; return statusClasses[status] || "badge-secondary";
}; };
@@ -96,6 +97,7 @@ export default {
shipping: "در حال ارسال", shipping: "در حال ارسال",
delivered: "تحویل‌شده", delivered: "تحویل‌شده",
canceled: "لغو‌شده", canceled: "لغو‌شده",
in_cart: "در سبد خرید",
}; };
return statusLabels[status] || "نامشخص"; return statusLabels[status] || "نامشخص";
}; };
@@ -270,23 +272,29 @@ export default {
<table class="table table-hover table-bordered m-0" dir="rtl"> <table class="table table-hover table-bordered m-0" dir="rtl">
<thead class="table-light"> <thead class="table-light">
<tr> <tr>
<th>شناسه</th>
<th>وضعیت</th> <th>وضعیت</th>

<th>قیمت</th>
<th>کاربر</th>
<th>کد پیگیری</th>
<th>تاریخ ایجاد</th> <th>تاریخ ایجاد</th>

<th>عملیات</th> <th>عملیات</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="order in orders" :key="order.id"> <tr v-for="order in orders" :key="order.id">
<td>{{ order?.id }}</td>
<td> <td>
<span class="badge" :class="getStatusClass(order.status)"> <span class="badge" :class="getStatusClass(order.status)">
{{ getStatusLabel(order.status) }} {{ getStatusLabel(order.status) }}
</span> </span>
</td> </td>


<td>{{ order?.total_price || "" }}</td>
<td>{{ order?.user?.mobile || "مهمان" }}</td>

<td>{{ order?.tracking_code || "" }}</td>

<td>{{ convertToJalali(order?.created_at) }}</td> <td>{{ convertToJalali(order?.created_at) }}</td>


<td> <td>
@@ -597,6 +605,9 @@ export default {
.badge-canceled { .badge-canceled {
background-color: #6c757d; background-color: #6c757d;
} }
.badge-in-cart {
background-color: #0620c6; /* You can choose any color you prefer */
}
.dropdown-item { .dropdown-item {
cursor: pointer; cursor: pointer;
} }


+ 170
- 123
src/views/live-preview/pages/products/addProduct.vue Näytä tiedosto

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


<BCol v-if="spescial" md="6">
<BCol v-if="spescial == 1" md="6">
<div class="form-group"> <div class="form-group">
<label class="form-label"> تخفیف ویژه </label> <label class="form-label"> تخفیف ویژه </label>
<input <input
@@ -252,7 +252,7 @@
</small> </small>
</BCol> </BCol>


<BCol v-if="spescial" md="6">
<BCol v-if="spescial == 1" md="6">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
تاریخ انقضای تخفیف محصول ویژه تاریخ انقضای تخفیف محصول ویژه
@@ -262,7 +262,6 @@
:format="'jYYYY/jMM/jDD HH:mm:ss'" :format="'jYYYY/jMM/jDD HH:mm:ss'"
type="datetime" type="datetime"
v-model="expire" v-model="expire"
@input="handleInput"
></DatePicker> ></DatePicker>
</div> </div>
<small v-if="errors.expire" class="text-danger"> <small v-if="errors.expire" class="text-danger">
@@ -270,7 +269,7 @@
</small> </small>
</BCol> </BCol>


<BCol md="6">
<!-- <BCol md="6">
<div class="form-group"> <div class="form-group">
<label class="form-label">دسته</label> <label class="form-label">دسته</label>
<select <select
@@ -288,26 +287,34 @@
<small v-if="errors.selectedCat" class="text-danger"> <small v-if="errors.selectedCat" class="text-danger">
{{ errors.selectedCat }} {{ errors.selectedCat }}
</small> </small>
</BCol> -->

<BCol md="6">
<div class="form-group">
<label class="form-label">دسته</label>
<VueSelect
:isLoading="categorySelectorLoader"
v-model="selectedCat"
:options="formattedCategories"
placeholder="دسته ای را انتخاب کنید"
@search="handleSearch"
/>
</div>
<small v-if="errors.selectedCat" class="text-danger">
{{ errors.selectedCat }}
</small>
</BCol> </BCol>


<BCol md="6"> <BCol md="6">
<div class="form-group"> <div class="form-group">
<label class="form-label">برند</label> <label class="form-label">برند</label>
<select
:class="{ 'is-invalid': errors.selectedBrand }"
<VueSelect
v-model="selectedBrand" v-model="selectedBrand"
class="form-control"
@change="clearError('selectedBrand')"
placeholder="انتخاب برند محصول"
>
<option
v-for="brand in brands"
:key="brand.id"
:value="brand.id"
>
{{ brand.title }}
</option>
</select>
:isLoading="brandSelectorLoader"
:options="formattedBrands"
placeholder="برندی را انتخاب کنید"
@search="handleBrandSearch"
/>
</div> </div>
<small v-if="errors.selectedBrand" class="text-danger"> <small v-if="errors.selectedBrand" class="text-danger">
{{ errors.selectedBrand }} {{ errors.selectedBrand }}
@@ -549,7 +556,10 @@
</div> </div>
</div> </div>
</BCardFooter> </BCardFooter>
<addIdentity :cats="cats" @identity-updated="handleIdentityUpdate" />
<addIdentity
:cats="categories"
@identity-updated="handleIdentityUpdate"
/>
<addAttribute <addAttribute
:attributeValues="attributeValues" :attributeValues="attributeValues"
@attribute-updated="handleAttributeUpdated()" @attribute-updated="handleAttributeUpdated()"
@@ -561,15 +571,17 @@
</template> </template>


<script> <script>
import VueSelect from "vue3-select-component";
import addAttribute from "@/components/modals/attribute/addAttribute.vue"; import addAttribute from "@/components/modals/attribute/addAttribute.vue";
import moment from "moment";
import moment from "jalali-moment";
import { toast } from "vue3-toastify"; import { toast } from "vue3-toastify";
import "vue3-toastify/dist/index.css"; import "vue3-toastify/dist/index.css";
import ApiServiece from "@/services/ApiService"; import ApiServiece from "@/services/ApiService";
import { ref, onMounted, watch } from "vue";
import { ref, onMounted, watch, computed } from "vue";
import Layout from "@/layout/custom.vue"; import Layout from "@/layout/custom.vue";
import DatePicker from "vue3-persian-datetime-picker"; import DatePicker from "vue3-persian-datetime-picker";
import addIdentity from "@/components/modals/identity/addIdentity.vue"; import addIdentity from "@/components/modals/identity/addIdentity.vue";

export default { export default {
name: "SAMPLE-PAGE", name: "SAMPLE-PAGE",
components: { components: {
@@ -577,8 +589,12 @@ export default {
DatePicker, DatePicker,
addIdentity, addIdentity,
addAttribute, addAttribute,
VueSelect,
}, },
setup() { setup() {
const categorySelectorLoader = ref(false);
const categories = ref([]);
const brandSelectorLoader = ref(false);
const attributeValues = ref(); const attributeValues = ref();
const relatedAttrebutes = ref([]); const relatedAttrebutes = ref([]);
const countInCarton = ref(); const countInCarton = ref();
@@ -610,18 +626,8 @@ export default {
const blogCat = ref(); const blogCat = ref();
const author = ref(""); const author = ref("");
const editor = ref(null); const editor = ref(null);
const cats = ref([]);
const editorContent = ref("");


const getCats = () => {
ApiServiece.get(`admin/categories`)
.then((resp) => {
cats.value = resp.data.data;
})
.catch((err) => {
console.log(err);
});
};
const editorContent = ref("");


const getAttributeValues = () => { const getAttributeValues = () => {
ApiServiece.get(`admin/attributes`).then((resp) => { ApiServiece.get(`admin/attributes`).then((resp) => {
@@ -656,28 +662,54 @@ export default {
}); });
}; };


const handleInput = () => {
if (expire.value) {
// Convert from Jalali to Georgian (Gregorian)
expire.value = moment(expire.value, "jYYYY/jMM/jDD HH:mm:ss").format(
"YYYY-MM-DD HH:mm:ss"
const handleBrandSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;
brandSelectorLoader.value = true;
try {
const response = await ApiServiece.get(
`admin/brands?title=${searchTerm}`
); );
} else {
expire.value = null;
clearError("expire");
brands.value = response.data.data;
brandSelectorLoader.value = false;
} catch (error) {
brandSelectorLoader.value = false;
brands.value = [];
} }
}; };


const getBrands = () => {
ApiServiece.get(`admin/brands`)
.then((resp) => {
brands.value = resp.data.data;
})
.catch((err) => {
console.log(err);
});
const formattedBrands = computed(() =>
Array.isArray(brands.value)
? brands.value.map((brand) => ({
value: brand.id,
label: brand.title,
}))
: []
);

const handleSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;
categorySelectorLoader.value = true;
try {
const response = await ApiServiece.get(
`admin/categories?title=${searchTerm}`
);
categories.value = response.data.data;
categorySelectorLoader.value = false;
} catch (error) {
categorySelectorLoader.value = false;
categories.value = [];
}
}; };


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

const getAttrebuteValues = () => { const getAttrebuteValues = () => {
ApiServiece.get(`admin/attribute-values?attribute_id=1`) ApiServiece.get(`admin/attribute-values?attribute_id=1`)
.then((resp) => { .then((resp) => {
@@ -815,18 +847,12 @@ export default {
errors.value[field] = ""; errors.value[field] = "";
}; };



onMounted(() => { onMounted(() => {
getCats();
getBrands();
getAttrebuteValues(); getAttrebuteValues();
getAttributeValues(); getAttributeValues();
}); });
let id = "";
const submitForm = () => { const submitForm = () => {
console.log(expire.value , "test")
if (!validateForm()) { if (!validateForm()) {
toast.error("لطفا فیلد های لازم را وارد نمایید", { toast.error("لطفا فیلد های لازم را وارد نمایید", {
position: "top-right", position: "top-right",
@@ -842,6 +868,8 @@ export default {
formData.append("slug", slug.value); formData.append("slug", slug.value);
formData.append("summary", summary.value); formData.append("summary", summary.value);
formData.append("description", description.value); formData.append("description", description.value);
formData.append("is_special", spescial.value);
formData.append("is_chosen", isChosen.value);
if (productType.value == 2) { if (productType.value == 2) {
formData.append("wholesale_price", wholesalePrice.value); formData.append("wholesale_price", wholesalePrice.value);
} }
@@ -858,31 +886,24 @@ export default {
formData.append("chosen_price", chosenPrice.value); formData.append("chosen_price", chosenPrice.value);
} }


if (isChosen.value == 1 && spescial.value == 0) {
formData.append("is_chosen", isChosen.value);
}

if (isChosen.value == 0 && spescial.value == 1) {
formData.append("is_chosen", isChosen.value);
}

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

if (spescial.value == 1 && isChosen.value == 0) {
formData.append("is_special", spescial.value);
}


if (spescial.value == 0 && isChosen.value == 1) {
formData.append("is_special", spescial.value);
// Convert Jalali to Gregorian before sending
const georgianDate = moment(
expire.value,
"jYYYY/jMM/jDD HH:mm:ss"
).format("YYYY/MM/DD HH:mm:ss");
formData.append("special_expires_at", georgianDate);
} }


formData.append("brand_id", selectedBrand.value); formData.append("brand_id", selectedBrand.value);
formData.append("category_id", selectedCat.value); formData.append("category_id", selectedCat.value);
formData.append("count_in_carton", countInCarton.value);
if (productType.value === "2" || productType.value === "3") {
formData.append("count_in_carton", countInCarton.value);
}

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


ApiServiece.post(`admin/products`, formData, { ApiServiece.post(`admin/products`, formData, {
@@ -892,76 +913,98 @@ export default {
}, },
}) })
.then((resp) => { .then((resp) => {
id = resp.data.data.id;
console.log(id);
const id = resp.data.data.id;


// 🔸 Call attributes API if needed
selectedAttributes.value = attrebutes.value selectedAttributes.value = attrebutes.value
.filter((attribute) => attribute.isChecked)
.filter((attribute) => attribute.isChecked && attribute.value)
.map((attribute) => ({ .map((attribute) => ({
attribute_value_id: attribute.id, attribute_value_id: attribute.id,
inventory: attribute.value, inventory: attribute.value,
type: productType.value, type: productType.value,
})); }));


const finalPayload = {
productAttributes: selectedAttributes.value,
};

const jsonString = JSON.stringify(finalPayload, null, 2);

console.log(jsonString);
ApiServiece.post(`admin/products/${id}/attributes`, jsonString)
.then(() => {
selectedIdentities.value = relatedAttrebutes.value
.filter((identity) => identity.isChecked)
.map((identity) => ({
attribute_id: identity.id,
attribute_value_title: identity.value,
}));

const finalPayload = {
productSolidAttributes: selectedIdentities.value,
};

const jsonString = JSON.stringify(finalPayload, null, 2);
ApiServiece.post(
`admin/products/${id}/solid-attributes`,
jsonString
).then((resp) => {
console.log(resp);
});
})
.then((resp) => {
console.log(resp);
images.value.map((image) => {
console.log(image.file);
const formData = new FormData();
formData.append("image", image.file);
ApiServiece.post(`admin/products/${id}/images`, formData, {
headers: {
"content-type": "multipart",
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
});
if (selectedAttributes.value.length > 0) {
const json = JSON.stringify(
{ productAttributes: selectedAttributes.value },
null,
2
);
ApiServiece.post(`admin/products/${id}/attributes`, json);
}

// 🔸 Call solid-attributes API if needed
selectedIdentities.value = relatedAttrebutes.value
.filter((identity) => identity.isChecked)
.map((identity) => ({
attribute_id: identity.id,
attribute_value_title: identity.value,
}));

if (selectedIdentities.value.length > 0) {
const json = JSON.stringify(
{ productSolidAttributes: selectedIdentities.value },
null,
2
);
ApiServiece.post(`admin/products/${id}/solid-attributes`, json);
}

// 🔸 Upload images if any
const validImages = images.value.filter((image) => image.file);
if (validImages.length > 0) {
console.log(validImages, "valid images");
validImages.forEach((image) => {
const formData = new FormData();
formData.append("image", image.file);
ApiServiece.post(`admin/products/${id}/images`, formData, {
headers: {
"content-type": "multipart",
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
}); });
}); });
})
}


.then(() => {
loading.value = false;
toast.success("!محصول با موفقیت اضافه شد", { toast.success("!محصول با موفقیت اضافه شد", {
position: "top-right", position: "top-right",
autoClose: 1000, autoClose: 1000,
}); });
}) })

.catch((error) => { .catch((error) => {
console.error(error); console.error(error);
loading.value = false;
toast.error(`${error?.response?.data?.message}`, { toast.error(`${error?.response?.data?.message}`, {
position: "top-right", position: "top-right",
autoClose: 1000, autoClose: 1000,
}); });
})
.finally(() => {
loading.value = false;
title.value = "";
imagePreview.value = null;
slug.value = "";
summary.value = "";
description.value = "";
spescial.value = 0;
isChosen.value = 0;
wholesalePrice.value = "";
retailePrice.value = "";
productType.value = "";
chosenPrice.value = "";
spescialPrice.value = "";
expire.value = "";
selectedBrand.value = null;
selectedCat.value = null;
countInCarton.value = "";
image.value = null;
images.value = [];
attrebutes.value.forEach((attribute) => {
attribute.isChecked = false;
attribute.value = "";
});
relatedAttrebutes.value.forEach((identity) => {
identity.isChecked = false;
});
}); });
}; };


@@ -970,7 +1013,6 @@ export default {
slug, slug,
summary, summary,
editor, editor,
cats,
errors, errors,
image, image,
imagePreview, imagePreview,
@@ -1001,13 +1043,18 @@ export default {
spescialPrice, spescialPrice,
chosenPrice, chosenPrice,
expire, expire,
handleInput,
countInCarton, countInCarton,
relatedAttrebutes, relatedAttrebutes,
selectedIdentities, selectedIdentities,
attributeValues, attributeValues,
handleAttributeUpdated, handleAttributeUpdated,
handleIdentityUpdate, handleIdentityUpdate,
handleBrandSearch,
brandSelectorLoader,
formattedBrands,
handleSearch,
formattedCategories,
categorySelectorLoader,
}; };
}, },
}; };


+ 114
- 104
src/views/live-preview/pages/products/editProduct.vue Näytä tiedosto

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


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


<BCol v-if="spescial" md="6">
<BCol v-if="spescial == 1" md="6">
<div class="form-group"> <div class="form-group">
<label class="form-label"> تخفیف ویژه </label> <label class="form-label"> تخفیف ویژه </label>
<input <input
@@ -246,7 +246,7 @@
</small> </small>
</BCol> </BCol>


<BCol v-if="spescial" md="6">
<BCol v-if="spescial == 1" md="6">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
تاریخ انقضای تخفیف محصول ویژه تاریخ انقضای تخفیف محصول ویژه
@@ -263,7 +263,7 @@
</small> </small>
</BCol> </BCol>


<BCol md="6">
<!-- <BCol md="6">
<div class="form-group"> <div class="form-group">
<label class="form-label">دسته</label> <label class="form-label">دسته</label>
<select <select
@@ -281,43 +281,40 @@
<small v-if="errors.blogCat" class="text-danger"> <small v-if="errors.blogCat" class="text-danger">
{{ errors.blogCat }} {{ errors.blogCat }}
</small> </small>
</BCol> -->

<BCol md="6">
<div class="form-group">
<label class="form-label">دسته</label>
<VueSelect
:isLoading="categorySelectorLoader"
v-model="selectedCat"
:options="formattedCategories"
placeholder="دسته ای را انتخاب کنید"
/>
</div>
<small v-if="errors.selectedCat" class="text-danger">
{{ errors.selectedCat }}
</small>
</BCol> </BCol>


<BCol md="6"> <BCol md="6">
<div class="form-group"> <div class="form-group">
<label class="form-label">برند</label> <label class="form-label">برند</label>
<select
:class="{ 'is-invalid': errors.selectedBrand }"
<VueSelect
v-model="selectedBrand" v-model="selectedBrand"
class="form-control"
@select="clearError('selectedBrand')"
placeholder="انتخاب برند محصول"
>
<option
v-for="brand in brands"
:key="brand.id"
:value="brand.id"
>
{{ brand.title }}
</option>
</select>
:isLoading="brandSelectorLoader"
:options="formattedBrands"
placeholder="برندی را انتخاب کنید"
@search="handleBrandSearch"
/>
</div> </div>
<small v-if="errors.selectedBrand" class="text-danger"> <small v-if="errors.selectedBrand" class="text-danger">
{{ errors.selectedBrand }} {{ errors.selectedBrand }}
</small> </small>
</BCol> </BCol>


<BCard>
<div class="card-header">
<h5 class="mb-0">ویرایش ویژگی ها</h5>
</div>
<template v-if="locals.length === 0">
<BCol>
<div class="alert alert-info text-center">
هیچ ویژگی برای این محصول انتخاب نکرده اید ...
</div>
</BCol>
</template>
<BCard v-if="locals.length !== 0">
<BRow class="g-3 mt-2"> <BRow class="g-3 mt-2">
<!-- Loop through attributes --> <!-- Loop through attributes -->
<BCol <BCol
@@ -410,18 +407,11 @@
</BRow> </BRow>
</BCard> </BCard>


<BCard>
<BCard v-if="localIdentities.length !== 0">
<div class="card-header"> <div class="card-header">
<h5 class="mb-0">ویرایش مشخصه ها</h5> <h5 class="mb-0">ویرایش مشخصه ها</h5>
</div> </div>
<template v-if="localIdentities.length === 0">
<BCol>
<div class="alert alert-info text-center">
برای شما مشخصه‌ای ثبت نشده است. برای ساختن یک مشخصه کلیک
کنید.
</div>
</BCol>
</template>

<BRow class="g-3 mt-2"> <BRow class="g-3 mt-2">
<!-- Loop through attributes --> <!-- Loop through attributes -->
<BCol <BCol
@@ -802,7 +792,7 @@
</div> </div>
</div> </div>
<addIdentity <addIdentity
:cats="cats"
:cats="categories"
@attribute-updated="handleAttributeUpdated()" @attribute-updated="handleAttributeUpdated()"
/> />
<addAttribute @attribute-updated="handleAttributeUpdated()" /> <addAttribute @attribute-updated="handleAttributeUpdated()" />
@@ -814,13 +804,14 @@
</template> </template>


<script> <script>
import VueSelect from "vue3-select-component";
import Swal from "sweetalert2"; import Swal from "sweetalert2";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { toast } from "vue3-toastify"; import { toast } from "vue3-toastify";
import "vue3-toastify/dist/index.css"; import "vue3-toastify/dist/index.css";
import moment from "moment";
import moment from "jalali-moment";
import ApiServiece from "@/services/ApiService"; import ApiServiece from "@/services/ApiService";
import { ref, onMounted, watch } from "vue";
import { ref, onMounted, watch, computed } from "vue";
import Layout from "@/layout/custom.vue"; import Layout from "@/layout/custom.vue";
import addIdentity from "@/components/modals/identity/addIdentity.vue"; import addIdentity from "@/components/modals/identity/addIdentity.vue";
import addAttribute from "@/components/modals/attribute/addAttribute.vue"; import addAttribute from "@/components/modals/attribute/addAttribute.vue";
@@ -833,6 +824,7 @@ export default {
DatePicker, DatePicker,
addIdentity, addIdentity,
addAttribute, addAttribute,
VueSelect,
}, },
setup() { setup() {
const locals = ref([ const locals = ref([
@@ -885,19 +877,45 @@ export default {
const blogCat = ref(); const blogCat = ref();
const author = ref(""); const author = ref("");
const editor = ref(null); const editor = ref(null);
const cats = ref([]);
const categories = ref([]);
const editorContent = ref(""); const editorContent = ref("");
const categorySelectorLoader = ref(false);
const brandSelectorLoader = ref(false);


const getCats = () => { const getCats = () => {
ApiServiece.get(`admin/categories`)
.then((resp) => {
cats.value = resp.data.data;
})
.catch((err) => {
console.log(err);
});
ApiServiece.get("admin/categories").then((resp) => {
categories.value = resp.data.data;
});

console.log(categories.value, "cats");
}; };


const getBrands = () => {
ApiServiece.get("admin/brands").then((resp) => {
brands.value = resp.data.data;
});

console.log(brands.value, "brands");
};

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

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

watch(selectedCat, () => { watch(selectedCat, () => {
ApiServiece.get(`admin/attributes?category_id=${selectedCat.value}`) ApiServiece.get(`admin/attributes?category_id=${selectedCat.value}`)
.then((resp) => { .then((resp) => {
@@ -937,43 +955,17 @@ export default {
}); });
}; };


const convertToGeorgian = (date) => {
return moment(date, "jYYYY/jMM/jDD HH:mm:ss").format(
"YYYY-MM-DD HH:mm:ss"
);
};

watch(expire, (newValue) => {
if (newValue) {
// Convert from Jalali to Georgian (Gregorian) format when `expire` changes
expire.value = convertToGeorgian(newValue);
console.log(expire.value); // Logs the Georgian format for debugging
}
});

const getBrands = () => {
ApiServiece.get(`admin/brands`)
.then((resp) => {
brands.value = resp.data.data;
})
.catch((err) => {
console.log(err);
});
};

const getAttrebuteValues = () => { const getAttrebuteValues = () => {
ApiServiece.get(`admin/attribute-values?attribute_id=1`) ApiServiece.get(`admin/attribute-values?attribute_id=1`)
.then((resp) => { .then((resp) => {
console.log(resp);
console.log(resp, "attrebute values");
attrebutes.value = resp.data.data; attrebutes.value = resp.data.data;
console.log("Attributes before filtering:", attrebutes.value); console.log("Attributes before filtering:", attrebutes.value);
}) })
.then(() => { .then(() => {
getProduct(); getProduct();
}) })
.then(() => {
console.log("Locals after processing:", locals.value);
})

.catch((err) => { .catch((err) => {
console.log(err); console.log(err);
}); });
@@ -1180,12 +1172,12 @@ export default {
if (!selectedBrand.value) if (!selectedBrand.value)
errors.value.selectedBrand = "انتخاب برند برای محصول ضروری می باشد"; errors.value.selectedBrand = "انتخاب برند برای محصول ضروری می باشد";


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


if (images.value.length <= 0)
errors.value.images = "انتخاب عکس برای محصول ضروری می باشد";
// if (images.value.length <= 0)
// errors.value.images = "انتخاب عکس برای محصول ضروری می باشد";


const missingInventory = attrebutes.value.filter( const missingInventory = attrebutes.value.filter(
(attribute) => (attribute) =>
@@ -1241,9 +1233,15 @@ export default {
chosenPrice.value = product.value?.chosen_price; chosenPrice.value = product.value?.chosen_price;
spescial.value = product.value?.is_special; spescial.value = product.value?.is_special;
spescialPrice.value = product.value?.special_price; spescialPrice.value = product.value?.special_price;
expire.value = product.value?.special_expires_at;
if (product.value?.special_expires_at) {
expire.value = moment(
product.value.special_expires_at,
"YYYY-MM-DD HH:mm:ss"
).format("jYYYY/jMM/jDD HH:mm:ss");
}
selectedBrand.value = product.value?.brand_id; selectedBrand.value = product.value?.brand_id;
selectedCat.value = product.value?.category_id; selectedCat.value = product.value?.category_id;

countInCarton.value = product.value?.count_in_carton; countInCarton.value = product.value?.count_in_carton;


// Update images // Update images
@@ -1255,8 +1253,9 @@ export default {
preview: imageUrl, preview: imageUrl,
})); }));


locals.value = product.value.product_attributes.map(
locals.value = product.value.attribute_value_products.map(
(productAttribute) => { (productAttribute) => {
console.log(locals.value, "locals");
return { return {
id: productAttribute.id, id: productAttribute.id,
title: productAttribute.attribute_value.title, title: productAttribute.attribute_value.title,
@@ -1283,21 +1282,18 @@ export default {


repeatedAttrebute.value = hasRepeatedAttribute; repeatedAttrebute.value = hasRepeatedAttribute;


console.log(attrebutes.value);

console.log(attrebutes.value);

localIdentities.value = product.value.product_solid_attributes.map(
(productIdentities) => {
return {
id: productIdentities.id,
title: productIdentities.attribute_value.attribute.title,
isChecked: true,
value: productIdentities.attribute_value.title,
attribute_value_id: productIdentities.attribute_value_id,
};
}
);
localIdentities.value =
product.value.solid_attribute_value_products.map(
(productIdentities) => {
return {
id: productIdentities.id,
title: productIdentities.attribute_value.attribute.title,
isChecked: true,
value: productIdentities.attribute_value.title,
attribute_value_id: productIdentities.attribute_value_id,
};
}
);
}) })
.then(() => { .then(() => {
getIdentities(); getIdentities();
@@ -1376,14 +1372,12 @@ export default {
}; };


onMounted(() => { onMounted(() => {
getAttrebuteValues();
getCats(); getCats();
getBrands(); getBrands();
getAttrebuteValues();
}); });
let id = ""; let id = "";
const submitForm = () => { const submitForm = () => {


if (!validateForm()) { if (!validateForm()) {
toast.error("لطفا فیلد های لازم را وارد نمایید", { toast.error("لطفا فیلد های لازم را وارد نمایید", {
position: "top-right", position: "top-right",
@@ -1400,7 +1394,9 @@ export default {
formData.append("slug", slug.value); formData.append("slug", slug.value);
formData.append("summary", summary.value); formData.append("summary", summary.value);
formData.append("description", description.value); formData.append("description", description.value);
formData.append("count_in_carton", countInCarton.value);
if (productType.value === "2" || productType.value === "3") {
formData.append("count_in_carton", countInCarton.value);
}
if (productType.value == 2) { if (productType.value == 2) {
formData.append("wholesale_price", wholesalePrice.value); formData.append("wholesale_price", wholesalePrice.value);
} }
@@ -1426,8 +1422,18 @@ export default {
} }


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

if (expire.value) {
const georgianDate = moment(
expire.value,
"jYYYY/jMM/jDD HH:mm:ss"
).format("YYYY/MM/DD HH:mm:ss");

formData.append("special_expires_at", georgianDate);
}
} }


if (spescial.value == 1 && isChosen.value == 0) { if (spescial.value == 1 && isChosen.value == 0) {
@@ -1562,7 +1568,7 @@ export default {
slug, slug,
summary, summary,
editor, editor,
cats,
categories,
errors, errors,
image, image,
imagePreview, imagePreview,
@@ -1607,6 +1613,10 @@ export default {
editIdentity, editIdentity,
repeatedIdentity, repeatedIdentity,
repeatedAttrebute, repeatedAttrebute,
categorySelectorLoader,
formattedCategories,
formattedBrands,
brandSelectorLoader,
}; };
}, },
}; };


+ 12
- 9
src/views/live-preview/pages/products/products.vue Näytä tiedosto

@@ -15,6 +15,8 @@ export default {
VueSelect, VueSelect,
}, },
setup() { setup() {
const brandSelectorLoader = ref(false);
const categorySelectorLoader = ref(false);
let searchTimeout = null; let searchTimeout = null;
const selectedProductType = ref(""); const selectedProductType = ref("");
const categories = ref([]); const categories = ref([]);
@@ -208,15 +210,15 @@ export default {


const handleSearch = async (searchTerm) => { const handleSearch = async (searchTerm) => {
if (searchTerm.length < 3) return; if (searchTerm.length < 3) return;
categorySelectorLoader.value = true;
try { try {
const response = await ApiServiece.get( const response = await ApiServiece.get(
`admin/categories?title=${searchTerm}` `admin/categories?title=${searchTerm}`
); );
categories.value = response.data.data; categories.value = response.data.data;
console.log(categories.value, "products");
categorySelectorLoader.value = false;
} catch (error) { } catch (error) {
console.error("Error fetching products:", error);
categorySelectorLoader.value = false;
categories.value = []; categories.value = [];
} }
}; };
@@ -232,14 +234,15 @@ export default {


const handleBrandSearch = async (searchTerm) => { const handleBrandSearch = async (searchTerm) => {
if (searchTerm.length < 3) return; if (searchTerm.length < 3) return;
brandSelectorLoader.value = true;
try { try {
const response = await ApiServiece.get( const response = await ApiServiece.get(
`admin/brands?title=${searchTerm}` `admin/brands?title=${searchTerm}`
); );
brands.value = response.data.data; brands.value = response.data.data;
brandSelectorLoader.value = false;
} catch (error) { } catch (error) {
console.error("Error fetching products:", error);
brandSelectorLoader.value = false;
brands.value = []; brands.value = [];
} }
}; };
@@ -279,6 +282,8 @@ export default {
formattedBrands, formattedBrands,
handleBrandSearch, handleBrandSearch,
selectedBrand, selectedBrand,
categorySelectorLoader,
brandSelectorLoader,
}; };
}, },
}; };
@@ -317,6 +322,7 @@ export default {
--vs-min-height: 18px; --vs-min-height: 18px;
margin-right: 7px; margin-right: 7px;
" "
:isLoading="categorySelectorLoader"
v-model="selectedcategory" v-model="selectedcategory"
:options="formattedCategories" :options="formattedCategories"
placeholder="دسته ای را انتخاب کنید" placeholder="دسته ای را انتخاب کنید"
@@ -330,6 +336,7 @@ export default {
margin-right: 7px; margin-right: 7px;
" "
v-model="selectedBrand" v-model="selectedBrand"
:isLoading="brandSelectorLoader"
:options="formattedBrands" :options="formattedBrands"
placeholder="برندی را انتخاب کنید" placeholder="برندی را انتخاب کنید"
@search="handleBrandSearch" @search="handleBrandSearch"
@@ -638,8 +645,6 @@ export default {
font-weight: bold; font-weight: bold;
} }




.Product-Image { .Product-Image {
width: 50px; width: 50px;
height: 50px; height: 50px;
@@ -681,8 +686,6 @@ export default {
color: #fff; color: #fff;
} }




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


Ladataan…
Peruuta
Tallenna